Android 8.0及以上版本File Provider自动下载安装APK失败问题
我来帮你搞定这个问题!你的代码在Oreo以下版本正常运行,但8.0及以上安装失败,核心原因有两个:Android 8.0引入了「未知应用安装权限」的强制校验,再加上你代码里DownloadManager的目标路径配置错误,导致FileProvider无法正确定位到下载的APK文件。
一、先处理Android 8.0+的安装权限限制
从API 26开始,系统默认禁止应用安装未知来源的APK,必须主动申请REQUEST_INSTALL_PACKAGES权限才能触发安装流程:
1. 在Manifest中添加权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
2. 启动安装前检查并请求权限
在调用安装Intent之前,必须先判断是否拥有该权限,如果没有就引导用户去设置页开启:
private static final int REQUEST_INSTALL_PERMISSION = 1001; // 检查权限并启动安装 private void startInstallApk(Uri apkUri) { Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE); installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive"); installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 检查是否拥有安装未知应用的权限 if (getPackageManager().canRequestPackageInstalls()) { startActivity(installIntent); } else { // 跳转到设置页开启权限 Intent permissionIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName())); startActivityForResult(permissionIntent, REQUEST_INSTALL_PERMISSION); } } else { // 低版本直接启动安装 startActivity(installIntent); } } // 处理权限请求的回调 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_INSTALL_PERMISSION) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (getPackageManager().canRequestPackageInstalls()) { // 权限已开启,重新触发安装 startInstallApk(uri); } else { // 用户拒绝了权限,提示无法安装 Toast.makeText(this, "需要开启未知应用安装权限才能完成更新", Toast.LENGTH_SHORT).show(); } } } }
二、修正DownloadManager的目标路径配置
你代码里的request.setDestinationInExternalFilesDir第二个参数写错了!应该指定文件存储的目录(比如Environment.DIRECTORY_DOWNLOADS),而不是FileProvider的authority:
错误代码:
request.setDestinationInExternalFilesDir(Main2Activity.this, BuildConfig.APPLICATION_ID + ".provider", fileName);
修正后的代码:
request.setDestinationInExternalFilesDir(Main2Activity.this, Environment.DIRECTORY_DOWNLOADS, fileName);
这个路径要和你之前创建的folder目录完全一致,这样FileProvider才能找到下载好的APK文件。
三、确保FileProvider的配置完全正确
1. 在Manifest中注册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/file_paths" /> </provider>
注意:如果你的项目用的是老的support库,把androidx.core.content.FileProvider换成android.support.v4.content.FileProvider。
2. 创建file_paths.xml配置文件
在res/xml目录下新建file_paths.xml,配置外部文件目录的访问路径:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 对应getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)的路径 --> <external-files-path name="downloads" path="Downloads" /> </paths>
这里的path="Downloads"就是Environment.DIRECTORY_DOWNLOADS对应的子目录名称,必须和你存储文件的路径匹配。
四、完整的下载后安装流程优化
最后,确保你在DownloadManager下载完成后,再获取文件Uri并启动安装。可以通过广播接收器监听下载完成事件:
// 注册下载完成广播 private BroadcastReceiver downloadCompleteReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); // 验证是我们的下载任务 if (downloadId == yourDownloadId) { // 获取下载的文件Uri Uri apkUri = FileProvider.getUriForFile(Main2Activity.this, BuildConfig.APPLICATION_ID + ".provider", file); // 启动安装 startInstallApk(apkUri); } } }; // 在onCreate中注册广播 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); // ...其他初始化代码 registerReceiver(downloadCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } // 在onDestroy中注销广播 @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(downloadCompleteReceiver); }
按照以上步骤修改后,你的APK自动下载安装功能应该就能在Android 8.0及以上版本正常工作了!
内容的提问来源于stack exchange,提问作者abhi wadaskar




