# 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` 的 `` | `强宝爱记账` | | 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` 热重载,几乎无等待。