You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android 8.0及以上版本File Provider自动下载安装APK失败问题

Android 8.0+ 无法自动安装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

火山引擎 最新活动