第6章 6.2 移动 App 打包(iOS/Android)

🎯 开场 3 分钟:为什么要学这个?

上一章我们折腾完了 H5 端开发,你已经能在浏览器里跑起完整的 uniapp 项目了。但等等——浏览器里跑和真正的手机 App 那是两码事!

你有没有遇到过这种情况:

  • 老板说:「把这个 H5 项目打个 iOS 安装包,我要发测试」
  • 测试说:「安卓包呢?我用的是小米」
  • 你发现:在微信里能跑,但 APK 安装后闪退

这就是今天要解决的问题——把你的代码变成真正的手机 App,让用户不用开浏览器,直接点图标就能用

学完这一章,你就能:
1. 理解「云打包」和「离线打包」的区别,知道什么时候用哪个
2. 搞定 iOS 证书和 Android 签名,不再被这些名词吓退
3. 独立打出能安装的 APK 和 IPA 文件


🧱 基础 25 分钟:核心概念

什么是打包?先别急着操作

打包是什么?

说白了,打包就是「把网页代码翻译成手机能听懂的语言,然后装进一个安装包」\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\n\n。

类比一下:就好比你写好的菜谱(代码),要变成外卖送到客人手里,需要一个餐盒来装——打包就是找餐盒的过程

uniapp 有两种打包方式

打包方式 适合场景 难度
云打包 不想装复杂环境,临时打包,练习阶段 ⭐ 简单
离线打包 正式发布,需要定制原生功能 ⭐⭐⭐ 复杂

云打包:省事的餐盒工厂

云打包就像点外卖——你把菜谱(代码)发给美团(uniapp 官方服务器),他们帮你做好装盒,送到你手里。

为什么要用云打包?
- 不用在自己电脑上安装 Android Studio 和 Xcode
- 几分钟就能出包
- 最适合学习和测试阶段

怎么用云打包?

  1. 在 HBuilderX 里右键你的项目
  2. 选择「发行」→「原生App-云打包」
  3. 勾选你要的平台(iOS / Android)
  4. 点「打包」,等邮件通知

离线打包:自己在家做饭

离线打包就是自己搭建厨房——你需要安装 Android Studio(做大菜)和 Xcode(做甜点),自己配置一切。

为什么要用离线打包?
- 云打包不支持某些原生插件
- 需要深度定制原生功能(比如微信 SDK 深度集成)
- 正式上线需要更好的性能优化

离线打包的核心文件结构

你的项目/
├── native/           # 原生代码目录
│   ├── android/      # Android 相关
│   │   ├── app/      # 主应用
│   │   ├── gradle/   # 构建配置
│   │   └── keystore/ # 签名文件 🔐
│   └── ios/          # iOS 相关
│       └── CodeResources
└── dist/             # 编译产出

证书:你的身份证

什么是证书?

类比:就像开车需要驾照一样,你的 App 要安装到用户手机上,也需要一个「数字身份证」——这就是证书。

平台 证书类型 文件格式 干嘛用的
iOS 开发证书 .p12 开发测试用
iOS 发布证书 .p12 提交 App Store
iOS 描述文件 .mobileprovision 给证书授权
Android 签名文件 .keystore / .jks 给 APK 签名,证明是你发布的

Android 签名(最常被问):

# 生成一个 Android 签名文件命令(简单了解)
keytool -genkey -v -keystore my-release-key.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias my-key-alias

解释一下这行在干嘛:
- keytool 是 Java 自带的签名工具
- -keystore my-release-key.jks 指定输出文件
- -validity 10000 表示证书有效期 10000 天(够用 27 年)
- -alias my-key-alias 是这个钥匙的绰号

原生插件:你和厨师之间的翻译员

uniapp 的代码是 JavaScript,但手机原生功能(摄像头、蓝牙、地图)只认「本地话」。原生插件就是那个翻译员。

什么时候需要原生插件?
- 需要调微信支付
- 需要蓝牙连接设备
- 需要地图定位功能
- 需要友盟/极光推送

用插件市场装插件(最简单的方式):

  1. 上 DCloud 插件市场搜你要的插件
  2. 复制插件名字
  3. 在 HBuilderX 里:「工具」→「插件安装」→「从市场导入」
  4. 在代码里 import 一下就能用
// 举个例子:使用地图插件
import myMap from '@/uni_modules/uni-map'

// 调用地图定位
myMap.getLocation({
success: (res) => {
console.log('当前位置:', res.latitude, res.longitude)
}
})

🔥 实战 35 分钟:3 个递进小项目

项目 1:5 分钟搞定首个 APK(云打包)

目标:把一个空白项目打出 Android 安装包

步骤

  1. 新建项目:「文件」→「新建」→「项目」→「uni-app」→「默认模板」
  2. 项目命名 my-first-app
  3. 右键项目根目录 →「发行」→「原生App-云打包」
  4. 勾选「Android」
  5. 证书选「使用公共测试证书」(学习用这个就行)
  6. 点「打包」,等待 2-3 分钟

预期输出

打包成功!
下载链接:https://...
文件名:my-first-app_20240115_Android.apk

一句话解释:云打包就是把你的代码传到服务器,服务器帮你编译成 APK,你下载回来就能装手机上测试了。


项目 2:申请你的 iOS 证书(15 分钟)

目标:为真机测试准备 iOS 证书和环境

需求:在 iOS 真机上跑你的 App(模拟器不需要证书)

完整步骤

第一步:申请 Apple 开发者账号(如果你没有)

  1. 访问 https://developer.apple.com
  2. 注册账号(个人开发者年费 688 元)
  3. 登录后进入「Certificates, Identifiers & Profiles」

第二步:创建 App ID

  1. 「Identifiers」→「+」→ 选「App IDs」→「Continue」
  2. Description 填:com.youname.myfirstapp
  3. Bundle ID 要和 HBuilderX 里配置的一致!

第三步:生成证书

# 1. 在 Mac 上打开钥匙串访问
# 2. 钥匙串访问 → 证书助理 → 从证书颁发机构请求证书
# 3. 填邮箱,选「存储到磁盘」
# 4. 得到 CertificateSigningRequest.certSigningRequest 文件

# 5. 回到 Apple Developer 网站
# 6. Certificates → "+" → iOS App Development
# 7. 上传刚才的 .certSigningRequest 文件
# 8. 下载证书,双击导入钥匙串

第四步:创建描述文件

  1. Profiles → "+" → iOS App Development
  2. 选你的 App ID
  3. 选你的证书
  4. 选你的测试设备(需要先在 Devices 里添加)
  5. 下载 .mobileprovision 文件

第五步:在 HBuilderX 里配置

  1. 项目右键 →「发行」→「原生App-云打包」
  2. 勾选 iOS
  3. 证书选「使用私有证书」,上传你的 .p12 和 .mobileprovision

预期输出

iOS 打包成功!
ipa 文件路径:__UNI__XXXXXX.ipa

一句话解释:证书就是你的身份证,描述文件是「授权书」,两者配合才能让你的 App 安装到特定 iPhone 上。


项目 3:离线打包集成推送功能(15 分钟)

目标:把友盟推送插件集成到 Android 项目里,自己编译打包

需求背景
云打包不支持某些高级插件,必须用离线打包才能集成友盟/极光推送。

完整代码和配置

第一步:下载离线 SDK

  1. 去 DCloud 插件市场下载 uni-push 的离线版本
  2. 解压后会得到 AndroidiOS 两个目录

第二步:集成到 Android 项目

// 在 app/build.gradle 里添加依赖
dependencies {
implementation 'com.aliyun.ams:alicloud-android-push:3.x.x'
implementation 'com.aliyun.ams:alicloud-android-push-serverless:1.x.x'
}

第三步:配置 AndroidManifest.xml

<!-- 在 application 标签内添加 -->
<application
android:name=".UniAppApplication"
...>

<!-- 友盟推送配置 -->
<meta-data
    android:name="com.umeng.message.sdk.appkey"
    android:value="你的 AppKey" />

<receiver
    android:name="com.umeng.message.MyPushReceive">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
</application>

第四步:在 uniapp 代码里调用推送

// push.js
export default {
// 初始化推送
initPush() {
// #ifdef APP-PLUS
const push = uni.requireNativePlugin('uni-push')

push.init({
  appkey: '你的 AppKey',
  debug: true  // 打开调试日志
})

// 监听透传消息
push.on('message', (message) => {
  console.log('收到推送:', message)
  // 这里可以写自己的业务逻辑
  uni.showToast({
    title: message.content,
    icon: 'none'
  })
})

// 监听点击通知
push.on('notification', (notification) => {
  console.log('用户点击了通知:', notification)
})
// #endif
},

// 获取客户端推送 ID
getClientId() {
return new Promise((resolve, reject) => {
  // #ifdef APP-PLUS
  plus.push.getClientInfo({
    success: (info) => {
      resolve(info.clientid)
    },
    fail: (err) => {
      reject(err)
    }
  })
  // #endif
})
}
}

第五步:在 App.vue 里启用

// App.vue
<script>
import push from '@/utils/push.js'

export default {
onLaunch() {
// 初始化推送服务
push.initPush()
console.log('App 启动')
}
}
</script>

预期输出(控制台):

[Push] 初始化成功
[Push] 收到推送:你的订单已发货
[Push] 客户端ID: AXV3xxxxxxxxxxxxxx

一句话解释:离线打包就是自己搭厨房,虽然麻烦但能装各种高级插件,实现云打包做不到的功能。


💪 进阶 20 分钟:常见坑 + 性能小贴士

❌ 坑 1:Android 包明明打好了,安装时提示「解析包错误」

原因:签名不一致。用调试证书打的包,不能覆盖用正式证书安装的旧包。

✅ 解决

# 方法1:先卸载旧 App,再安装新的
adb uninstall com.yourcompany.yourapp

# 方法2:确认打包时用的是同一个 keystore
# 如果丢了 keystore,你只能换包名重新打

❌ 坑 2:iOS 包打好了,真机测试时提示「无法验证开发者」

原因:证书过期了,或者描述文件和设备不匹配。

✅ 解决

  1. 检查证书是否过期(钥匙串访问 → 左侧选「证书」→ 看有效期)
  2. 确认描述文件包含你的设备 UDID
  3. 手机设置 → 通用 → VPN 与设备管理 → 信任你的开发者证书

❌ 坑 3:云打包一直排队,等了 10 分钟还没动静

原因:高峰期 uniapp 官方服务器排队人多。

✅ 解决

// 换个时间再打,或者用本地离线打包
// 离线打包配置:
// 项目右键 → 发行 → 原生App-本地打包 → 了解如何打包

❌ 坑 4:App 隐私合规问题,提审被拒

原因:2023 年起 iOS 和 Android 都强制要求声明隐私权限。

✅ 解决:在 manifest.json 里准确声明需要的权限:

{
"app-plus": {
"privacy": {
  "prompt": "always"  // 始终弹窗询问
},
"permissions": {
  "CAMERA": "使用摄像头",
  "LOCATION": "获取位置信息用于附近功能",
  "STORAGE": "保存用户数据"
}
}
}

❌ 坑 5:Android 12 以上 targetSdkVersion 太高导致闪退

原因:Android 12 对组件权限管理更严格。

✅ 解决

// app/build.gradle 里的配置
android {
defaultConfig {
    // 不要用太高的 targetSdkVersion
    targetSdkVersion 31  // Android 12
    // 或者保持 30,等官方适配
}
}

🚀 性能小优化:图片懒加载

// 列表页用懒加载,减少内存占用
<template>
<scroll-view>
<image
  v-for="item in images"
  :key="item.id"
  :src="item.url"
  loading="lazy"  <!-- 关键:懒加载 -->
/>
</scroll-view>
</template>

🔍 调试技巧:看日志定位崩溃

// 在关键位置加日志
console.log('[MyApp] 当前页面:', getCurrentPages().length)
console.log('[MyApp] 用户信息:', userInfo)

// 崩溃时抓日志
// Android: adb logcat | grep "myapp"
// iOS: Xcode → Devices → View Device Logs

✏️ 练习题 + 作业题

练习题(10 分钟)

练习 1(2 分钟):换个包名
- 输入:用云打包打出任意一个 APK
- 预期输出:控制台显示打包成功,生成 .apk 文件
- 提示:在 HBuilderX 的 manifest.json 里改一下 appid 试试

练习 2(2 分钟):添加权限声明
- 输入:在 manifest.json 里添加「获取位置」权限
- 预期输出:打包后的 App 首次启动会弹位置权限申请
- 提示:找 app-pluspermission 部分添加

练习 3(3 分钟):给 Android 包签名
- 输入:用 keytool 生成一个测试用的 keystore 文件
- 预期输出:得到 .jks 格式的签名文件
- 提示:命令里 -validity 参数设大一点(比如 10000)

练习 4(3 分钟):分析证书状态
- 输入:你的 Mac 钥匙串里导入了某个 iOS 证书
- 预期输出:说出这个证书是「开发」还是「发布」类型,以及过期时间
- 提示:双击证书,看「种类」那一栏

练习 5(5 分钟,看截图分析问题)
- 输入:一张报错截图,写着「The application does not have a valid signature」
- 预期输出:说出原因和解决办法(用 2-3 句话)
- 提示:想想安装和签名是什么关系


作业题(30 分钟 - 2 小时)

作业:做一个「版本检测 + 强制更新」功能

需求描述
做一个检测 App 版本的脚本,当服务器上有新版本时,提示用户更新。

功能点
1. checkVersion() 函数检测当前版本号
2. 从模拟的服务器接口获取最新版本
3. 比较版本号,如果服务器版本 > 当前版本,弹窗提示更新
4. 点击更新链接跳转浏览器下载(用 H5 链接模拟)

加分项
1. 区分「普通更新」和「强制更新」(强制更新用户必须更)
2. 显示「发现新版本 v1.2.3(当前 v1.2.0)」的对比信息

验收标准
- 代码能跑,不报错
- 逻辑清晰:版本低就弹更新提示,不低就不弹
- 有注释说明关键逻辑

提交方式:把代码贴到评论区,说明在真机测试还是模拟器


📚 总结 + 资源

本文学到的 3 个核心点
1. 云打包适合快速测试,离线打包适合深度定制
2. iOS 证书 + 描述文件是安装到真机的两把钥匙,缺一不可
3. Android 签名要妥善保管,丢了签名文件就只能换包名重新来

延伸学习资源

  1. DCloud 官方文档 - App 打包配置
  2. iOS 证书官方申请指南
  3. 《uniapp 跨平台开发实战》- 第 8 章 App 打包详解(各大电商平台有售)

互动钩子

打包过程中最让你头疼的是哪一步?证书申请、材料准备、还是签名配置?评论区聊聊你是怎么解决的,老粉优先回复!

下一章我们要讲各种小程序平台差异——微信、支付宝、抖音小程序各有各的脾气,打包方式也不一样。学会这一章,你就能把同一个 App 发到不同平台了,敬请期待!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。