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

16 KiB
Raw Permalink Blame History

Flutter 打包 APK 与真机安装指南

项目:强宝小助手(qiangbao_accounting) 环境:Windows 11 + Flutter 3.47.4 / Dart 3.13.3 测试机:小米 M2007J22C(Android 12 / MIUI 14,设备号 9dr4gyqgivl7tw5l)


0. 快速开始(最常用的三条命令)

# ① 开发调试:热重载,改代码存盘即生效
flutter run -d 9dr4gyqgivl7tw5l

# ② 打包正式包
flutter build apk --release

# ③ 装到手机
flutter install -d 9dr4gyqgivl7tw5l

1. 前置检查

# 环境是否齐全(应全绿)
flutter doctor

# 手机是否连上(应看到设备号,状态为 device 而不是 unauthorized)
adb devices

# 依赖是否安装
flutter pub get

如果 adb devices 显示 unauthorized,在手机上确认「允许 USB 调试」弹窗。 如果是 offline,拔插数据线或执行 adb kill-server && adb start-server。


2. 场景一:开发调试(日常用这个)

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 模式验证性能(无热重载):

flutter run --release -d 9dr4gyqgivl7tw5l

3. 场景二:打包 APK

3.1 通用包(最省事,能装到任何 Android 手机)

flutter build apk --release

产物:build/app/outputs/flutter-apk/app-release.apk

3.2 按 CPU 架构拆分(体积更小,推荐)

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 只出指定架构

flutter build apk --release --target-platform android-arm64

3.4 其他选项

flutter build apk --release -v            # 显示详细日志(排查构建失败用)
flutter clean && flutter pub get          # 构建异常时清缓存重来

4. 场景三:安装到手机

方式 A:Flutter 命令(推荐)

flutter install -d 9dr4gyqgivl7tw5l

会自动找最近构建的 APK 并安装。想指定安装 debug 包:

flutter install --debug -d 9dr4gyqgivl7tw5l

方式 B:adb 直接安装

adb install -r build/app/outputs/flutter-apk/app-release.apk
  • -r = 覆盖安装(保留数据)
  • -d = 允许降级安装
  • 指定手机:adb -s 9dr4gyqgivl7tw5l install -r xxx.apk

方式 C:文件管理器手动安装(绕开 USB 限制)

# 先把 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/...。

卸载

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 映射到电脑:

adb reverse tcp:12345 tcp:12345      # 每次插拔设备/重启 adb 后都要重新执行
adb reverse --list                   # 确认映射存在

好处:不受 Windows 防火墙影响,也不要求手机和电脑在同一 Wi-Fi。

6.2 不想用 adb reverse 时:改用局域网 IP

# 查电脑局域网 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 下都生效,优先级最高:

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 签名(模板默认,仅供自测,应用商店会拒收):

buildTypes {
    release {
        // TODO: Add your own signing config for the release build.
        signingConfig = signingConfigs.getByName("debug")
    }
}

① 生成密钥库(一次性,务必备份 + 记住密码)

keytool -genkey -v -keystore D:/keys/qiangbao-release.jks \
  -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias qiangbao

② 新建 android/key.properties(⚠️ 加入 .gitignore,不要提交)

storePassword=你的密码
keyPassword=你的密码
keyAlias=qiangbao
storeFile=D:/keys/qiangbao-release.jks

③ 修改 android/app/build.gradle.kts

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")
        }
    }
}

④ 打包

flutter build apk --release         # 国内应用商店用 APK
flutter build appbundle --release   # Google Play 用 AAB(产物在 build/app/outputs/bundle/release/)

7.2 版本号

pubspec.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 自适应图标前景层,透明底

完整生成链路(改图标只需重跑这两条):

python tools/make_app_icon.py     # 去水印 + 抠图 + 居中缩放 → assets/icon/app_icon*.png
dart run flutter_launcher_icons   # 铺开成 Android / iOS / Web 全部尺寸

tools/make_app_icon.py 的辅助参数:

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 段:

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:

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:

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:

<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. 命令速查

# ---------- 开发 ----------
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 热重载,几乎无等待。