☰
hello-uniapp 中的 uni-installApk 插件:从 changelog 到源码的 Android APK 安装能力全解析
2026/10/15 3:53:53 网站建设 项目流程
  • 示例工程
  • 前端

【免费下载链接】hello-uniapp

uni-app框架演示示例

项目地址:https://gitcode.com/gh_mirrors/he/hello-uniapp
点击查看免费下载

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.02023-10-26安装 apk 的插件(首个版本)
1.0.12023-10-27支持 js 层调用
1.0.22023-10-27修改文档
1.0.32023-10-27遵循 UniError 规范
1.0.42023-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) : void

3.2 InstallApkOptions 参数说明

参数类型必填默认值说明
filePathstring是—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触发场景
1300002No 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 }

这段代码展示了两个值得借鉴的实战要点:

  1. 安装失败即标记installed = false并提示重新下载,避免用户停留在"已安装"的错误状态;
  2. 非强制更新场景下,唤起安装器后立即navigateBack返回上一页——因为系统安装确认界面会盖在应用之上,用户完成操作返回时应用不应停留在升级弹窗页。

此外,upgrade-popup.vue 中还有url.indexOf('.apk') === -1的判定逻辑,用于区分 apk 包与 wgt 资源包——apk 整包更新走 uni-installApk,wgt 热更新走plus.runtime.install,两种更新机制并存互补。

八、使用步骤与注意事项小结

8.1 三步接入

  1. 安装插件:在 HBuilderX 中通过插件市场安装uni-installApk(本仓库中已内置在uni_modules目录可直接引用),或将其uni_modules/uni-installApk目录拷贝进项目;
  2. 确认环境:HBuilderX 版本 ≥ 3.94;若低于 3.95 需运行自定义基座调试;
  3. 调用 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框架演示示例

项目地址:https://gitcode.com/gh_mirrors/he/hello-uniapp
点击查看免费下载
上一篇:wifit3社区致谢:Linux无线驱动逆向英雄们如何成就了wifit3
下一篇:WARP实验日志LEARNED.md全解析:如何用验证方法论保留所有失败结论

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询