Android 安装 apk 与 Intent.VIEW_ACTION 不与文件提供者一起工作

新手上路,请多包涵

我的应用程序有一个自动更新功能,可以下载一个 APK,当下载完成时,一个 Intent.VIEW_ACTION 打开应用程序并让用户安装下载的 apk

 Uri uri = Uri.parse("file://" + destination);
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
install.setDataAndType(uri,
    manager.getMimeTypeForDownloadedFile(downloadId));
activity.startActivity(install);

这适用于所有 < 24 的设备

现在使用 Android 24 显然我们不再允许使用 file:/// 开始意图并且在谷歌搜索后建议使用文件提供程序

新代码:

 Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri apkUri = FileProvider.getUriForFile(AutoUpdate.this,
    BuildConfig.APPLICATION_ID + ".provider", file);
install.setDataAndType(apkUri,
    manager.getMimeTypeForDownloadedFile(downloadId));
activity.startActivity(install);

现在 activity.startActivity(install);抛出错误

找不到处理 Intent 的活动0x4000000 }

有什么方法可以在 Android 7 (24) 中打开 APK 查看器?

原文由 Johny19 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.7k
2 个回答

经过大量尝试后,我已经能够通过为低于 Nougat 的任何东西创建不同的意图来解决这个问题,因为在 Nougat 导致错误之前使用 FileProvider 创建带有 Android 版本的安装意图:

 ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.INSTALL_PACKAGE dat=content://XXX.apk flg=0x1 }

在 Android Nougat 上使用普通 Uri 时会产生以下错误:

 FileUriExposedException: file:///XXX.apk exposed beyond app through Intent.getData()

我的解决方案适用于模拟器上的 Android N 和运行 Android M 的手机。

 File toInstall = new File(appDirectory, appName + ".apk");
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    Uri apkUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileprovider", toInstall);
    intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
    intent.setData(apkUri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
    Uri apkUri = Uri.fromFile(toInstall);
    intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
activity.startActivity(intent);

Android 牛轧糖 7.1 更新:

您还需要在清单中添加权限 REQUEST_INSTALL_PACKAGES。它从 Api Level 23 (Android 6.0 Marshmallow) 开始可用,从 Level 25 (Android 7.1 Nougat) 开始需要。

更新:

如果您尝试安装的文件在外部存储上,请记住请求读取和写入外部存储的权限。还要为 Android Nougat 及更高版本设置正确的 FileProvider。

首先通过调用下面的 canReadWriteExternal() 检查你是否有写权限,如果没有调用 requestPermission() 之前:

 private static final int REQUEST_WRITE_PERMISSION = 786;

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_WRITE_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED)
        Toast.makeText(this, "Permission granted", Toast.LENGTH_LONG).show();
}

private void requestPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
        requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_PERMISSION);
}

private boolean canReadWriteExternal() {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
            ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED;
}

以下是外部存储上下载文件夹的文件提供程序示例。 安卓清单.xml

 <application ... >
    ...

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>
</application>

资源/xml/文件路径.xml

 <?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_download" path="Download"/>
</paths>

如果您在安装 .apk 时遇到类似“解析包时出现问题”之类的错误。可能是您没有请求读/写权限,或者您尝试安装的文件不存在或已损坏。

Android Oreo 8.0 更新:

您必须检查当前应用程序是否允许在 Android Oreo 8.0 或更高版本上安装 APK。

您可以使用 PackageManager 类的 canRequestPackageInstalls 方法检查您的应用是否被允许安装 APK。如果它返回 false,那么您可以使用 ACTION_MANAGE_UNKNOWN_APP_SOURCES 操作启动 intent 以启动设置对话框,用户可以在其中允许应用程序安装 APK。

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
        && !getPackageManager().canRequestPackageInstalls()) {
    Intent unknownAppSourceIntent = new Intent()
            .setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
            .setData(Uri.parse(String.format("package:%s", getPackageName())));

    unknownAppSourceDialog.launch(unknownAppSourceIntent);
} else {
    // App already have the permission to install so launch the APK installation.
    startActivity(intent);
}

确保将以下代码添加到您的活动中以接收意图的结果。

 ActivityResultLauncher<Intent> unknownAppSourceDialog = registerForActivityResult(
    new ActivityResultContracts.StartActivityForResult(),
    result -> {
        if (result.getResultCode() == Activity.RESULT_OK) {
            // User has allowed app to install APKs
            // so we can now launch APK installation.
            startActivity(intent);
        }
    });

原文由 just_user 发布,翻译遵循 CC BY-SA 4.0 许可协议

我在调用启动活动时遇到了这个问题。暂停我当前的活动后,它突然回来并调用 onResume。就像什么都没发生一样。我的问题是清单中的此权限:

  <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

几乎没有人提到这一点。所以记住这一点。在 sdk >= 24 中你需要使用 provider 因为它需要一个以 file:/// 开头的 intent 在 sdk 24 下你应该给 uri 以 content:/// 开头所以这就是为什么我们需要 sdk 24 及更高版本的文件提供者。我认为我不需要为此编写任何代码,因为@just_user 已经写出了正确的答案。 https://stacklearn.ir

原文由 Hossein Karami 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题