16 KiB
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
然后在手机上:
- 打开「文件管理」
- 进入 手机存储 → Download
- 点击
qiangbao.apk - 若提示「禁止安装未知来源应用」→ 点提示里的设置,允许「文件管理」安装应用
- 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 热重载,几乎无等待。