- 示例工程
- 前端
【免费下载链接】hello-uniapp
uni-app框架演示示例
uni-installApk 是 uni-app 生态中用于Android 平台安装 APK的官方 UTS 插件,它通过uni.installApk(options)这一简洁 API,让 App 应用能够在运行时拉起系统安装器完成 APK 的安装,是 App 内整包升级(热更新之外的完整安装包更新)场景的关键基础设施。本文以插件根目录 changelog.md 的版本演进为主线,结合插件的 readme.md 接口文档、Android 端 index.uts 核心实现、interface.uts 类型定义与 unierror.uts 错误体系,逐层解析该插件的能力边界、调用方式与底层原理,读完你即可在自己的 uni-app / uni-app x 项目中正确集成并排查 APK 安装问题。
一、changelog 揭示的插件演进脉络
hello-uniapp 仓库中的 uni-installApk/changelog.md 完整记录了该插件的 5 个版本,虽然条目简短,却精准勾勒出一条清晰的成熟路径:
| 版本 | 日期 | 核心变更 |
|---|---|---|
| 1.0.0 | 2023-10-26 | 安装 apk 的插件(首个版本) |
| 1.0.1 | 2023-10-27 | 支持 js 层调用 |
| 1.0.2 | 2023-10-27 | 修改文档 |
| 1.0.3 | 2023-10-27 | 遵循 UniError 规范 |
| 1.0.4 | 2023-12-08 | 兼容 asset 目录文件的处理 |
结合 package.json 中声明的版本号1.0.4,可以确认当前仓库内即为最终稳定版。这五个版本对应了插件生命周期中的五个关键能力里程碑:
- 1.0.0 → 1.0.1(js 层调用):插件从纯原生层能力扩展为可通过 JS 直接调用,这意味着在 vue 页面、nvue 页面乃至 uni-app x 项目中都能以标准
uni.installApk({...})语法使用,无需编写原生代码。 - 1.0.3(遵循 UniError 规范):失败回调从随意结构收敛为统一的 UniError 错误对象,携带
errSubject、errCode、errMsg三要素,与 uni-app 全局错误体系对齐,便于调用方统一处理。 - 1.0.4(兼容 asset 目录文件):新增对
android_asset目录内 APK 文件的安装支持,解决了"APK 随应用打包进 assets 后无法直接安装"的痛点。
版本兼容性提示:插件 package.json 中
engines.HBuilderX要求^3.94;readme 明确注明3.95 以下需要自定义基座才能使用该插件,正式打包发布则无此限制。
二、插件定位与平台支持矩阵
根据 readme.md 与 package.json 中uni_modules.platforms的声明,插件的适用范围非常明确:
- 适用平台:仅 Android(
app-android: y),iOS 端源码app-ios/index.uts为空文件(iOS 系统不允许第三方应用直接安装应用,符合系统限制); - 前端框架:同时支持 vue2 与 vue3;
- 运行环境:
uni-ext-api声明中js: true、kotlin: true(Android 原生由 Kotlin/UTS 编译实现)、swift: false; - 不支持范围:H5 移动端、H5-PC 及所有小程序端(微信、支付宝、百度、字节、QQ、钉钉、快手、飞书、京东)均为
n——这是合理的,因为"安装 APK"天然是 App 平台专属能力。
interface.uts中的@uniPlatform注解进一步给出运行门槛:Android 端要求osVer: 4.4、uniVer/unixVer: 3.94+,即 Android 4.4 及以上系统、HBuilderX 3.94 及以上版本(含 uni-app x)即可使用。
三、API 与参数详解:uni.installApk(options)
3.1 函数签名
根据 readme.md 与 interface.uts:
uni.installApk(options : InstallApkOptions) : void3.2 InstallApkOptions 参数说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| filePath | string | 是 | — | apk 文件地址,支持本地绝对路径或android_asset目录路径 |
| success | (res: InstallApkSuccess) => void | 否 | null | 调用成功的回调函数,res.errMsg为"success" |
| fail | (err: InstallApkFail) => void | 否 | null | 调用失败的回调函数,err.errCode为 UniError 错误码 |
| complete | (res: any) => void | 否 | null | 调用结束的回调函数(成功、失败都会执行) |
其中InstallApkSuccess、InstallApkFail等类型在 index.d.ts 中为 JS 层提供了完整的 TypeScript 声明,IDE 可据此获得智能提示。interface.uts中给出的最小可运行示例为:
uni.installApk({ filePath: "/xx/xx/xx.apk", complete: (res: any) => { console.log("complete => " + JSON.stringify(res)); } });3.3 三种回调的执行时机
从 app-android/index.uts 的实现可见,插件在context.startActivity(intent)成功拉起系统安装器后,立即构造InstallApkSuccess { errMsg: "success" }并依次触发success与complete。需要特别说明的是:该回调只代表系统安装界面被成功唤起,并不代表 APK 真正安装完成——最终安装结果由 Android 系统的安装确认界面决定。因此在实际业务中,通常需要在应用自身回到前台后再做安装结果校验(如通过版本号比对或plus.runtime.checkApplication等方式)。
四、错误处理与 UniError 规范(v1.0.3 核心能力)
v1.0.3 引入的"遵循 UniError 规范"使错误回调具备了统一结构。查看 unierror.uts 的实现:
export const UniErrorSubject = 'uni-installApk'; export const UniErrors : Map<InstallApkErrorCode, string> = new Map([ [1300002, 'No such file'], // 找不到文件 ]); export class InstallApkFailImpl extends UniError implements InstallApkFail { constructor(errCode : InstallApkErrorCode) { super(); this.errSubject = UniErrorSubject; this.errCode = errCode; this.errMsg = UniErrors[errCode] ?? ""; } }当前版本定义了唯一错误码:
| 错误码 | errMsg | 触发场景 |
|---|---|---|
| 1300002 | No such file | 指定的 apk 文件不存在或不是有效文件 |
错误对象遵循 UniError 三要素:errSubject: 'uni-installApk'(错误主题,标识来源插件)、errCode: 1300002(错误码)、errMsg: 'No such file'(可读信息)。调用方可在fail回调中通过err.errCode精确判断失败原因,例如升级中心在安装失败时弹出"错误码 1300002"的提示(见 uni-upgrade-center-app.uvue)。
五、Android 端源码级实现原理
插件核心逻辑位于 app-android/index.uts,整体流程可分为四步:
5.1 路径标准化与文件校验
const context = UTSAndroid.getAppContext() as Context var filePath = UTSAndroid.convert2AbsFullPath(options.filePath) var apkFile : File | null = null; if (filePath.startsWith("/android_asset/")) { filePath = filePath.replace("/android_asset/", "") apkFile = copyAssetFileToPrivateDir(context, filePath) } else { apkFile = new File(filePath) } if (apkFile != null && !apkFile.exists() && !apkFile.isFile()) { let error = new InstallApkFailImpl(1300002); options.fail?.(error) options.complete?.(error) return }- 先通过
UTSAndroid.convert2AbsFullPath将传入路径转换为绝对路径; - 若路径以
/android_asset/开头(即 APK 打包在 assets 资源目录中),则走 asset 复制分支(详见下文 v1.0.4 特性解析); - 否则直接以
java.io.File构造文件对象; - 文件不存在或不是常规文件时,直接抛出 1300002 错误并短路返回。
5.2 拉起系统安装器(Intent 构造)
const intent = new Intent() intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.setAction(Intent.ACTION_VIEW) if (Build.VERSION.SDK_INT >= 24) { const authority = context.getPackageName() + ".dc.fileprovider" const apkUri = FileProvider.getUriForFile(context, authority, apkFile!!) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); } else { intent.setDataAndType(Uri.fromFile(apkFile!!), "application/vnd.android.package-archive"); } context.startActivity(intent)这里体现了关键的Android 7.0(API 24)分水岭处理:
- Android 7.0 及以上(
SDK_INT >= 24):由于系统禁止file://URI 跨应用共享(FileUriExposedException),必须通过FileProvider.getUriForFile生成content://URI,authority 为包名.dc.fileprovider,并授予FLAG_GRANT_READ_URI_PERMISSION读取权限; - Android 6.0 及以下:直接使用
Uri.fromFile(apkFile)的传统file://URI 方式。
两者统一设置ACTION_VIEW动作与application/vnd.android.package-archiveMIME 类型,从而拉起系统包安装器完成安装。
5.3 权限声明
Android 上安装第三方应用需要权限,插件通过自带清单 app-android/AndroidManifest.xml 声明:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />该权限用于 Android 8.0+ 的"未知来源应用安装"授权。同时 package.json 的declaration字段也向插件市场声明了这一权限。需要注意:Android 8.0 及以上设备用户需在系统设置中允许"安装未知应用",否则安装会被系统拦截;从源码实现看,插件本身未做该设置的检测与引导,需要调用方在业务层面补充提示。
六、v1.0.4 特性:asset 目录 APK 的兼容处理
v1.0.4"兼容 asset 目录文件的处理"对应的就是 copyAssetFileToPrivateDir 函数。其原理是:assets 目录内的文件是只读的,无法直接提供给安装器,必须先复制到应用私有缓存目录。
function copyAssetFileToPrivateDir(context : Context, fileName : string) : File | null { const destPath = context.getCacheDir().getPath() + "/apks/" + fileName const outFile = new File(destPath) // 确保父目录存在 const parentFile = outFile.getParentFile() if (parentFile != null) { if (!parentFile.exists()) { parentFile.mkdirs() } } if (!outFile.exists()) { outFile.createNewFile() } // 以 1024 字节缓冲流式复制 assets 文件到缓存目录 const inputStream = context.getAssets().open(fileName) const outputStream = new FileOutputStream(outFile) let buffer = new ByteArray(1024); do { let len = inputStream.read(buffer); if (len == -1) { break; } outputStream.write(buffer, 0, len) } while (true) inputStream.close() outputStream.close() return outFile }实现要点:
- 复制目标统一为
getCacheDir()/apks/<原始文件名>,即应用缓存目录,无需额外存储权限; - 使用 1024 字节缓冲区边读边写,避免一次性加载大 APK 到内存;
- 若复制过程抛出异常(如资源不存在、IO 失败),函数返回
null,随后被 5.1 节的文件校验逻辑拦截并触发 1300002 错误。
这一特性使"内置 APK 静默更新"成为可能:开发者在打包时将升级 APK 放进android_asset目录,运行时即可通过uni.installApk({ filePath: "/android_asset/xxx.apk" })完成安装。
七、真实集成案例:uni-upgrade-center-app 的调用方式
该插件在 hello-uniapp 仓库中的直接消费者是升级中心插件uni-upgrade-center-app,其 package.json 将uni-installApk声明为三方依赖,changelog 也注明"新增依赖 uni-installApk、uts-progressNotification"。
场景一:uni-app x 项目整包升级(强制更新)
在 check-update.ts 中,通过条件编译区分传统 5+ 环境与 uni-app x 环境,后者使用本插件:
// #ifdef UNI-APP-X uni.installApk({ filePath: res.tempFilePath, success: () => { uni.showModal({ title: '安装成功请手动重启' }); }, fail: err => { uni.showModal({ title: '更新失败', content: err.message, showCancel: false }); } }); // #endif(对应传统环境中使用plus.runtime.install的代码在// #ifndef UNI-APP-X分支内,两者互斥。)
场景二:升级弹窗内的安装入口
在 uni-upgrade-center-app.uvue 的installPackage()方法中,APK 下载完成后调用插件并处理回调:
installPackage() { this.installing = true; // #ifdef APP-PLUS uni.installApk({ filePath: this.tempFilePath, success: _ => { console.log('installApk success'); this.installing = false; this.installed = true; }, fail: err => { console.error('installApk fail', err); // 安装失败需要重新下载安装包 this.installing = false; this.installed = false; uni.showModal({ title: '更新失败,请重新下载', content: `uni.installApk 错误码 ${err.errCode}`, showCancel: false }); } }); // 安装跳出覆盖安装,此处直接返回上一页 if (!this.is_mandatory) { uni.navigateBack() } // #endif }这段代码展示了两个值得借鉴的实战要点:
- 安装失败即标记
installed = false并提示重新下载,避免用户停留在"已安装"的错误状态; - 非强制更新场景下,唤起安装器后立即
navigateBack返回上一页——因为系统安装确认界面会盖在应用之上,用户完成操作返回时应用不应停留在升级弹窗页。
此外,upgrade-popup.vue 中还有url.indexOf('.apk') === -1的判定逻辑,用于区分 apk 包与 wgt 资源包——apk 整包更新走 uni-installApk,wgt 热更新走plus.runtime.install,两种更新机制并存互补。
八、使用步骤与注意事项小结
8.1 三步接入
- 安装插件:在 HBuilderX 中通过插件市场安装
uni-installApk(本仓库中已内置在uni_modules目录可直接引用),或将其uni_modules/uni-installApk目录拷贝进项目; - 确认环境:HBuilderX 版本 ≥ 3.94;若低于 3.95 需运行自定义基座调试;
- 调用 API:
uni.installApk({ filePath: "/storage/emulated/0/Download/app.apk", // 或 "/android_asset/app.apk" success: (res) => { console.log('安装界面已拉起', res.errMsg); }, fail: (err) => { console.error(`[${err.errSubject}] 错误码 ${err.errCode}: ${err.errMsg}`); }, complete: () => { console.log('installApk 调用流程结束'); } });8.2 注意事项清单
- 仅 Android:iOS 端源码为空,调用无效;H5、小程序端不可用;
- 权限链路:
REQUEST_INSTALL_PACKAGES已由插件清单自动声明,但 Android 8.0+ 需引导用户允许"安装未知来源应用"; - 回调语义:
success仅代表安装界面成功拉起,不代表安装完成,业务层需自行校验安装结果; - filePath 取值:优先使用下载任务返回的
tempFilePath(如升级中心案例)或应用私有目录路径;使用android_asset前缀时插件会自动复制到缓存目录; - 错误排查:出现 1300002 时,检查路径是否正确、文件是否真实存在、asset 复制是否成功。
九、总结:从 changelog 看一个插件的成熟之路
回看 changelog.md 的 5 个版本,恰是插件从"可用"走向"好用"的完整缩影:v1.0.0 解决"能不能装"(Android 平台拉起安装器)、v1.0.1 解决"好不好调"(JS 层直接调用)、v1.0.3 解决"错得明白"(UniError 统一错误规范)、v1.0.4 解决"包从哪来"(asset 内置包兼容)。配合 readme.md 的 API 文档与 Android 端 index.uts 的完整实现,这套"文档 + 类型声明 + 原生实现 + 错误体系 + 集成案例"的组合,正是 uni-app 生态中 UTS 插件的最佳实践范式,也是 hello-uniapp 仓库为开发者提供的可直接落地的整包升级参考。
- 示例工程
- 前端
【免费下载链接】hello-uniapp
uni-app框架演示示例
相关推荐
uni-app x uni-installApk 插件解析:用 UTS 实现 Android APK 本地安装能力
uni app x uni installApk 插件解析:用 UTS 实现 Android APK 本地安装能力 本篇技术指南以开源仓库 uni app 中的
示例工程前端移动开发跨平台hello-uniapp 中的 uni-fav 收藏按钮组件:从 changelog 到源码的全方位解析
hello uniapp 中的 uni fav 收藏按钮组件:从 changelog 到源码的全方位解析 导读 uni fav 是 uni app 生态中一个开
示例工程前端uni-app x 中的 uni.installApk:Android APK 安装能力解析与 UTS 插件源码实践
uni app x 中的 uni.installApk:Android APK 安装能力解析与 UTS 插件源码实践 uni.installApk 是 uni
示例工程前端移动开发跨平台
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考