uniapp开发APP重点

UniApp 开发 iOS/Android App 端

本文档为纯App端专属内容,剔除所有跨端冗余信息,完整覆盖从项目搭建、核心配置、生命周期、条件编译、原生能力、性能优化到打包上架的全流程。


项目实践

暂无


一、App端项目初始化与核心配置(全量专属详解)

App端的所有原生能力、打包规则、权限配置均围绕 manifest.json 展开,是App开发的核心根基,所有配置项必须精准设置,否则会导致功能不可用、打包失败、上架被拒。

1.1 项目初始化

  1. 使用HBuilderX创建项目,选择「uni-app项目」,根据技术栈选择Vue2/Vue3模板,推荐使用uni-ui官方模板,原生兼容性最优。
  2. 项目创建后,优先配置manifest.json,所有App端专属能力均在此文件中开启和配置。

1.2 manifest.json App端全量核心配置

1.2.1 基础配置(全局唯一,上架后不可修改)

配置项 核心说明 硬性规则
应用名称 App在手机桌面、应用商店显示的名称 控制在6个字以内,避免特殊字符,需与应用商店上架名称一致
应用版本名称 对外显示的版本号,如1.0.0 遵循语义化版本规范(主版本.次版本.修订版本),每次上架必须递增
应用版本号 对内的整数版本号,如1、2、3 Android对应versionCode,iOS对应CFBundleVersion,每次打包上架必须严格递增,否则无法更新应用商店版本
应用标识(AppID) App的唯一身份标识 Android对应包名(推荐反向域名格式,如com.example.app),iOS对应Bundle ID,上架后不可修改,必须与开发者后台申请的包名/Bundle ID完全一致

1.2.2 图标与启动图配置(上架必备,避免被拒)

  1. 应用图标
    • Android端:必须提供48*48、72*72、96*96、144*144、192*192分辨率图标,同时提供自适应前景+背景图标,避免拉伸变形、圆角裁切异常。
    • iOS端:必须提供1024*1024(App Store专用)、60*60@2x、60*60@3x、76*76@2x等全分辨率图标,禁止包含透明通道、圆角、alpha通道,否则上架App Store会被直接拒审。
  2. 启动图(Splash图)
    • 提供不同分辨率的启动图,适配全面屏、折叠屏、平板等不同屏幕尺寸,Android端推荐使用.9.png格式,避免拉伸。
    • 必须开启「等待首页渲染完成后关闭启动图」选项,避免启动后出现白屏。
    • 可配置启动图的状态栏样式(深色/浅色、是否隐藏)、开屏广告时长、关闭逻辑。

1.2.3 App模块配置(功能可用的核心)

必须勾选App中需要使用的原生模块,未勾选的模块打包后会被完全剔除,无法调用对应API,不需要的模块禁止勾选,否则会增加包体积、申请不必要的权限,导致上架被拒。

常用核心模块清单:

模块名称 核心作用 适用场景
Payment 支付模块,集成微信支付、支付宝支付原生SDK App内商品支付、订单支付
OAuth 第三方登录鉴权模块,集成微信、QQ、微博登录 第三方账号一键登录
Push 推送模块,集成个推/极光推送原生SDK 离线消息推送、运营通知推送
Maps 地图模块,集成高德/百度地图原生SDK 地图展示、路径规划、POI检索
Geolocation 定位模块,集成系统定位、高德/百度定位 获取用户经纬度、位置信息
Camera 相机模块 拍照、扫码、人脸识别
Gallery 相册模块 选择/保存图片、视频
Video 视频播放模块 本地/在线视频播放、全屏播放
Audio 音频播放模块 本地/在线音频、后台音频播放
File 文件系统模块 本地文件读写、文件夹管理
SQLite 本地数据库模块 本地结构化数据存储、离线数据管理
Bluetooth 蓝牙模块 蓝牙设备连接、数据通信
WebView 网页视图模块 加载远程网页、H5页面交互
Share 分享模块 分享内容到微信、QQ、微博等平台

1.2.4 权限配置(合规核心,上架必查)

仅申请功能必须的权限,非必要权限禁止勾选,否则会因隐私合规问题被应用商店拒审。

  1. Android权限配置
    常用核心权限清单:
    权限名称 核心作用 申请前提
    android.permission.INTERNET 网络访问权限 所有需要联网的App必须申请
    android.permission.ACCESS_NETWORK_STATE 网络状态获取权限 监听网络切换、判断网络是否可用
    android.permission.CAMERA 相机访问权限 拍照、扫码、录像功能
    android.permission.ACCESS_FINE_LOCATION 精确定位权限 获取用户精准位置、导航功能
    android.permission.READ_EXTERNAL_STORAGE 存储读取权限 读取相册、本地文件
    android.permission.WRITE_EXTERNAL_STORAGE 存储写入权限 保存图片、文件到本地
    android.permission.RECORD_AUDIO 麦克风录音权限 语音录制、视频拍摄
  2. iOS权限配置
    所有申请的权限必须填写清晰、真实的用途描述,否则上架App Store会被直接拒审,常用核心权限与描述:
    权限Key 用途描述规范示例
    NSCameraUsageDescription 需要访问您的相机,用于拍摄照片、扫码识别商品
    NSLocationWhenInUseUsageDescription 需要访问您的位置信息,用于展示附近门店、导航服务
    NSPhotoLibraryAddUsageDescription 需要访问您的相册,用于保存图片到手机
    NSPhotoLibraryUsageDescription 需要访问您的相册,用于选择图片发布内容
    NSMicrophoneUsageDescription 需要访问您的麦克风,用于录制语音、视频拍摄收音

1.2.5 原生配置

  1. 屏幕方向配置:配置App支持的横竖屏方向,默认仅开启竖屏,视频、直播类页面可单独配置横屏。
  2. 安全区域适配:强制开启「适配iPhone X及以上安全区域」,避免内容被刘海屏、底部小黑条遮挡。
  3. Android原生配置:自定义AndroidManifest.xml内容,如应用主题、Activity启动模式、intent-filter(第三方跳转、URL Scheme)、meta-data(第三方SDK配置)。
  4. iOS原生配置:自定义info.plist内容,如URL Scheme(微信/支付宝回调、第三方跳转)、LSApplicationQueriesSchemes(应用跳转白名单)、后台运行模式、ATS配置。

1.2.6 打包配置

  1. Android打包配置
    • 签名配置:必须生成.keystore签名文件,填写别名、密码、密钥库密码,签名文件必须妥善备份,丢失后无法更新应用商店的App
    • 打包类型:Debug测试包(用于开发测试)、Release正式包(用于上架应用商店)。
    • CPU架构:仅勾选armeabi-v7a、arm64-v8a,可覆盖99%以上Android手机,同时大幅减少包体积。
    • 代码混淆:开启ProGuard代码混淆,保护代码安全,必须配置第三方SDK的混淆规则,避免SDK被混淆后无法使用。
  2. iOS打包配置
    • 证书配置:配置iOS开发证书(真机测试)、发布证书(上架App Store),以及对应的描述文件,所有证书必须从Apple Developer后台申请。
    • 打包类型:Debug测试包(真机调试)、Release正式包(上架App Store)。
    • 最低支持iOS版本:推荐设置为iOS 11.0以上,覆盖99%以上活跃iOS设备。

1.3 pages.json App端专属配置

大部分基础配置与跨端规范一致,App端专属差异与核心配置如下:

  1. tabBar页面可放在分包中,无小程序端的主包限制。
  2. 页面style支持配置软键盘弹出模式:adjustResize(页面压缩)、adjustPan(页面上移),适配输入框被软键盘遮挡的场景。
  3. 支持自定义下拉刷新样式,如circle圆形刷新样式,可自定义颜色、背景。
  4. 可配置页面是否支持iOS侧滑返回,默认开启,可通过popGesture: none关闭。
  5. 支持配置页面状态栏样式、是否隐藏状态栏,适配沉浸式页面。

二、App端全量生命周期体系(含Plus引擎专属)

App端生命周期融合了UniApp标准生命周期与HTML5+(Plus)原生引擎生命周期,所有Plus原生API必须在生命周期钩子触发后调用,否则会报错,全量钩子详解如下。

2.1 应用生命周期(仅在App.vue中生效,全局唯一)

控制整个App的启动、前后台切换、全局错误捕获,是App原生能力初始化的唯一入口。

生命周期钩子 触发时机 接收参数 核心使用场景 App端专属注意事项
onLaunch App冷启动初始化完成时触发,全局仅触发1次,此时Plus原生引擎已完全就绪 options对象,包含:
- path:启动页面路径
- query:启动页面参数
- scene:启动场景(桌面图标、推送点击、第三方App跳转)
- referrerInfo:来源应用的包名/Bundle ID
1. 全局原生能力初始化:注册Android返回键监听、推送点击监听、前后台切换监听
2. 第三方SDK初始化:推送、统计、支付、地图SDK
3. 全局状态初始化:检查用户登录状态、token有效性
4. App热更新检查、整包版本更新检查
5. 调用Plus API获取设备信息、原生模块初始化
1. 仅冷启动触发,热启动(后台切回)不触发
2. 此时Plus引擎已100%就绪,可安全调用所有plus.* API,无需额外监听plusready事件
3. 禁止写入高耗时同步逻辑,否则会导致App启动白屏、卡顿
onShow App启动,或从后台切入前台显示时触发,每次切前台都会触发 onLaunchoptions参数 1. 刷新用户登录状态、token续期
2. 检查App版本更新、热更新
3. 重启暂停的音频、定位、蓝牙服务
4. 统计App前台停留时长、启动次数
1. 冷启动时,在onLaunch之后触发
2. 热启动仅触发onShow,不触发onLaunch
3. 锁屏后解锁、从其他App切回都会触发,触发频率高,禁止写入高耗时逻辑
onHide App从前台切入后台时触发(按Home键、切换App、锁屏) 无参数 1. 暂停音频、视频播放,避免后台耗电
2. 保存用户临时操作数据、页面状态
3. 断开非必要的websocket、定位、蓝牙连接
4. 统计App后台停留时长
1. Android系统对后台App的运行时间、内存占用有限制,禁止执行耗时过长操作
2. 锁屏时触发onHide,解锁时触发onShow
onError App发生脚本错误、API调用失败、原生模块报错时触发 error:错误信息完整字符串 1. 全局错误捕获与上报
2. 错误日志写入本地文件,便于后续排查
3. 友好错误提示,避免App白屏、无响应
1. 同步代码错误、异步Promise reject、原生模块报错均会触发
2. 禁止在此钩子内抛出新错误,导致死循环
onPageNotFound App打开的页面路径不存在时触发 对象参数,包含path(无效页面路径)、query(页面参数)、isEntryPage(是否为启动页) 1. 无效页面的兜底跳转,如跳转到首页/404页
2. 无效路径上报
3. 旧版本下线页面的重定向兼容
重定向必须使用uni.reLaunch/uni.redirectTo,禁止使用uni.navigateTo
onUnhandledRejection App出现未处理的Promise reject时触发 对象参数,包含reason(拒绝原因)、promise(对应Promise对象) 全局捕获异步接口、异步操作的错误,统一上报和友好提示 App端全版本支持,无需考虑基础库兼容
onThemeChange App系统深色/浅色主题切换时触发 对象参数,包含theme(dark/light) 监听系统主题变化,动态切换App的深色/浅色模式 仅iOS 13.0+、Android 10.0+系统支持

App.vue 完整代码示例(App端专属)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<script>
export default {
// App冷启动初始化,仅触发1次,Plus引擎已就绪
onLaunch(options) {
console.log('App冷启动,启动参数:', options)
// #ifdef APP-PLUS
// 1. 登录状态校验
const token = uni.getStorageSync('token')
if (!token) {
uni.reLaunch({ url: '/pages/login/login' })
return
}

// 2. Android物理返回键监听(核心必备)
plus.key.addEventListener('backbutton', (e) => {
e.preventDefault() // 阻止系统默认返回行为
const pages = getCurrentPages()
// 首页:提示是否退出App
if (pages.length === 1) {
uni.showModal({
title: '温馨提示',
content: '确定要退出应用吗?',
success: (res) => {
if (res.confirm) plus.runtime.quit() // 退出App
}
})
} else {
// 非首页:返回上一页
uni.navigateBack()
}
}, false)

// 3. 推送通知点击监听
plus.push.addEventListener('click', (msg) => {
console.log('推送点击事件:', msg)
// 根据推送参数跳转到对应页面
if (msg.payload && msg.payload.path) {
uni.navigateTo({ url: msg.payload.path })
}
}, false)

// 4. 推送消息接收监听
plus.push.addEventListener('receive', (msg) => {
console.log('收到推送消息:', msg)
// 处理前台收到的推送
}, false)

// 5. 启动时检查热更新
this.checkWgtHotUpdate()
// #endif
},

// App切前台触发,每次都会执行
onShow(options) {
console.log('App进入前台')
// #ifdef APP-PLUS
// 检查整包版本更新
this.checkFullPackageUpdate()
// token续期逻辑
const tokenExpire = uni.getStorageSync('token_expire')
if (tokenExpire && Date.now() > tokenExpire - 3600 * 1000) {
this.refreshToken()
}
// #endif
},

// App切后台触发
onHide() {
console.log('App进入后台')
// #ifdef APP-PLUS
// 暂停全局音频播放
const globalAudio = plus.audio.createPlayer()
if (!globalAudio.paused) globalAudio.pause()
// 保存用户最后操作时间
uni.setStorageSync('last_operation_time', Date.now())
// 断开非必要的长连接
// #endif
},

// 全局错误捕获
onError(error) {
console.error('App全局错误:', error)
// #ifdef APP-PLUS
// 错误日志写入本地文件
this.writeErrorLogToLocal(error)
// 错误信息上报到服务端
uni.request({
url: 'https://api.example.com/error/report',
method: 'POST',
data: {
error_msg: error,
app_version: plus.runtime.version,
platform: plus.os.name,
device_model: plus.device.model,
system_version: plus.os.version
}
})
// #endif
},

// 页面不存在兜底处理
onPageNotFound(res) {
console.error('访问的页面不存在:', res.path)
uni.reLaunch({ url: '/pages/index/index' })
},

methods: {
// #ifdef APP-PLUS
// 检查WGT热更新
async checkWgtHotUpdate() {
try {
const res = await uni.request({
url: 'https://api.example.com/app/hot-update',
method: 'POST',
data: {
current_version: plus.runtime.versionCode,
platform: plus.os.name.toLowerCase()
}
})
const updateData = res.data.data
// 存在热更新包
if (updateData.has_update && updateData.wgt_url) {
uni.showModal({
title: '版本更新',
content: updateData.update_desc || '发现新版本,是否立即更新?',
success: (modalRes) => {
if (modalRes.confirm) this.downloadAndInstallWgt(updateData.wgt_url)
}
})
}
} catch (err) {
console.error('热更新检查失败:', err)
}
},

// 下载并安装WGT热更新包
downloadAndInstallWgt(wgtUrl) {
uni.showLoading({ title: '更新中...', mask: true })
// 创建下载任务
const downloadTask = plus.downloader.createDownload(wgtUrl, {
filename: '_doc/update/',
timeout: 120
}, (download, status) => {
uni.hideLoading()
if (status === 200) {
// 安装WGT更新包
plus.runtime.install(download.filename, { force: true }, () => {
uni.showModal({
title: '更新成功',
content: '新版本已安装完成,是否立即重启应用?',
showCancel: false,
success: () => {
plus.runtime.restart() // 重启App生效
}
})
}, (installErr) => {
uni.showToast({ title: '更新包安装失败', icon: 'none' })
console.error('WGT安装失败:', installErr)
})
} else {
uni.showToast({ title: '更新包下载失败', icon: 'none' })
}
})
// 开始下载
downloadTask.start()
},

// 检查整包版本更新
async checkFullPackageUpdate() {
// 整包更新逻辑,与热更新类似,区分强制更新和可选更新
},

// 刷新token
async refreshToken() {
// token续期逻辑
},

// 错误日志写入本地文件
writeErrorLogToLocal(error) {
// 用plus.io写入本地文件逻辑
}
// #endif
}
}
</script>

<style>
/* App全局样式 */
page {
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
/* 适配iOS底部安全区域 */
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
}
</style>

2.2 Plus引擎专属就绪事件(特殊场景备用)

UniApp的onLaunch钩子已保证Plus引擎完全就绪,常规开发无需监听此事件,仅在脱离UniApp生命周期的独立JS文件、原生回调中需要使用。

1
2
3
4
5
// 仅用于非UniApp标准生命周期的代码中
document.addEventListener('plusready', function(){
console.log('Plus原生引擎已完全就绪')
// 此处可安全调用所有plus.* API
}, false)

2.3 页面生命周期(App端专属详解)

每个页面.vue文件中生效,控制单个页面的加载、渲染、显示、隐藏、卸载,App端专属规则与用法如下:

生命周期钩子 触发时机 接收参数 核心使用场景 App端专属注意事项
onLoad 页面加载时触发,每个页面仅触发1次 options:接收上一页通过URL传递的参数 1. 获取页面入参,初始化核心数据
2. 发起页面初始数据接口请求
3. 初始化定时器、事件监听
1. 仅触发1次,页面卸载前不会重复触发
2. 页面返回再进入,不会触发onLoad,仅触发onShow
3. 此时DOM未渲染,不能获取节点尺寸、调用原生UI组件
onShow 页面显示时触发,每次切入前台都会触发(首次加载、返回当前页、App切前台) 无参数 1. 刷新页面数据,如详情页返回后刷新列表状态
2. 重启暂停的动画、音频、定位
3. 更新实时数据(倒计时、用户信息)
1. tabBar页面切换时,仅触发onShow/onHide,不触发onLoad/onUnload
2. 触发频率高,禁止写入高耗时同步逻辑
onReady 页面初次渲染完成时触发,每个页面仅触发1次 无参数 1. 获取DOM节点尺寸、位置信息
2. 初始化canvas、地图、视频等原生组件
3. 执行依赖DOM渲染完成的动画
1. 仅触发1次,在onLoad/onShow之后触发
2. 必须配合this.$nextTick使用,确保节点渲染完成
3. 可通过this.$scope.$getAppWebview()获取当前页面的原生Webview实例,进行原生级操作
onHide 页面隐藏时触发(跳转到下一页、tab切换、App切后台) 无参数 1. 暂停动画、定时器、音频播放
2. 保存临时输入内容、页面滚动位置
3. 解绑事件监听,避免内存泄漏
页面跳转时,先触发当前页onHide,再触发下一页onLoad
onUnload 页面卸载时触发,每个页面仅触发1次 无参数 1. 销毁所有定时器、延时器
2. 解绑所有事件监听(事件总线、推送监听)
3. 销毁canvas、地图、视频等占用内存的组件
4. 取消未完成的接口请求
1. 触发场景:uni.redirectTouni.navigateBack关闭当前页、uni.reLaunch关闭所有页面
2. uni.navigateTo不会触发当前页onUnload,仅触发onHide
3. 必须在此清理所有副作用,否则会导致App内存泄漏、卡顿、闪退
onPullDownRefresh 页面下拉刷新时触发 无参数 重新请求页面数据,刷新列表、重置页面状态 1. 必须在pages.jsonglobalStyle或页面style中配置enablePullDownRefresh: true才能触发
2. 刷新完成后必须调用uni.stopPullDownRefresh()停止刷新动画
onReachBottom 页面滚动到底部时触发 无参数 列表分页加载,触发下一页数据请求 1. 触发距离可通过onReachBottomDistance配置,默认50px
2. 页面高度不足1屏时不会触发
3. scroll-view的滚动不会触发此钩子,需用scrolltolower事件
onPageScroll 页面滚动时实时触发 对象参数,包含scrollTop(垂直滚动距离,单位px) 1. 滚动时动态修改导航栏透明度、标题样式
2. 控制回到顶部按钮的显示/隐藏
触发频率极高,必须做节流处理(推荐100ms以上),禁止在此做频繁的数据更新,严重影响性能
onTabItemTap tabBar页面点击tab按钮时触发 对象参数,包含index(tab序号,从0开始)、pagePathtext 1. 点击tab时刷新页面数据
2. 拦截tab点击,如未登录时跳转到登录页
仅在tabBar页面中生效,非tabBar页面不触发,点击当前tab也会触发
onBackPress App端页面返回时触发(物理返回键、顶部返回按钮、侧滑返回) 对象参数,包含from(触发来源:backbutton/navigateBack) 1. 拦截返回行为,如未保存内容时提示用户
2. 自定义返回逻辑,如返回指定页面
App端专属钩子,小程序端不支持;return true会阻止默认返回行为

页面生命周期完整代码示例(App端专属)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<template>
<view class="list-container">
<view class="list-item" v-for="item in list" :key="item.id">
<text>{{ item.title }}</text>
</view>
</view>
</template>

<script>
export default {
data() {
return {
id: '',
list: [],
page: 1,
pageSize: 10,
isLoading: false,
hasMore: true,
scrollTimer: null
}
},
// 页面加载,仅触发1次
onLoad(options) {
console.log('页面加载,入参:', options)
this.id = options.id
this.getListData()
},
// 页面显示,每次切前台都触发
onShow() {
console.log('页面显示')
},
// 页面渲染完成,仅触发1次
onReady() {
console.log('页面渲染完成')
// #ifdef APP-PLUS
// 获取当前页面的原生Webview实例(App端专属高级用法)
const currentWebview = this.$scope.$getAppWebview()
// 设置Webview原生样式
currentWebview.setStyle({
background: '#f5f5f5',
overscrollEffect: 'none' // 关闭iOS回弹效果
})
console.log('当前Webview实例:', currentWebview)
// #endif

// 获取DOM节点信息
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this)
query.select('.list-container').boundingClientRect((rect) => {
console.log('容器尺寸:', rect)
}).exec()
})
},
// 页面隐藏
onHide() {
console.log('页面隐藏')
},
// 页面卸载
onUnload() {
console.log('页面卸载')
// 清理所有副作用
clearTimeout(this.scrollTimer)
uni.$off('refreshList')
},
// App端专属:返回拦截
onBackPress(e) {
console.log('返回触发来源:', e.from)
// 拦截返回,提示用户
uni.showModal({
title: '提示',
content: '确定要离开当前页面吗?未保存的内容会丢失',
success: (res) => {
if (res.confirm) {
uni.navigateBack()
}
}
})
// return true 阻止默认返回行为
return true
},
// 下拉刷新
onPullDownRefresh() {
console.log('下拉刷新')
this.page = 1
this.hasMore = true
this.getListData().finally(() => {
uni.stopPullDownRefresh()
})
},
// 滚动到底部,加载更多
onReachBottom() {
console.log('滚动到底部')
if (!this.isLoading && this.hasMore) {
this.page++
this.getListData()
}
},
// 页面滚动
onPageScroll(e) {
// 节流处理
if (this.scrollTimer) clearTimeout(this.scrollTimer)
this.scrollTimer = setTimeout(() => {
console.log('滚动距离:', e.scrollTop)
}, 100)
},
methods: {
async getListData() {
this.isLoading = true
try {
const res = await uni.request({
url: 'https://api.example.com/list',
data: {
id: this.id,
page: this.page,
pageSize: this.pageSize
}
})
const { data } = res.data
if (this.page === 1) {
this.list = data.list
} else {
this.list = [...this.list, ...data.list]
}
// 判断是否还有更多数据
this.hasMore = this.list.length < data.total
} catch (err) {
console.error('列表请求失败:', err)
} finally {
this.isLoading = false
}
}
}
}
</script>

<style scoped>
.list-container {
padding: 30rpx;
/* 适配iOS底部安全区域 */
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
}
.list-item {
padding: 20rpx;
margin-bottom: 20rpx;
background: #fff;
border-radius: 12rpx;
}
</style>

2.4 组件生命周期(App端专属)

App端组件生命周期完全遵循Vue标准生命周期,无额外专属钩子,核心规则如下:

生命周期钩子 触发时机 核心使用场景 App端注意事项
beforeCreate 组件实例刚创建,数据观测未初始化 极少使用 无法访问data、methods,触发时机极早
created 组件实例创建完成,data、methods已初始化,DOM未生成 1. 发起组件所需的接口请求
2. 初始化组件内部状态
组件未挂载,不能操作DOM节点
beforeMount 组件挂载开始前,模板编译完成 极少使用 触发在created之后,mounted之前
mounted 组件挂载到DOM完成 1. 获取组件内DOM节点信息
2. 初始化canvas、动画等依赖DOM的逻辑
必须配合this.$nextTick使用,确保节点渲染完成
beforeUpdate 组件数据更新,DOM重渲染前触发 监听数据变化,获取更新前的DOM状态 频繁触发,慎用,避免影响性能
updated 组件数据更新,DOM重渲染完成后触发 数据更新后,操作更新后的DOM 禁止在此修改数据,否则会触发无限更新循环
beforeDestroy 组件实例销毁前触发,实例完全可用 1. 清理定时器、延时器
2. 解绑事件监听
3. 取消未完成的接口请求
必须在此清理所有副作用,避免App内存泄漏
destroyed 组件实例销毁完成后触发 最终清理逻辑,销毁占用内存的对象 仅触发1次

三、App端条件编译 全场景实战详解

条件编译是App端处理iOS/Android平台差异、原生能力兼容、环境区分的核心方案,通过特殊注释语法,让代码仅在对应平台编译时生效,避免冗余代码和兼容报错。

3.1 基础语法与平台标识

核心语法

  • #ifdef 平台标识:如果当前编译平台匹配该标识,编译这段代码
  • #ifndef 平台标识:如果当前编译平台不匹配该标识,编译这段代码
  • #endif:条件编译结束标记

App端专属平台标识

平台标识 对应平台
APP-PLUS 所有App端(iOS+Android)
APP-PLUS-IOS 仅iOS App端
APP-PLUS-ANDROID 仅Android App端
H5 仅H5端(用于排除App端逻辑)
MP 所有小程序端(用于排除App端逻辑)

3.2 模板(template)中的条件编译

用于控制iOS/Android平台显示不同的组件、内容、功能入口,最常用场景示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<view class="setting-container">
<!-- 所有端通用内容 -->
<view class="common-item">通用设置</view>

<!-- 仅App端显示的内容 -->
<!-- #ifdef APP-PLUS -->
<view class="app-item">
<text>版本更新</text>
<text>{{ appVersion }}</text>
</view>
<view class="app-item">
<text>清除缓存</text>
<text>{{ cacheSize }}</text>
</view>
<!-- #endif -->

<!-- 仅iOS App端显示的内容 -->
<!-- #ifdef APP-PLUS-IOS -->
<view class="ios-item">
<text>App Store评分</text>
</view>
<!-- #endif -->

<!-- 仅Android App端显示的内容 -->
<!-- #ifdef APP-PLUS-ANDROID -->
<view class="android-item">
<text>退出应用</text>
</view>
<view class="android-item">
<text>后台权限设置</text>
</view>
<!-- #endif -->

<!-- 非App端显示的内容 -->
<!-- #ifndef APP-PLUS -->
<view class="h5-mp-item">非App端专属内容</view>
<!-- #endif -->
</view>
</template>

3.3 脚本(script)中的条件编译

用于处理iOS/Android平台的原生API差异、原生插件调用、逻辑区分,避免不兼容API报错,全场景示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
export default {
data() {
return {
appVersion: '',
cacheSize: '0MB'
}
},
onLoad() {
// #ifdef APP-PLUS
this.appVersion = plus.runtime.version
this.getAppCacheSize()
// #endif
},
methods: {
// 清除App缓存(仅App端生效)
clearAppCache() {
// #ifdef APP-PLUS
uni.showLoading({ title: '清除中...' })
// 仅iOS执行的逻辑
// #ifdef APP-PLUS-IOS
plus.io.clearCache(() => {
uni.hideLoading()
this.getAppCacheSize()
uni.showToast({ title: '缓存清除成功' })
})
// #endif

// 仅Android执行的逻辑
// #ifdef APP-PLUS-ANDROID
// Android清除缓存的原生逻辑
plus.android.importClass('android.os.Environment')
plus.android.importClass('java.io.File')
// 完整清除逻辑...
uni.hideLoading()
this.getAppCacheSize()
uni.showToast({ title: '缓存清除成功' })
// #endif
// #endif

// #ifndef APP-PLUS
uni.showToast({ title: '仅App端支持此功能', icon: 'none' })
// #endif
},

// 获取App缓存大小
getAppCacheSize() {
// #ifdef APP-PLUS
// iOS/Android差异化获取缓存逻辑
// #endif
},

// 调用原生蓝牙插件(仅App端生效)
connectBluetoothDevice(deviceId) {
// #ifdef APP-PLUS
// 引入原生蓝牙插件
const BluetoothPlugin = uni.requireNativePlugin('com.example.bluetooth')
BluetoothPlugin.connectDevice(deviceId, (res) => {
console.log('蓝牙连接结果:', res)
})
// #endif

// #ifndef APP-PLUS
uni.showToast({ title: '仅App端支持蓝牙连接', icon: 'none' })
// #endif
},

// 退出App(仅Android端生效)
exitApp() {
// #ifdef APP-PLUS-ANDROID
uni.showModal({
title: '提示',
content: '确定要退出应用吗?',
success: (res) => {
if (res.confirm) plus.runtime.quit()
}
})
// #endif

// #ifndef APP-PLUS-ANDROID
uni.showToast({ title: '仅Android端支持此功能', icon: 'none' })
// #endif
}
}
}

3.4 样式(style)中的条件编译

用于处理iOS/Android平台的样式适配、安全区域适配、系统UI差异,示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.setting-container {
width: 100%;
padding: 30rpx;
box-sizing: border-box;
}

/* 所有App端通用样式 */
/* #ifdef APP-PLUS */
.setting-container {
padding-top: 88rpx; /* 适配App原生导航栏 */
font-size: 30rpx;
}
/* #endif */

/* 仅iOS App端样式 */
/* #ifdef APP-PLUS-IOS */
.setting-container {
padding-top: calc(88rpx + env(safe-area-inset-top)); /* 适配iOS刘海屏 */
padding-bottom: calc(30rpx + env(safe-area-inset-bottom)); /* 适配iOS底部小黑条 */
}
/* #endif */

/* 仅Android App端样式 */
/* #ifdef APP-PLUS-ANDROID */
.setting-container {
background-color: #fafafa;
padding-bottom: 30rpx;
}
/* #endif */

3.5 配置文件(pages.json)中的条件编译

用于处理iOS/Android平台的页面配置、tabBar样式、窗口表现差异,示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
// #ifdef APP-PLUS-IOS
"navigationBarBackgroundColor": "#007AFF",
"navigationBarTextStyle": "white",
// #endif
// #ifdef APP-PLUS-ANDROID
"navigationBarBackgroundColor": "#2196F3",
"navigationBarTextStyle": "white",
// #endif
"enablePullDownRefresh": true
}
},
{
"path": "pages/setting/setting",
"style": {
"navigationBarTitleText": "设置"
}
}
],
"tabBar": {
"color": "#999999",
// #ifdef APP-PLUS-IOS
"selectedColor": "#007AFF",
"backgroundColor": "#ffffff",
// #endif
// #ifdef APP-PLUS-ANDROID
"selectedColor": "#2196F3",
"backgroundColor": "#fafafa",
// #endif
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tab/home.png",
"selectedIconPath": "static/tab/home-active.png"
},
{
"pagePath": "pages/setting/setting",
"text": "设置",
"iconPath": "static/tab/setting.png",
"selectedIconPath": "static/tab/setting-active.png"
}
]
},
// App端专属分包配置
// #ifdef APP-PLUS
"subPackages": [
{
"root": "pages/goods",
"pages": [
{ "path": "detail", "style": { "navigationBarTitleText": "商品详情" } },
{ "path": "list", "style": { "navigationBarTitleText": "商品列表" } }
]
}
]
// #endif
}

3.6 环境区分条件编译

用于区分开发环境和生产环境,配置不同的接口域名、调试开关,示例:

1
2
3
4
5
6
7
8
9
// 接口域名配置
const BASE_URL = process.env.NODE_ENV === 'development'
? 'http://test-api.example.com' // 开发环境测试域名
: 'https://api.example.com' // 生产环境正式域名

// 调试开关
const DEBUG = process.env.NODE_ENV === 'development'

export { BASE_URL, DEBUG }

四、App端核心原生能力(Plus API 全量核心用法)

App端与小程序、H5端的核心差异,在于可以通过HTML5+(Plus)引擎调用系统原生能力,突破浏览器限制,实现和原生App一致的功能体验,核心能力全量详解如下。

4.1 运行时与应用管理(plus.runtime)

核心API 功能说明 代码示例
plus.runtime.version 获取App版本名称 const version = plus.runtime.version
plus.runtime.versionCode 获取App整数版本号 const versionCode = plus.runtime.versionCode
plus.runtime.quit() 退出App(仅Android支持) plus.runtime.quit()
plus.runtime.restart() 重启App plus.runtime.restart()
plus.runtime.install() 安装WGT热更新包、APK安装包 plus.runtime.install(filePath, { force: true }, successCallback, errorCallback)
plus.runtime.openURL() 打开链接、调用第三方App plus.runtime.openURL('tel:10086') // 拨打电话
plus.runtime.isApplicationExist() 判断第三方App是否安装 plus.runtime.isApplicationExist({ pname: 'com.tencent.mm' }) // 判断微信是否安装

4.2 文件系统管理(plus.io)

App端可完全访问本地文件系统,实现文件读写、文件夹管理、缓存清理等功能,核心用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// #ifdef APP-PLUS
// 1. 获取应用文档目录
const docDir = plus.io.PUBLIC_DOCUMENTS

// 2. 创建文件夹
function createFolder(folderPath) {
plus.io.resolveLocalFileSystemURL(folderPath, (entry) => {
console.log('文件夹已存在')
}, (err) => {
// 文件夹不存在,创建
plus.io.resolveLocalFileSystemURL(docDir, (entry) => {
entry.getDirectory('my_folder', { create: true, exclusive: false }, (dirEntry) => {
console.log('文件夹创建成功')
})
})
})
}

// 3. 写入文件
function writeFile(fileName, content) {
plus.io.resolveLocalFileSystemURL(docDir + '/' + fileName, (entry) => {
entry.createWriter((writer) => {
writer.write(content)
writer.onwrite = () => {
console.log('文件写入成功')
}
})
}, (err) => {
// 文件不存在,创建并写入
plus.io.resolveLocalFileSystemURL(docDir, (entry) => {
entry.getFile(fileName, { create: true }, (fileEntry) => {
fileEntry.createWriter((writer) => {
writer.write(content)
})
})
})
})
}

// 4. 读取文件
function readFile(fileName) {
plus.io.resolveLocalFileSystemURL(docDir + '/' + fileName, (entry) => {
entry.file((file) => {
const reader = new FileReader()
reader.onload = (e) => {
console.log('文件内容:', e.target.result)
}
reader.readAsText(file)
})
})
}
// #endif

4.3 本地数据库(plus.sqlite)

App端支持SQLite本地数据库,可存储大量结构化离线数据,核心用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// #ifdef APP-PLUS
// 1. 打开/创建数据库
function openDB() {
const isOpen = plus.sqlite.isOpenDatabase({
name: 'app_data.db',
path: '_doc/app_data.db'
})
if (!isOpen) {
plus.sqlite.openDatabase({
name: 'app_data.db',
path: '_doc/app_data.db',
success: () => {
console.log('数据库打开成功')
// 创建表
createTable()
},
fail: (err) => {
console.error('数据库打开失败:', err)
}
})
}
}

// 2. 创建数据表
function createTable() {
plus.sqlite.executeSql({
name: 'app_data.db',
sql: 'CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)',
success: () => {
console.log('表创建成功')
}
})
}

// 3. 新增数据
function insertUserData(name, age) {
plus.sqlite.executeSql({
name: 'app_data.db',
sql: `INSERT INTO user (name, age) VALUES ('${name}', ${age})`,
success: () => {
console.log('数据插入成功')
}
})
}

// 4. 查询数据
function queryUserData() {
plus.sqlite.selectSql({
name: 'app_data.db',
sql: 'SELECT * FROM user',
success: (data) => {
console.log('查询结果:', data)
}
})
}
// #endif

4.4 原生UI与交互(plus.nativeUI)

App端可调用系统原生UI组件,体验比web组件更流畅,核心API:

API 功能说明
plus.nativeUI.toast() 原生Toast提示,比uni.showToast更稳定
plus.nativeUI.alert() 原生弹窗提示
plus.nativeUI.confirm() 原生确认弹窗
plus.nativeUI.prompt() 原生输入弹窗
plus.nativeUI.showWaiting() 原生加载等待框,不可被点击穿透
plus.nativeUI.closeWaiting() 关闭加载等待框
plus.nativeUI.pickDate() 原生日期选择器
plus.nativeUI.pickTime() 原生时间选择器

4.5 设备信息与系统能力(plus.device)

核心API 功能说明
plus.device.uuid 获取设备唯一标识
plus.device.model 获取设备型号
plus.device.vendor 获取设备厂商
plus.device.os 获取设备系统名称(iOS/Android)
plus.device.version 获取系统版本号
plus.device.vibrate() 设备震动
plus.device.setVolume() 设置设备音量
plus.device.getInfo() 获取完整设备信息

4.6 下载与上传(plus.downloader / plus.uploader)

App端支持后台下载、断点续传、批量上传,不受浏览器限制,核心用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// #ifdef APP-PLUS
// 1. 创建下载任务
function downloadFile(url, fileName) {
const downloadTask = plus.downloader.createDownload(url, {
filename: '_doc/download/' + fileName,
timeout: 300,
retry: 2
}, (download, status) => {
if (status === 200) {
console.log('下载完成,文件路径:', download.filename)
// 打开文件
plus.runtime.openFile(download.filename)
} else {
console.error('下载失败')
}
})

// 监听下载进度
downloadTask.addEventListener('statechanged', (task) => {
if (task.downloadedSize && task.totalSize) {
const progress = Math.floor((task.downloadedSize / task.totalSize) * 100)
console.log('下载进度:', progress + '%')
}
})

// 开始下载
downloadTask.start()
}

// 2. 暂停下载
downloadTask.pause()

// 3. 恢复下载
downloadTask.resume()
// #endif

4.7 核心业务能力

  1. 支付能力:通过uni.requestPayment调用微信支付、支付宝支付,需在manifest.json中勾选Payment模块,配置支付参数。
  2. 第三方登录:通过uni.login调用微信、QQ、微博登录,需在manifest.json中勾选OAuth模块,配置平台参数。
  3. 分享能力:通过uni.share分享内容到微信、QQ、微博等平台,需在manifest.json中勾选Share模块。
  4. 推送能力:通过plus.push实现离线消息推送,需在manifest.json中勾选Push模块,配置推送平台参数。
  5. 地图与定位:通过plus.mapsplus.geolocation实现地图展示、定位、路径规划,需在manifest.json中勾选Maps、Geolocation模块。

五、App端原生插件开发与集成

当UniApp内置API和Plus API无法满足需求时,可通过原生插件扩展App能力,实现和原生App完全一致的功能。

5.1 插件市场原生插件使用

  1. 打开DCloud插件市场,筛选「App原生插件」,选择符合需求的插件(免费/付费)。
  2. 点击「购买并下载」,绑定你的DCloud账号和App的包名/Bundle ID。
  3. 在HBuilderX中,右键项目 → 「原生插件」 → 「选用云端插件」,勾选已购买的插件。
  4. 制作自定义调试基座:HBuilderX → 「运行」 → 「运行到手机或模拟器」 → 「制作自定义调试基座」,等待云端打包完成。
  5. 使用自定义基座运行项目,即可调用插件API,插件用法参考插件文档。

注意:原生插件必须使用自定义调试基座才能生效,标准基座不包含原生插件,调用会报错。

5.2 本地原生插件集成

  1. 项目根目录创建nativeplugins文件夹,将下载的原生插件解压到该目录。
  2. manifest.json → 「App原生插件配置」 → 「本地插件」中,勾选本地插件。
  3. 制作自定义调试基座,运行项目即可调用。

5.3 原生插件开发基础

  1. Android原生插件开发:使用Android Studio开发,基于UniApp的Android插件规范,继承UniModule类,编写原生方法,通过@UniJSMethod注解暴露给前端调用。
  2. iOS原生插件开发:使用Xcode开发,基于UniApp的iOS插件规范,继承DCUniModule类,编写原生方法,通过UNI_EXPORT_METHOD宏暴露给前端调用。
  3. 前端调用原生插件
    1
    2
    3
    4
    5
    6
    7
    8
    // #ifdef APP-PLUS
    // 引入原生插件
    const MyPlugin = uni.requireNativePlugin('插件包名')
    // 调用插件方法
    MyPlugin.nativeMethod({ param1: 'test' }, (res) => {
    console.log('原生方法返回结果:', res)
    })
    // #endif

5.4 离线打包

当需要深度修改原生代码、集成公司内部SDK、自定义原生功能时,需使用离线打包:

  1. 从DCloud官网下载对应版本的UniApp离线SDK。
  2. Android端:使用Android Studio打开离线SDK中的Android项目,配置包名、签名、模块、原生插件,编译生成APK。
  3. iOS端:使用Xcode打开离线SDK中的iOS项目,配置Bundle ID、证书、模块、原生插件,编译生成IPA。

六、App端数据通信与状态管理

6.1 全局状态管理

  • Vue2项目:使用Vuex管理全局状态(用户信息、token、全局配置),适合中大型项目。
  • Vue3项目:使用Pinia管理全局状态,比Vuex更简洁,支持TypeScript,推荐使用。
  • 小型项目:使用getApp().globalData挂载全局变量,简单便捷,适合少量全局数据。

6.2 页面间通信

  1. URL参数传递:最常用方式,通过uni.navigateTo的url拼接参数,目标页面onLoad中接收,适合少量简单参数。
  2. 事件总线(EventBus):通过uni.$emit触发事件,uni.$on监听事件,实现跨页面通信,必须在页面销毁时用uni.$off解绑,避免内存泄漏。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 页面A:触发事件
    uni.$emit('refreshList', { page: 1 })

    // 页面B:监听事件
    onLoad() {
    uni.$on('refreshList', (data) => {
    this.getList(data.page)
    })
    },
    onUnload() {
    uni.$off('refreshList')
    }
  3. 全局存储:通过uni.setStorageSync/uni.getStorageSync存储和读取数据,适合大量数据、持久化数据传递。
  4. 页面栈获取:通过getCurrentPages()获取页面栈实例,直接操作上一个页面的数据和方法,适合父子页面通信。
    1
    2
    3
    4
    const pages = getCurrentPages()
    const prevPage = pages[pages.length - 2]
    prevPage.getList() // 调用上一页的方法
    prevPage.$vm.status = 1 // 修改上一页的数据

6.3 原生层与前端层通信

  1. 原生插件通过回调函数、事件通知的方式,将原生层的数据传递给前端。
  2. 前端通过uni.requireNativePlugin调用原生方法,传递参数给原生层。
  3. 通过plus.globalEvent监听原生层的全局事件,实现原生到前端的主动通信。

七、App端专属性能优化全方案

7.1 启动速度优化(核心指标)

  1. 包体积瘦身:仅勾选必须的原生模块,剔除未使用的模块;图片、视频等资源压缩,非必要资源上传CDN;开启代码混淆、摇树优化,剔除冗余代码。
  2. 启动逻辑精简App.vueonLaunch中,仅执行必须的初始化逻辑,非必须逻辑延迟到首页渲染完成后执行,避免阻塞启动。
  3. 首页优化:首页onLoad/onShow中不做高耗时同步逻辑,先渲染骨架屏,再异步加载数据;首页DOM结构精简,减少嵌套层级。
  4. 渲染引擎优化:manifest.json中开启「v3编译器」「App端渲染加速」,提升渲染速度。
  5. 原生启动图优化:开启「等待首页渲染完成后关闭启动图」,避免启动后白屏;启动图精简,避免加载耗时资源。

7.2 内存优化(解决闪退、卡顿)

  1. 页面栈管理:App端页面栈无10层限制,但层级过深会导致内存占用过大,推荐使用uni.redirectTo代替uni.navigateTo关闭不需要的页面,避免页面栈层级超过20层。
  2. 资源释放:页面onUnload、组件beforeDestroy时,必须清理所有定时器、事件监听、websocket、canvas、地图、视频实例,避免内存泄漏。
  3. 图片内存优化:大图、长图使用缩略图,点击查看原图;图片设置固定宽高,避免渲染时缩放;大量图片使用虚拟列表,仅渲染可视区域图片,开启懒加载。
  4. 数据优化:避免在data中存放大量不渲染的数据,仅把需要渲染的数据放在data中;大列表数据分页加载,避免一次性渲染上万条数据。

7.3 渲染性能优化(解决卡顿、掉帧)

  1. DOM优化:页面DOM节点数控制在1000个以内,嵌套层级不超过10层;频繁切换的组件用v-show,仅渲染一次的用v-if,避免频繁销毁重建。
  2. 长列表优化:超过100条的长列表,必须使用虚拟滚动,仅渲染可视区域节点,推荐使用uni-listmescroll-uni组件;列表item结构精简,减少嵌套层级。
  3. 动画优化:优先使用CSS3动画,避免JS动画;使用transformopacity实现动画,触发硬件加速,避免修改width、height、top等属性导致重排。
  4. 滚动优化onPageScroll中必须做节流处理,禁止频繁修改数据、操作DOM;长列表滚动时,暂停非必要的接口请求、图片加载。

7.4 包体积优化

  1. 原生模块:仅勾选必须的模块,剔除所有未使用的模块,每个模块都会增加包体积。
  2. CPU架构:Android端仅勾选armeabi-v7a、arm64-v8a,减少包体积。
  3. 静态资源:所有图片、音频、视频资源必须压缩,非必要资源上传CDN,不打包到App中。
  4. 代码瘦身:开启代码压缩、摇树优化,剔除未使用的代码、组件、依赖;避免引入过大的第三方库,按需引入。
  5. 资源分包:非核心页面、资源放入分包,按需加载,减少主包体积。

7.5 热更新优化

  1. 区分整包更新和热更新:原生模块修改必须整包更新,仅前端代码修改使用WGT热更新。
  2. 热更新包体积优化:WGT包仅包含修改的文件,剔除未修改的资源,减少下载体积。
  3. 增量更新:实现增量更新,仅下载修改的文件,减少流量消耗和更新时间。
  4. 更新时机:App启动时、切前台时检查更新,非强制更新可在用户空闲时后台下载更新包。

八、App端调试、兼容与适配

8.1 调试方法

  1. 真机运行调试:HBuilderX连接手机,直接运行项目到真机,查看控制台日志。
  2. 自定义基座调试:集成原生插件后,必须制作自定义调试基座,使用自定义基座运行调试。
  3. Chrome开发者工具调试:App运行到手机后,打开Chrome浏览器,输入chrome://inspect,即可调试App的WebView页面,查看DOM、Console、Network、Storage等。
  4. 原生日志调试:Android端使用Android Studio查看Logcat日志,iOS端使用Xcode查看控制台日志,排查原生模块报错。
  5. 离线包调试:打包后的APK/IPA,可通过HBuilderX的「云端打包」生成Debug包,连接调试工具排查问题。

8.2 屏幕适配

  1. 单位适配:优先使用rpx单位(基于750px设计稿),配合flex弹性布局,适配不同屏幕尺寸。
  2. 安全区域适配:iOS端必须适配刘海屏、底部小黑条,使用env(safe-area-inset-top)env(safe-area-inset-bottom)设置内边距。
  3. 全面屏适配:Android端适配全面屏、挖孔屏、折叠屏,避免内容被遮挡。
  4. 字体适配:禁止使用固定字体大小,使用rpx单位,适配系统字体大小调整。

8.3 系统版本兼容

  1. 最低版本设置:Android最低支持5.0+,iOS最低支持11.0+,覆盖99%以上活跃设备。
  2. API兼容:高版本系统新增的API,必须做版本判断,低版本系统做降级处理。
  3. 权限兼容:Android 10+分区存储权限、Android 13+通知权限、iOS 14+定位权限、iOS 15+隐私权限,必须做兼容处理。
  4. 厂商适配:适配华为、小米、OPPO、vivo等国产Android厂商的定制系统,如后台权限、自启动权限、推送权限。

8.4 iOS/Android平台差异兼容

差异点 iOS端 Android端 兼容方案
返回键 无物理返回键,仅侧滑返回、顶部返回按钮 有物理返回键,需监听backbutton事件 通过条件编译分别处理,Android端必须监听返回键事件
安全区域 刘海屏、底部小黑条,必须适配 大部分机型无底部安全区域,仅全面屏有顶部挖孔 iOS端使用env(safe-area-inset-*)适配,Android端单独处理
权限申请 申请权限必须填写用途描述,一次申请,永久生效/拒绝 权限可多次申请,部分权限需要动态申请 统一封装权限申请方法,通过条件编译处理平台差异
状态栏 可自定义状态栏样式,沉浸式适配简单 不同厂商系统状态栏适配复杂,部分机型不支持状态栏文字颜色修改 使用plus.navigator设置状态栏样式,做机型兼容
安装包格式 IPA格式,仅能通过App Store、企业证书安装 APK格式,可直接安装、应用商店安装 整包更新区分平台,提供对应安装包

九、App端打包、签名与上架全流程

9.1 Android端打包、签名与上架

9.1.1 生成签名文件

  1. 安装JDK环境,使用keytool命令生成.keystore签名文件:
    1
    keytool -genkey -alias myapp -keyalg RSA -keysize 2048 -validity 36500 -keystore myapp.keystore
  2. 填写别名、密码、姓名、组织、城市等信息,生成签名文件,妥善保管,丢失后无法更新应用商店的App

9.1.2 打包生成正式APK

  1. 在HBuilderX中,打开manifest.json → 「App发行」 → 「原生App-云打包」。
  2. 选择Android平台,填写包名,选择「使用自有证书」,上传.keystore签名文件,填写别名、密码。
  3. 勾选「Release正式包」,选择CPU架构,开启代码混淆,点击「打包」,等待云端打包完成。
  4. 打包完成后,下载正式APK文件,进行加固、测试。

9.1.3 应用加固

上架前必须对APK进行加固,防止反编译、破解,推荐使用腾讯乐固、360加固、梆梆加固等平台。

9.1.4 上架应用商店

  1. 注册开发者账号:华为开发者联盟、小米开放平台、OPPO开放平台、vivo开放平台、应用宝等。
  2. 完善应用信息:应用名称、简介、图标、截图、资质文件、隐私政策、用户协议。
  3. 上传加固后的APK文件,填写版本号、更新日志。
  4. 提交审核,等待审核通过后,即可发布上线。

9.2 iOS端打包、签名与上架

9.2.1 申请证书与描述文件

  1. 注册Apple Developer开发者账号,年费99美元。
  2. 在开发者后台,创建App ID(Bundle ID),必须与manifest.json中的AppID完全一致。
  3. 生成开发证书、发布证书(.p12格式),安装到Mac电脑中。
  4. 生成开发描述文件、发布描述文件(.mobileprovision格式)。

9.2.2 打包生成正式IPA

  1. 在HBuilderX中,打开manifest.json → 「App发行」 → 「原生App-云打包」。
  2. 选择iOS平台,填写Bundle ID,上传发布证书(.p12)、发布描述文件(.mobileprovision),填写证书密码。
  3. 勾选「Release正式包」,点击「打包」,等待云端打包完成。
  4. 打包完成后,下载正式IPA文件。

9.2.3 上架App Store

  1. 登录App Store Connect后台,创建App,填写App信息、简介、图标、截图、隐私政策、资质文件。
  2. 使用Mac电脑,通过Transporter软件,将IPA文件上传到App Store Connect。
  3. 上传完成后,在App Store Connect中,创建新版本,选择上传的构建版本,填写更新日志。
  4. 提交审核,等待审核通过后,即可发布上线。

十、App端安全合规与上架避坑

10.1 安全防护

  1. 代码安全:开启代码混淆、加固,防止反编译、破解;敏感代码使用原生插件实现,避免前端明文存储。
  2. 数据安全:用户敏感信息加密存储,禁止明文存储;token设置有效期,定期续期;接口请求做签名校验,防止抓包篡改。
  3. 网络安全:所有接口使用HTTPS协议,禁止使用HTTP;证书校验,防止中间人攻击;敏感数据传输加密。
  4. 权限安全:遵循最小必要原则,仅申请功能必须的权限,不申请无关权限;权限申请必须明确告知用户用途,用户同意后才能使用。

10.2 合规要求

  1. 隐私合规:必须制定完整的《隐私政策》《用户协议》,App首次启动时弹窗提示,获取用户同意后,才能收集用户信息、申请权限;禁止未经用户同意收集个人信息、私自上传用户数据。
  2. 权限合规:禁止在用户未同意隐私政策前申请权限;禁止非必要场景申请权限;权限申请必须明确告知用途,用户拒绝后不能强制退出App。
  3. 内容合规:App内无违规内容、违法信息,符合国家法律法规;特殊类目(金融、医疗、教育、新闻等)必须提供对应资质文件。
  4. 支付合规:iOS端禁止使用微信支付、支付宝等第三方支付进行虚拟商品支付,必须使用苹果内购(IAP),否则会被App Store拒审。

10.3 上架避坑核心要点

  1. App Store上架避坑
    • 必须提供完整的测试账号,供审核人员测试所有功能。
    • 禁止隐藏违规功能、热更新违规内容。
    • 虚拟商品必须使用苹果内购,禁止第三方支付。
    • 所有申请的权限必须填写真实、清晰的用途描述。
    • 禁止诱导用户好评、分享、下载。
    • 必须提供完整的隐私政策链接,且可正常访问。
  2. Android应用商店上架避坑
    • 必须提供软件著作权证书,大部分应用商店强制要求。
    • 必须完成APP备案,2024年起国内应用商店强制要求。
    • 隐私政策必须符合《个人信息保护法》,通过第三方隐私合规检测。
    • 禁止私自收集用户信息、私自申请权限。
    • 必须提供完整的资质文件,特殊类目必须提供对应许可证。

uniapp开发APP重点
https://cszy.top/20250923-uniapp开发APP重点/
作者
csorz
发布于
2025年9月23日
许可协议