我最早接触 uni-app 是在几年前,当时手上一个项目要同时覆盖微信小程序、H5 网页和安卓 App 三个端。前后对比了好几个方案,最终选它,原因说白了就一句话:把多端开发语言统一到 Vue 这一套语法上,后端接口、公共组件、工具函数全部能复用。你写一个页面,可以编译出小程序版本、浏览器版本、安卓安装包版本。这个收益对资源有限的小团队特别明显,尤其是产品需要快速验证的时候。
这篇文章是真正意义上的保姆级教程,默认读者不是第一次写前端,但没接触过 uni-app 的工程结构和打包链路。我会从开发工具安装开始,把「项目创建」和「打包出 apk」这条容易卡住新人的链路完整走一遍。图形化创建、命令行脚手架、pages.json 和 manifest.json 配置、Android 云打包、本地打包,再到我实际踩过的几个坑,都会展开讲。跟着做,半天内拿到一个能装到手机里的安装包,问题不大。
1. 项目概述与核心设计思路
有人会觉得,创建 uni-app 项目不就是点一下新建吗?打包不就是按一下发行吗?理论上确实如此,但实际操作里,「新建项目」之后会遇到运行器连不上、小程序工具调用失败、页面白屏;「打包」之后会遇到签名不一致、图标缺失、权限没配置、安卓应用市场审核被驳回。这些问题,往往比写业务代码更浪费时间。
1.1 「项目创建」到底创建了什么
新建 uni-app 项目,本质上不是建一个文件夹,而是搭好一套工程骨架。这套骨架里包含页面目录、静态资源目录、全局配置、应用入口、样式变量这些内容。它跟普通 Vue 项目很像,但多了两个非常核心的配置文件:pages.json 和 manifest.json。
pages.json 负责整个应用的页面路由、导航栏、tabBar,相当于整栋楼的「户型图」。manifest.json 负责应用在各平台的标识、权限、图标、SDK 配置,相当于这栋楼的「产权证」。很多人创建完项目第一件事就是写页面,完全不去管这两个文件,等到打包上架的时候才发现路由错了、权限没开、图标全空白,返工成本非常高。
所以我的建议是:新建项目后,先花十分钟把这两个配置文件从头到尾看一遍,明白每个字段控制的是什么。这十分钟省下来的,可能是后面两小时的排查时间。
1.2 创建与打包的核心设计主线
如果给 uni-app 从零到上架画一条主线,它是这样的:创建工程 → 理解配置 → 开发页面 → 条件编译处理多端差异 → 配置图标权限 → 生成构建资源 → 用证书签名 → 打包出安装包 → 真机验证 → 上架渠道。
这里值得一说的是「打包」为什么被单独拿出来当难点。打包动作本身是平台在做,但对前端开发者来说,难点集中在三个地方:证书和签名规则、各平台的配置差异、打包前后依赖版本的一致性。这些知识不在 Vue 文档里,也不在 CSS 教程里,只能在项目实践里积累。
我见过不少开发者在微信群问「为什么我打出来的包装到手机上提示签名不一致」「为什么昨天还能打的包今天突然失败」,基本都是对打包背后的资源生成逻辑不了解。这篇文章的后半部分,重点就是把这些逻辑讲明白。
2. 环境准备与开发工具安装
在创建项目之前,先把开发环境整理好。这一节看起来琐碎,但几乎所有新手卡住的点,都出在工具没配对、路径没放对、联动没开启这三个问题上。
2.1 工具清单与版本搭配
先列一张表,把需要用到的工具说清楚:
| 工具 | 主要用途 | 版本建议 |
|---|---|---|
| HBuilderX | 主开发工具,负责创建项目、编写代码、云打包入口 | 当前最新正式版 |
| 微信开发者工具 | 调试小程序端,查看编译后的页面效果和报错信息 | 最新稳定版 |
| Android Studio | 安卓本地打包、SDK 管理、模拟器运行 | 最新稳定版 |
| Node.js | 命令行创建项目、执行 npm 脚本 | 长期支持版 LTS |
| Java 环境 | 生成签名证书、本地打包构建时需要 | JDK 17 更稳妥 |
这里必须强调一个原则:不要一味追新。开发工具的配套体系对版本极度敏感,HBuilderX 升级之后,本地打包 SDK 的版本也要跟着匹配;Node.js 版本过新或过旧,都可能让工程脚本报错。我个人的习惯是,团队里统一一套经过验证的版本组合,出了环境问题大家能互相参照。
2.2 安装时的三处关键配置
第一处,安装路径别有中文,别有空格。工具类软件我一般统一放到 D 盘某个无中文目录下,比如D:\dev_tools。这个习惯在本地打包阶段特别有用,因为很多 Gradle 脚本和原生构建工具遇到中文路径会直接崩溃,报错信息还特别难懂。
第二处,HBuilderX 和微信开发者工具的联动。HBuilderX 默认不能直接唤起微信开发者工具,需要在微信开发者工具里打开设置 → 安全设置 → 开启服务端口。然后在 HBuilderX 的菜单栏选择运行 → 运行到小程序模拟器,第一次会提示填写微信开发者工具的安装路径,选择安装目录下的启动程序即可。如果端口没开,运行时会提示「无法连接到开发者工具」,这不是项目的问题,是联动配置没做。
第三处,Android 真机调试时,手机要开启开发者选项和 USB 调试。部分手机连上电脑后默认只充电,需要在通知栏里把 USB 模式切换为文件传输或调试模式。数据线也要注意,有些线只能充电不能传数据,这类问题经常被当成代码 bug 排查半天。
3. 创建uniapp项目:图形界面与命令行两种方式
环境准备好之后,开始正式创建项目。这一节我把两种方式都讲一遍,你可以根据自己的情况选。
3.1 用 HBuilderX 可视化创建项目,几分钟跑通第一个页面
打开 HBuilderX,点击文件 → 新建 → 项目,在弹出的窗口左侧选择 uni-app 分类。项目名称建议用全英文,比如my-app-demo,不要带空格,存放位置选一个自己记得住的目录。
模板选择这里卡住过不少人。默认模板自带首页、消息、我的这几个 tabBar 示例页面,还带 Vue 语法示例,适合第一次跑通流程。空白模板只给最基础的页面结构,适合已经清楚自己要做什么的人。新手阶段我建议直接用默认模板,先把项目跑起来,再慢慢清掉示例代码。如果你拿默认模板直接开始写正式业务,后面对着一堆示例代码清理会有点痛苦。
创建完成后,点击 HBuilderX 顶部菜单的运行 → 运行到浏览器 → Chrome。第一次运行会自动编译,编译完成后会弹出浏览器,页面渲染正常就说明项目创建成功。浏览器里修改页面的文字,保存后会自动刷新,这个开发体验跟普通前端工程一样。运行到微信开发者工具的操作类似,前提是前面说的联动配置已经完成,编译完成后它会自动唤起微信开发者工具并打开页面。
跑通这两个目标端之后,「项目创建」这件事就不只是建了空壳,你会亲眼看到同一套代码在浏览器和小程序里都能跑起来。这个正反馈对新手特别重要。
3.2 用命令行脚手架创建 Vue3 + Vite 工程
可视化创建适合一个人快速起步,但如果你所在团队已经有完整的 npm 工程体系,希望把 uni-app 集成到统一的版本管理和持续集成流程里,命令行脚手架更合适。以 Vue3 + Vite 模板为例,操作如下:
# 1. 用 degit 拉取官方 Vue3 模板到本地 npx degit dcloudio/uni-preset-vue#vite my-uniapp-demo # 2. 进入项目并安装依赖 cd my-uniapp-demo npm install # 3. 启动微信小程序编译模式 npm run dev:mp-weixin这里解释一下,npx degit的作用是把远端模板仓库复制到本地,不保留 git 历史,相当于下载了一个纯净模板。执行完第三步后,项目会在dist/dev/mp-weixin目录下生成编译产物,用微信开发者工具直接导入这个目录,就能看到页面。
常用脚本还有npm run dev:h5对应浏览器调试,npm run build:app对应生成 App 打包资源,npm run build:h5对应构建 H5 产物。对刚接触 CLI 方式的读者,我建议先跑通dev:mp-weixin,因为微信开发者工具的报错信息相对直观,方便你逐步理解编译过程。
需要提醒的是,命令行方式创建的工程,如果想跑安卓真机调试,最简单的方式是直接用 HBuilderX 的导入功能打开项目根目录,然后通过 HBuilderX 的运行菜单连接手机。CLI 工程和 HBuilderX 工程共用同一套配置规范,这一点不用担心。
3.3 项目目录结构逐层拆解
不管用哪种方式创建,项目目录结构基本一致。以 HBuilderX 创建的默认工程为例:
├── pages/ │ ├── index/index.vue │ └── ... ├── static/ ├── uni_modules/ ├── components/ ├── App.vue ├── main.js ├── manifest.json ├── pages.json ├── uni.scss └── vite.config.jspages目录存放页面组件,每个页面一个目录,文件名和路由一一对应。static目录放静态资源,比如本地图片、字体文件,这些文件会原样打包进应用。uni_modules是插件市场下载的模块统一存放位置,类似 npm 包的作用,但它是 uni-app 生态特有的安装目录。components放自定义可复用组件,如果组件只在某几个页面用,也可以就近放在页面目录下。
App.vue不是页面,它是整个应用的生命周期入口,onLaunch里可以做全局初始化,比如拉取用户信息、检查更新。main.js是应用入口文件,负责创建 Vue 实例。uni.scss存放全局样式变量,可以定义主题色、通用间距,方便所有页面引用。pages.json和manifest.json是全文最核心的两个配置文件,下面单独说。
3.4 pages.json 与 manifest.json 的第一次配置
pages.json 其实是一个 JSON 路由表。第一次创建项目后,我建议主动把里面的内容读一遍,它长这样:
{ "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "首页" } } ], "globalStyle": { "navigationBarTextStyle": "white", "navigationBarTitleText": "我的应用", "navigationBarBackgroundColor": "#007AFF", "backgroundColor": "#F5F5F5" }, "tabBar": { "color": "#7A7E83", "selectedColor": "#007AFF", "list": [ { "pagePath": "pages/index/index", "text": "首页" } ] } }pages数组的第一项是应用启动页,这个顺序很重要,改错了会导致启动时跳转到别的页面。globalStyle定义导航栏的全局样式,各页面可以覆盖。tabBar是最多五个的底部导航配置,注意文本颜色和选中颜色要符合设计稿,这里很容易被忽略。常见错误是页面路径写错一个字母,编译不报错,运行起来白屏,排查的时候要从控制台看路由报错。
manifest.json 在 HBuilderX 里是以可视化界面展示的,基本信息那一栏有应用名称、AppID。测试阶段用系统自带的测试 AppID 就够,但正式发布前一定要到 uni-app 官方开发者平台申请正式 AppID,否则打包上架会受阻。这里有个注意点:最好不要手动去改 manifest.json 的源码,尤其是一些标识字段,HBuilderX 的可视化配置会在编译时自动注入,手改容易改坏。图标、权限、隐私协议的配置都会在下一节展开讲。
4. 打包前的工程配置:条件编译与资源准备
很多人把代码写完就急着打包,结果在打包环节反复折腾。其实打包之前有几项配置如果不处理,后面几乎是必定出问题。这一节讲条件编译和资源准备,都直接影响最终安装包的行为。
4.1 条件编译:一套代码处理不同平台的差异
条件编译是 uni-app 最核心的能力之一,它的意思是:在编译阶段,根据当前目标平台,只保留对应的代码块。它的语法长得像注释,但编译器会识别并处理。
举一个最常见的 JS 条件编译例子:
// #ifdef APP-PLUS console.log('这段代码只在 App 端运行'); // #endif // #ifndef MP-WEIXIN console.log('这段代码在除了微信小程序之外的其他端运行'); // #endif前缀含义要记牢:#ifdef表示「如果定义了该平台则编译」,#ifndef表示「如果没定义该平台则编译」。常见平台标识有APP-PLUS、MP-WEIXIN、H5,分别对应 App 端、微信小程序端、网页端。
实际项目里,条件编译最常见的用途是处理导航栏差异。比如小程序端用系统原生导航栏,App 端想用自定义导航栏加渐变效果,就可以在页面模板里放两个导航容器,用条件编译标记区分,编译成小程序时只保留小程序那份代码,编译成 App 时只保留 App 那份代码。业务逻辑里也可以做不同平台的统计上报埋点。
这里要特别强调一点:条件编译是编译期行为,被排除的代码不会进入最终包体,所以它比运行时判断彻底得多。代价是语法要求苛刻,注释里的#ifdef少一个字符,整个块就可能被当成普通代码打进包里,还会产生莫名其妙的报错。我见过一个同事把#ifdef写成了#ifdefs,字节码处理的时候编译通过,但代码执行不到,排查了很久。
CSS 同样支持条件编译:
/* #ifdef APP-PLUS */ .custom-class { height: calc(100vh - var(--status-bar-height)); } /* #endif */这种方式在做沉浸式状态栏、安全区域适配时非常常用。
4.2 图标、启动图、应用名、权限与隐私协议配置
打包前的资源准备,重点看四个东西:应用名称、图标、启动图、权限声明。
应用名称在 manifest.json 可视化界面的基本信息里配置。不同平台的入口名称来自这个字段,微信小程序显示的名称在微信公众平台设置,App 桌面图标下的名称来自这里。打包前先确认应用名称不是默认的「uni-app」,否则应用装到手机上显得特别不专业。
图标建议准备一张 1024×1024 的 PNG 图片。HBuilderX 的图标配置界面支持一键生成各个尺寸和平台的图标,它会自动裁剪出安卓各分辨率的图标。启动图和图标类似,工具的自动化生成能力可以减少大量手工切图工作。这里有个细节:如果图片包含透明通道,某些安卓机型上桌面图标会出现黑底,所以底图最好用不透明的纯色背景。
权限声明是打包后能否正常使用的关键。manifest.json 里的权限配置分两类:一类是基础权限,比如网络、读写存储;另一类是专项权限,比如相机、定位、录音。如果你用了 uni-app 的拍照组件但没配置相机权限,打包后调起相机会直接失败。定位权限更典型,配置漏了,真机上拿不到位置信息,控制台还不一定报错。
隐私协议是最近几年应用市场审核特别关注的点。manifest.json 里需要配置隐私弹窗的标题、内容和政策链接。用户在手机上第一次打开应用时,会看到隐私协议弹窗,点击同意才能继续用。如果应用没做这个弹窗,安卓市场提交审核时大概率被驳回。我的建议是文案提前让法务或业务方输出,不要临时拼凑。
5. 安卓APK打包完整实操:云打包路径
配置做完,进入真正的打包环节。这一节先讲云打包,因为它对前端开发者最友好,不需要安装整套 Android SDK。
5.1 云打包的核心逻辑与适用场景
云打包,简单说就是把你的代码上传到云端构建服务器,服务器完成 Android 依赖集成、资源合并、签名等操作,最后返回一个安装包给你。它的优势是门槛低,前端开发者不用搭建原生构建环境;劣势是受网络和排队影响,遇到高峰期可能要等。而且如果你要深度集成原生插件,或者对包体有严格定制需求,云打包不一定够用。
云打包的典型使用场景有三个:第一个是给测试同事出一版测试包;第二个是打包完丢给设计师看还原度;第三个是正式发布到安卓应用市场。前两个场景用公共测试证书就行,第三个场景必须用正式签名。
5.2 云打包完整操作步骤
用 HBuilderX 走云打包的流程并不复杂:
第一步,确认 manifest.json 里的应用名称、应用图标、权限、包名都配置好。第二步,点击菜单栏发行 → 原生App-云打包。第三步,在弹窗中选择平台为 Android。第四步,选择证书。如果你有正式证书,上传 keystore 文件并填别名和密码;如果只是测试,选择公共测试证书。第五步,选择打包模式,测试阶段用 debug 模式,正式发行用 release 模式。第六步,点击打包,等待进度条走完,下载生成的 apk 文件。
拿到 apk 后,先用数据线传到安卓手机上安装,或者用模拟器安装测试。建议至少在一台 Android 13 以上机器和一台低版本机器上都装一下,看看权限弹窗、页面适配有没有问题。如果手机上之前装过用公共测试证书签名的包,再装正式证书签名的包会失败,先卸载旧的再装就行。这个现象本质上是签名不同,下面接着讲。
5.3 Android 签名证书:从生成到配置
Android 的安装包必须经过数字签名,系统靠签名识别应用作者身份。签名可以理解成你的「数字私章」:同一把私章盖出来的包,才能被认为是同一个应用,才能实现覆盖升级。换签名等于换了个应用,老用户无法直接覆盖安装更新,这个教训很多团队都吃过。
生成正式签名证书可以用 JDK 自带的 keytool 命令:
keytool -genkey -alias myalias -keyalg RSA -keysize 2048 -validity 36500 -keystore release.keystore这里每个参数都有实际意义。-alias是证书别名,后面配置签名时会用到;-keyalg RSA指定加密算法;-keysize 2048是密钥长度,太短有安全风险;-validity 36500是有效期,单位是天,36500 天差不多一百年,够正常业务使用了。执行命令后,命令行会依次询问姓名、组织、城市等信息,按真实情况填写即可。
证书生成后有两件事必须做:第一,牢记密码,忘了密码这串 keystore 就废了;第二,把 keystore 文件和密码一起存到公司的密码管理工具里,如果是个人项目,也要放到可靠的私有存储空间。
提示:凡是发布过应用市场的包,后续版本必须用同一个证书签名。发行新版本时如果提示签名不一致,说明你的证书或签名配置出了问题,这比代码 bug 更麻烦。
6. 安卓本地打包与 iOS 打包补充
云打包解决了大部分常规需求,但总有场景需要走本地打包。同时,iOS 打包的证书体系跟 Android 完全不同,这里一并做个补充说明。
6.1 本地打包:用 Android Studio 出正式 APK
什么时候需要本地打包?我觉得至少有这三种情况:云打包排队太久,项目需要集成自定义原生插件,或者你对安装包体积、构建过程有强控制需求。本地打包的核心思路是:用 HBuilderX 生成一份 App 资源包,再把它塞进安卓原生工程里,用 Android Studio 完成编译和签名。
操作步骤如下:
- 在 HBuilderX 点击发行 → 原生App-本地打包 → 生成本地打包App资源。这一步会在
unpackage/resources目录下生成__UNI__xxx命名的资源文件夹,这个名称就是应用标识,后面要用。 - 去 uni-app 官方文档找到 Android 离线打包 SDK,下载和你 HBuilderX 版本对应的 SDK 工程模板。版本对应关系极其重要,新版 HBuilderX 升级后,旧版离线 SDK 经常会编译失败。
- 用 Android Studio 打开 SDK 工程模板,把 HBuilderX 生成的资源文件夹复制到
app/src/main/assets/apps目录下。 - 修改工程里的包名配置,把默认包名改成你自己申请的包名。
- 在
build.gradle里配置签名文件,填写 keystore 路径、别名、密码。 - 菜单栏选择 Build → Generate Signed APK,按向导生成正式签名包。
本地打包最大的坑在于版本不匹配。你打开 HBuilderX 用的 3.x 版本和离线 SDK 的版本必须对得上,否则会遇到各种底层异常。遇到这类问题,先去对照官方文档的版本说明,别急着怀疑自己的代码。
6.2 iOS 打包:证书、描述文件与云打包
iOS 生态的签名体系比安卓严格很多。iOS 打包必须有开发者账号,整个流程可以归纳为三步。
第一步,生成 Certificate Signing Request 文件,也就是证书请求文件。第二步,在开发者后台创建 App ID,也叫 Bundle Identifier,相当于 iOS 的包名。第三步,用证书请求文件在后台生成安装发布证书,再生成对应的描述文件,也就是.mobileprovision文件。描述文件会把证书、App ID、测试设备绑定在一起。
在 HBuilderX 里做 iOS 云打包时,需要上传两个关键文件:导出后的.p12证书文件和.mobileprovision描述文件,同时填写 Bundle ID。打包完成后会下载到一个.ipa文件,测试阶段可以通过第三方分发平台装到手机上。
这里要提醒一个很实际的问题:iPhone 真机调试时,描述文件里必须先添加测试设备的 UDID,生成描述文件时要勾选对应设备,否则安装到手机时报「无法安装此App」。第一次接触 iOS 打包的人经常在这里卡住。
6.3 正式发布前的自检清单
把自检清单列在这里,每次打包前过一遍,能省下大量返工时间:
| 检查项 | 怎么检查 |
|---|---|
| 包名正确 | Android 包名、iOS Bundle ID,和市场后台保持一致 |
| 签名一致 | 新包签名与上市场版本一致,不换证书 |
| 图标无透明通道 | 用不透明底图,避免安卓桌面图标黑底 |
| 版本号合理 | 版本号和版本名称都升级,不要低于线上版本 |
| 权限声明完整 | 相机、定位等权限已勾选,且隐私协议文案已配置 |
| 隐私弹窗可用 | 首次安装打开,弹窗出现并能跳转政策页面 |
| 多版本真机测试 | 至少覆盖新旧主流安卓版本和 iOS 版本 |
7. 高频问题与排查技巧实录
最后把常见问题按「现象 → 原因 → 解决思路」整理成表格。这里的内容不是我凭空想出来的,都是日常答疑里反复出现的经典问题。
7.1 从创建到安装的高频报错速查
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 运行到微信开发者工具提示无法连接 | 微信开发者工具服务端口未开启 | 设置 → 安全设置 → 开启服务端口,重试 |
| 运行到手机白屏 | 页面路由路径写错或组件报错 | 打开手机端控制台,查看路由和 JS 报错信息 |
| 手机安装 apk 提示签名不一致 | 签名证书和之前安装的包不一致 | 卸载旧包重新安装,发布时必须保证签名一致 |
| 云打包提示图标缺失 | manifest 图标未生成或路径不对 | 回到 HBuilderX 图标配置界面,重新自动生成 |
| 新版包体积突然变大 | 静态资源被打包或基础库升级 | 检查 static 目录是否有大图,图片尽量走 CDN |
| H5 端正常但 App 端报错 | 代码包含浏览器专属 API | 检查代码里的document、window用法,改为条件编译或 uni API |
排查这类问题有一个基础原则:先看控制台报错,再改代码。很多新人遇到问题第一反应是整个文件删了重写,其实错误信息往往已经把原因说清楚了。H5 端和小程序端的控制台,都能定位到具体文件和行号。
7.2 本地打包编译出错与版本不匹配处理
本地打包报错,典型的是 Gradle 依赖拉不下来,或者编译时出现某个类找不到。这类问题绝大多数是版本对应关系错了。我在实际项目里遇到过uniapp离线包和 HBuilderX 版本差了一个小版本,结果运行时就崩溃,日志里全是底层库加载失败的异常。处理方法是:把 HBuilderX 升级到和离线 SDK 完全一致的版本,重新生成资源,重新构建。
如果只是外部依赖下载失败,可以通过配置国内镜像源解决,Android Studio 里 Gradle 仓库地址也可以手动指定。还有一类报错和 JDK 版本有关,比如提示Unsupported class file major version,大概率是 JDK 版本太高或者太低,调整到 17 一般能解决。
另外提醒一句:本地打包的报错日志在 Android Studio 底部 Build 窗口里,完整复制日志再排查。不少人提问只发一句「打包失败」,没有日志,谁也没法判断具体原因。学会看日志,是打包环节最重要的基本功。
8. 实操心得与建议
做了这么久 uni-app 项目,我个人的实操体会是:项目创建和打包这两件事,看起来是体力活,实际上考验的是对配置和签名的理解。
第一个心得,把 manifest.json 和 pages.json 的变更当成代码 Review 的一部分。配置文件的误改,比业务代码的 bug 更难发现,因为它在编译期不报错,在运行期才暴露。团队里每次有人改这两个文件,我都会要求把改动原因写清楚,尤其是权限和 AppID 相关字段。
第二个心得,给每个项目建一个部署信息文档,把包名、keystore 路径、证书密码、各市场账号、当前线上版本号全部记在里面。团队最怕的是核心同事离职后,证书密码跟着消失。这个文档应该跟代码仓库放在一起,并且有备份。
最后分享一个小技巧。新同学进团队,我从不让 TA 直接做业务,而是让 TA 先把一个空项目从创建走到云打包,再走一遍本地打包,最后跑通 iOS 云打包。这个流程走完,对新人对整个工程链路认知的建立,比看十篇文档都有效。打包这件事不难,难的是系统地理解它。