458 lines
16 KiB
Markdown
458 lines
16 KiB
Markdown
# Flutter 打包 APK 与真机安装指南
|
||
|
||
> 项目:强宝小助手(qiangbao_accounting)
|
||
> 环境:Windows 11 + Flutter 3.47.4 / Dart 3.13.3
|
||
> 测试机:小米 M2007J22C(Android 12 / MIUI 14,设备号 `9dr4gyqgivl7tw5l`)
|
||
|
||
---
|
||
|
||
## 0. 快速开始(最常用的三条命令)
|
||
|
||
```bash
|
||
# ① 开发调试:热重载,改代码存盘即生效
|
||
flutter run -d 9dr4gyqgivl7tw5l
|
||
|
||
# ② 打包正式包
|
||
flutter build apk --release
|
||
|
||
# ③ 装到手机
|
||
flutter install -d 9dr4gyqgivl7tw5l
|
||
```
|
||
|
||
---
|
||
|
||
## 1. 前置检查
|
||
|
||
```bash
|
||
# 环境是否齐全(应全绿)
|
||
flutter doctor
|
||
|
||
# 手机是否连上(应看到设备号,状态为 device 而不是 unauthorized)
|
||
adb devices
|
||
|
||
# 依赖是否安装
|
||
flutter pub get
|
||
```
|
||
|
||
如果 `adb devices` 显示 `unauthorized`,在手机上确认「允许 USB 调试」弹窗。
|
||
如果是 `offline`,拔插数据线或执行 `adb kill-server && adb start-server`。
|
||
|
||
---
|
||
|
||
## 2. 场景一:开发调试(日常用这个)
|
||
|
||
```bash
|
||
cd /d/nzy/workspace_nzy/qiangbao_accounting
|
||
flutter run -d 9dr4gyqgivl7tw5l
|
||
```
|
||
|
||
启动后终端会进入交互模式,这些键最常用:
|
||
|
||
| 按键 | 作用 |
|
||
|---|---|
|
||
| `r` | **热重载** —— 改完代码存盘后按一下,约 0.5 秒看到效果(不重启 App) |
|
||
| `R` | 热重启 —— 状态重置,但比重装快得多 |
|
||
| `q` | 退出并断开 |
|
||
| `p` | 显示 Widget 布局网格(调样式时很有用) |
|
||
| `o` | 切换 iOS/Android 平台预览模式 |
|
||
|
||
**默认是 debug 模式**,特点:
|
||
- 启动慢、体积大(本项目 debug APK 81MB),但支持热重载
|
||
- 后端地址:Android 真机走 `http://localhost:12345/api`(依赖 adb reverse,见第 6 节)
|
||
|
||
想跑 release 模式验证性能(无热重载):
|
||
```bash
|
||
flutter run --release -d 9dr4gyqgivl7tw5l
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 场景二:打包 APK
|
||
|
||
### 3.1 通用包(最省事,能装到任何 Android 手机)
|
||
|
||
```bash
|
||
flutter build apk --release
|
||
```
|
||
产物:`build/app/outputs/flutter-apk/app-release.apk`
|
||
|
||
### 3.2 按 CPU 架构拆分(体积更小,推荐)
|
||
|
||
```bash
|
||
flutter build apk --release --split-per-abi
|
||
```
|
||
产物(三个文件):`build/app/outputs/flutter-apk/`
|
||
- `app-arm64-v8a-release.apk` —— **现代手机装这个**(小米 M2007J22C 就是 arm64)
|
||
- `app-armeabi-v7a-release.apk` —— 老设备
|
||
- `app-x86_64-release.apk` —— 模拟器
|
||
|
||
体积对比:通用包约为单架构包的 **2~3 倍**(因为把所有架构的原生库都塞进去了)。
|
||
|
||
### 3.3 只出指定架构
|
||
|
||
```bash
|
||
flutter build apk --release --target-platform android-arm64
|
||
```
|
||
|
||
### 3.4 其他选项
|
||
|
||
```bash
|
||
flutter build apk --release -v # 显示详细日志(排查构建失败用)
|
||
flutter clean && flutter pub get # 构建异常时清缓存重来
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 场景三:安装到手机
|
||
|
||
### 方式 A:Flutter 命令(推荐)
|
||
|
||
```bash
|
||
flutter install -d 9dr4gyqgivl7tw5l
|
||
```
|
||
会自动找最近构建的 APK 并安装。想指定安装 debug 包:
|
||
```bash
|
||
flutter install --debug -d 9dr4gyqgivl7tw5l
|
||
```
|
||
|
||
### 方式 B:adb 直接安装
|
||
|
||
```bash
|
||
adb install -r build/app/outputs/flutter-apk/app-release.apk
|
||
```
|
||
- `-r` = 覆盖安装(保留数据)
|
||
- `-d` = 允许降级安装
|
||
- 指定手机:`adb -s 9dr4gyqgivl7tw5l install -r xxx.apk`
|
||
|
||
### 方式 C:文件管理器手动安装(绕开 USB 限制)
|
||
|
||
```bash
|
||
# 先把 APK 推到手机存储
|
||
MSYS_NO_PATHCONV=1 adb push build/app/outputs/flutter-apk/app-release.apk /sdcard/Download/qiangbao.apk
|
||
```
|
||
然后在手机上:
|
||
1. 打开「**文件管理**」
|
||
2. 进入 **手机存储 → Download**
|
||
3. 点击 `qiangbao.apk`
|
||
4. 若提示「禁止安装未知来源应用」→ 点提示里的**设置**,允许「文件管理」安装应用
|
||
5. MIUI 安全检测后选「**继续安装**」
|
||
|
||
> ⚠️ Windows 上用 Git Bash 执行 `adb push` 必须加 `MSYS_NO_PATHCONV=1`,否则 `/sdcard/...` 会被错误转换成 `C:/Program Files/Git/sdcard/...`。
|
||
|
||
### 卸载
|
||
|
||
```bash
|
||
adb uninstall com.qiangbao.qiangbao_accounting
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 小米 MIUI 特殊设置(必读)
|
||
|
||
如果没有开启下面这个开关,安装会被拦截并报:
|
||
|
||
```
|
||
Failure [INSTALL_FAILED_USER_RESTRICTED: Install canceled by user]
|
||
```
|
||
|
||
**开启路径**:
|
||
```
|
||
设置 → 更多设置 → 开发者选项 → 「USB 安装」→ 打开
|
||
```
|
||
在「调试」分组里,位于「USB 调试」下方。
|
||
|
||
注意:
|
||
- MIUI 通常要求**登录小米账号 + 插入 SIM 卡**才能开启该开关(防刷机策略)
|
||
- 同时看到「**USB 调试(安全设置)**」也建议打开(模拟点击需要)
|
||
- 看到 `USER_RESTRICTED` 就是厂商安全策略拦截,**不是代码或命令的问题**
|
||
|
||
---
|
||
|
||
## 6. 开发环境的后端连接(重要)
|
||
|
||
App 里的 API 地址会**按构建模式自动切换**(见 `lib/core/network/api_env.dart`):
|
||
|
||
| 构建模式 | 使用的后端地址 |
|
||
|---|---|
|
||
| debug | `http://localhost:12345/api`(本地后端) |
|
||
| release | `https://accounting.aqroid.cn/api`(**生产环境**) |
|
||
|
||
### 6.1 真机 debug 连本地后端:用 adb reverse
|
||
|
||
真机的 `localhost` 指向手机自己,不是你的电脑。用端口转发让手机的 localhost 映射到电脑:
|
||
|
||
```bash
|
||
adb reverse tcp:12345 tcp:12345 # 每次插拔设备/重启 adb 后都要重新执行
|
||
adb reverse --list # 确认映射存在
|
||
```
|
||
|
||
好处:不受 Windows 防火墙影响,也不要求手机和电脑在同一 Wi-Fi。
|
||
|
||
### 6.2 不想用 adb reverse 时:改用局域网 IP
|
||
|
||
```bash
|
||
# 查电脑局域网 IP(本项目实测为 192.168.50.182)
|
||
ipconfig | grep -A2 IPv4
|
||
|
||
flutter run -d 9dr4gyqgivl7tw5l --dart-define=API_BASE_URL=http://192.168.50.182:12345/api
|
||
```
|
||
|
||
`--dart-define` 在 debug 和 release 下都生效,优先级最高:
|
||
|
||
```bash
|
||
flutter build apk --release --dart-define=API_BASE_URL=http://192.168.50.182:12345/api
|
||
```
|
||
|
||
> 服务端 Spring Boot 需监听 `0.0.0.0`(默认即是),且 Windows 防火墙需放行 java 的入站。
|
||
|
||
---
|
||
|
||
## 7. 正式发布前要做的事
|
||
|
||
### 7.1 配置签名(自己测试可跳过,上架必须)
|
||
|
||
当前 `android/app/build.gradle.kts` 用的是 **debug 签名**(模板默认,仅供自测,应用商店会拒收):
|
||
|
||
```kotlin
|
||
buildTypes {
|
||
release {
|
||
// TODO: Add your own signing config for the release build.
|
||
signingConfig = signingConfigs.getByName("debug")
|
||
}
|
||
}
|
||
```
|
||
|
||
**① 生成密钥库(一次性,务必备份 + 记住密码)**
|
||
```bash
|
||
keytool -genkey -v -keystore D:/keys/qiangbao-release.jks \
|
||
-storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias qiangbao
|
||
```
|
||
|
||
**② 新建 `android/key.properties`(⚠️ 加入 .gitignore,不要提交)**
|
||
```properties
|
||
storePassword=你的密码
|
||
keyPassword=你的密码
|
||
keyAlias=qiangbao
|
||
storeFile=D:/keys/qiangbao-release.jks
|
||
```
|
||
|
||
**③ 修改 `android/app/build.gradle.kts`**
|
||
```kotlin
|
||
import java.util.Properties
|
||
import java.io.FileInputStream
|
||
|
||
val keystoreProperties = Properties()
|
||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||
if (keystorePropertiesFile.exists()) {
|
||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||
}
|
||
|
||
android {
|
||
signingConfigs {
|
||
create("release") {
|
||
keyAlias = keystoreProperties["keyAlias"] as String
|
||
keyPassword = keystoreProperties["keyPassword"] as String
|
||
storeFile = file(keystoreProperties["storeFile"] as String)
|
||
storePassword = keystoreProperties["storePassword"] as String
|
||
}
|
||
}
|
||
buildTypes {
|
||
release {
|
||
signingConfig = signingConfigs.getByName("release")
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**④ 打包**
|
||
```bash
|
||
flutter build apk --release # 国内应用商店用 APK
|
||
flutter build appbundle --release # Google Play 用 AAB(产物在 build/app/outputs/bundle/release/)
|
||
```
|
||
|
||
### 7.2 版本号
|
||
|
||
`pubspec.yaml`:
|
||
```yaml
|
||
version: 1.0.0+1
|
||
# ↑ ↑
|
||
# 版本名 版本号(versionCode)
|
||
```
|
||
**每次发版必须让 `+` 后面的数字递增**(否则应用商店/手机认为不是新版本,无法覆盖安装)。
|
||
|
||
产物中的体现:`versionName=1.0.0`、`versionCode=1`。
|
||
|
||
### 7.3 应用名称与图标
|
||
|
||
**应用名称**(已配置好,改名字动这几处):
|
||
|
||
| 平台 | 位置 | 当前值 |
|
||
|---|---|---|
|
||
| Android 桌面名 | `android/app/src/main/AndroidManifest.xml` 的 `android:label` | `强宝小助手` |
|
||
| iOS 桌面名 | `ios/Runner/Info.plist` 的 `CFBundleDisplayName` | `强宝小助手` |
|
||
| Web 标签页 | `web/index.html` 的 `<title>` | `强宝小助手` |
|
||
| Web 主屏名 | `web/index.html` 的 `apple-mobile-web-app-title` | `强宝小助手` |
|
||
| Web PWA | `web/manifest.json` 的 `name` / `short_name` | `强宝小助手` / `强宝助手` |
|
||
| 包名 | `android/app/build.gradle.kts` 的 `applicationId` | `com.qiangbao.qiangbao_accounting`(**上架后不可改**) |
|
||
|
||
> `short_name` 保持 4 个汉字以内,否则 Android 桌面图标下方会被截断成省略号。
|
||
> 改完名字必须**重新构建 + 安装**;若桌面名称没刷新,先卸载旧包再装(启动器缓存)。
|
||
> 应用商店里显示的名称在 Google Play / App Store 后台配置,代码里改不了。
|
||
|
||
**App 图标**(已配置好,链路如下):
|
||
|
||
素材分级存放,`assets/icon/` 未被 pubspec 声明为资源,不会打进包体积:
|
||
|
||
| 文件 | 作用 |
|
||
|---|---|
|
||
| `assets/icon/_source/raw_bear.png` | 图标素材原图(1024×1024,AI 生成,带水印) |
|
||
| `assets/icon/_source/raw_bear_dog.png` | 备选素材(熊 + 金毛),想换风格可用 |
|
||
| `assets/icon/app_icon.png` | 脚本产物:纯净版图标,无透明通道 |
|
||
| `assets/icon/app_icon_fg.png` | 脚本产物:Android 自适应图标前景层,透明底 |
|
||
|
||
完整生成链路(改图标只需重跑这两条):
|
||
|
||
```bash
|
||
python tools/make_app_icon.py # 去水印 + 抠图 + 居中缩放 → assets/icon/app_icon*.png
|
||
dart run flutter_launcher_icons # 铺开成 Android / iOS / Web 全部尺寸
|
||
```
|
||
|
||
`tools/make_app_icon.py` 的辅助参数:
|
||
|
||
```bash
|
||
python tools/make_app_icon.py --preview # 输出效果预览图 tools/_preview.png
|
||
python tools/make_app_icon.py --check # 核对产物色彩模式(iOS 必须 RGB 无 alpha)
|
||
python tools/make_app_icon.py --src <路径> # 换用其它素材图
|
||
```
|
||
|
||
配置在 `pubspec.yaml` 末尾的 `flutter_launcher_icons` 段:
|
||
|
||
```yaml
|
||
flutter_launcher_icons:
|
||
android: true
|
||
ios: true
|
||
image_path: "assets/icon/app_icon.png" # iOS / 传统 Android / Web
|
||
adaptive_icon_background: "#FFF6EC" # Android 8+ 背景层
|
||
adaptive_icon_foreground: "assets/icon/app_icon_fg.png" # 前景层
|
||
remove_alpha_ios: true # iOS 不允许透明,否则上架报 ITMS-90717
|
||
web:
|
||
generate: true
|
||
```
|
||
|
||
产物落点:
|
||
|
||
| 平台 | 产物 |
|
||
|---|---|
|
||
| Android 传统 | `res/mipmap-{m,h,xh,xxh,xxxh}dpi/ic_launcher.png`(48/72/96/144/192) |
|
||
| Android 自适应 | `res/mipmap-anydpi-v26/ic_launcher.xml` + `res/values/colors.xml` + `res/drawable-*/ic_launcher_foreground.png` |
|
||
| iOS | `ios/Runner/Assets.xcassets/AppIcon.appiconset/` 全套 22 张 |
|
||
| Web | `web/icons/Icon-{192,512}.png`、`Icon-maskable-{192,512}.png`、`web/favicon.png` |
|
||
|
||
> **不要把 `assets/images/logo.png` 当图标用**:它是登录页的贴纸海报(非正方形、带虚化底纹、含 5 个大字),缩到 48px 会糊成一团,且背景去不干净。图标必须是「单一主体 + 简洁轮廓」。
|
||
|
||
---
|
||
|
||
## 8. 本项目已做的环境配置(请勿改回)
|
||
|
||
这些是本机踩坑后落地的配置,改动前请先了解原因。
|
||
|
||
### 8.1 依赖缓存已迁到 D 盘
|
||
|
||
| 缓存 | 位置 | 配置方式 |
|
||
|---|---|---|
|
||
| pub 依赖 | `D:\dev-cache\pub` | 用户环境变量 `PUB_CACHE` |
|
||
| Gradle | `D:\dev-cache\gradle` | 用户环境变量 `GRADLE_USER_HOME` |
|
||
|
||
**为什么**:原先 pub 缓存在 C 盘、项目在 D 盘,Kotlin 增量编译无法处理跨盘符路径,构建报
|
||
`Could not close incremental caches ... different roots`。
|
||
迁移后同时释放了 C 盘约 5.4GB 空间。
|
||
|
||
> 若在别的机器上遇到同类报错,处理方式二选一:把缓存挪到与项目同盘,或在 `android/gradle.properties` 加 `kotlin.incremental=false`。
|
||
|
||
### 8.2 内存限额(防系统假死)
|
||
|
||
`android/gradle.properties`:
|
||
```properties
|
||
org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=1G -XX:+HeapDumpOnOutOfMemoryError
|
||
kotlin.daemon.jvmargs=-Xmx2G
|
||
```
|
||
**为什么**:Flutter 模板默认 `-Xmx8G`,本机总内存 16GB,叠加 Kotlin 守护进程 / IDEA / Spring Boot 会耗尽内存导致系统卡死(曾把后端进程挤死)。
|
||
|
||
### 8.3 Gradle 发行包走国内镜像
|
||
|
||
`android/gradle/wrapper/gradle-wrapper.properties`:
|
||
```properties
|
||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-9.3.1-bin.zip
|
||
```
|
||
**为什么**:官方源国内下载约 470KB/s,约 150MB 要十几分钟。用镜像 + `-bin` 包快很多。
|
||
|
||
### 8.4 Android 明文流量
|
||
|
||
`android/app/src/main/AndroidManifest.xml`:
|
||
```xml
|
||
<uses-permission android:name="android.permission.INTERNET"/>
|
||
<application android:usesCleartextTraffic="true" ...>
|
||
```
|
||
用于 debug 时访问 `http://` 的本地后端(生产是 HTTPS,其实可以不开)。
|
||
|
||
---
|
||
|
||
## 9. 常见错误速查表
|
||
|
||
| 报错 / 现象 | 原因 | 解决 |
|
||
|---|---|---|
|
||
| `INSTALL_FAILED_USER_RESTRICTED` | MIUI 未开 USB 安装 | 开发者选项 → USB 安装(见第 5 节) |
|
||
| `Could not close incremental caches` / `different roots` | pub 缓存与项目跨盘符 | 缓存迁到同盘(本项目已做,见 8.1) |
|
||
| `Timeout of 120000 waiting for exclusive access to gradle-*.zip` | 另一个 Gradle 进程占着下载锁 | 关掉其它构建窗口;必要时结束残留的 `gradlew` 进程 |
|
||
| 构建卡住不动 | 依赖从 Google Maven 慢速下载 | 已用镜像;首次构建最慢,缓存热了就快 |
|
||
| 系统假死、进程被杀 | 构建占用内存过高 | 已限制堆内存(见 8.2) |
|
||
| 应用登录报 **403** | ⚠️ 后端连不上数据库(如 JDBC URL 写错),Spring Security 用 403 兜底 | **去后端日志找根因**,别被 403 误导;URL 必须是 `jdbc:mysql://host:3306/db` |
|
||
| `Permission denied: getsockopt` | 代理/TUN 驱动拦截 Java socket | 给数据库 IP 加直连规则,或开发时关闭 TUN |
|
||
| 应用请求失败、后端却没收到 | 真机没做端口转发 | `adb reverse tcp:12345 tcp:12345` |
|
||
| `adb push` 报路径错误 | Git Bash 路径转换 | 命令前加 `MSYS_NO_PATHCONV=1` |
|
||
| 手机装了旧版覆盖不了 | 签名不一致或 versionCode 未递增 | 先卸载旧包,或递增 `pubspec.yaml` 的版本号 |
|
||
|
||
---
|
||
|
||
## 10. 命令速查
|
||
|
||
```bash
|
||
# ---------- 开发 ----------
|
||
flutter doctor # 环境检查
|
||
flutter devices # 列出可用设备
|
||
flutter pub get # 安装依赖
|
||
flutter run -d <设备号> # debug 运行(热重载)
|
||
flutter run --release -d <设备号> # release 运行
|
||
flutter analyze # 静态检查(提交前必跑)
|
||
|
||
# ---------- 构建 ----------
|
||
flutter clean # 清构建产物
|
||
flutter build apk --release # 通用 APK
|
||
flutter build apk --release --split-per-abi # 按架构拆分
|
||
flutter build appbundle --release # AAB(Google Play)
|
||
|
||
# ---------- 安装 ----------
|
||
flutter install -d <设备号> # 安装
|
||
adb install -r <apk路径> # adb 覆盖安装
|
||
adb uninstall com.qiangbao.qiangbao_accounting
|
||
|
||
# ---------- 调试辅助 ----------
|
||
adb devices # 设备列表
|
||
adb reverse tcp:12345 tcp:12345 # 端口转发(真机连本地后端)
|
||
adb reverse --list # 查看转发规则
|
||
adb logcat | grep flutter # 看应用日志
|
||
adb shell pm list packages | grep qiangbao # 确认是否已安装
|
||
adb shell screencap -p /sdcard/s.png && adb pull /sdcard/s.png # 截图
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 首次构建 vs 后续构建
|
||
|
||
| | 首次 | 后续(缓存热) |
|
||
|---|---|---|
|
||
| 耗时 | 5~20 分钟(下载 Gradle + AGP 依赖) | 30 秒~2 分钟 |
|
||
| 主要瓶颈 | 依赖下载 | Dart 编译 |
|
||
|
||
**故:第一次构建慢是正常的,不要中断。** 之后改代码用 `r` 热重载,几乎无等待。
|