Files
Aq-Accounting-Flutter/docs/Flutter打包与真机安装指南.md
T
2026-09-23 15:33:59 +08:00

458 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 热重载,几乎无等待。