如何在Kotlin中实现APK安装,解决下载后闪退及适配Oreo权限问题
APK安装闪退问题修复方案
现有实现的核心问题
- Android 8.0及以上仅在清单声明
REQUEST_INSTALL_PACKAGES权限无效,需要动态请求用户授权 - 清单中
REQUEST_INSTALL_PACKAGES权限声明前多了多余空格,导致权限注册失效 - Android N及以上的安装Intent未添加
FLAG_ACTIVITY_NEW_TASK标记,易引发上下文异常闪退 - FileProvider的
authorities属性在清单中写死,和代码中动态拼接的规则不一致,包名变更时会触发Uri解析错误 - 缺少安装包存在性校验,下载失败或文件丢失时直接跳转安装会崩溃
修复步骤
1. 修正AndroidManifest.xml配置
首先删除权限声明的多余空格,调整FileProvider的authorities为动态变量:
<!-- 权限声明 --> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <!-- FileProvider配置 --> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider>
2. 校验provider_paths.xml路径配置
确保你下载的APK存放目录在路径规则覆盖范围内,示例配置如下:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 适配外部存储Download目录 --> <external-path name="download" path="Download/"/> <!-- 适配应用内部存储的apk目录 --> <files-path name="internal_apk" path="apk/"/> <!-- 适配应用外部专属存储目录 --> <external-files-path name="external_file" path="apk/"/> </paths>
3. 新增安装权限动态申请逻辑
Android 8.0及以上需要先校验权限,无权限时先跳转授权:
// 先定义权限请求码常量 private const val REQUEST_INSTALL_PERMISSION = 1001 // 触发安装前先校验权限 fun checkInstallPermissionAndInstall(apkPath: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val hasPermission = requireActivity().packageManager.canRequestPackageInstalls() if (!hasPermission) { val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:${BuildConfig.APPLICATION_ID}")) startActivityForResult(intent, REQUEST_INSTALL_PERMISSION) // 授权完成后在onActivityResult/Activity Result回调中再执行安装逻辑 return } } // 已有权限,直接执行安装 startInstallApk(apkPath) }
4. 修正安装Intent代码
private fun startInstallApk(uriString: String) { val toInstall = File(uriString) // 先校验文件存在性 if (!toInstall.exists() || toInstall.length() <= 0) { Toast.makeText(requireActivity(), "安装包已损坏,请重新下载", Toast.LENGTH_SHORT).show() return } val intent = Intent(Intent.ACTION_VIEW) val apkUri: Uri if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { apkUri = FileProvider.getUriForFile(requireActivity(), BuildConfig.APPLICATION_ID + ".provider", toInstall) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } else { apkUri = Uri.fromFile(toInstall) } intent.setDataAndType(apkUri, "application/vnd.android.package-archive") // 所有版本统一添加NEW_TASK标记,避免跳转异常 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) requireActivity().startActivity(intent) }
额外注意事项
- 若使用DownloadManager下载APK,需通过文件路径读取文件,不要直接使用DownloadManager返回的Content Uri转File
- 若TargetSdkVersion >=30,APK存放在应用专属目录下时,不需要申请读写外部存储权限
- 安装前可校验APK的MD5值,避免下载损坏导致安装失败
内容的提问来源于stack exchange,提问作者miguel angel Asencio
相关产品推荐
相关产品推荐

