现象
小米应用内更新 解析包错误
vivo应用内更新 画面一闪而逝
原因
权限问题
FileProvider SecurityException
最终解决方案
前提是已经在AndroidManifest.xml 中配置过provider节点
1.添加权限申请
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
2.代码兼容
public static void installFile(Context c, String path) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
File apkFile = new File(path);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri uriForFile = FileProvider.getUriForFile(c, "com.xxx.provider", apkFile);
// List<ResolveInfo> resInfoList = c.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
// for (ResolveInfo resolveInfo : resInfoList) {
// String packageName = resolveInfo.activityInfo.packageName;
c.grantUriPermission(c.getPackageName(), uriForFile, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
// }
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION );
intent.setDataAndType(uriForFile, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
c.startActivity(intent);
} catch (Exception e) {
if (e instanceof SecurityException && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
if (!c.getPackageManager().canRequestPackageInstalls()) {
startInstallPermissionSettingActivity(c);
}
}
}
}
/**
* 跳转到设置-允许安装未知来源-页面
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private static void startInstallPermissionSettingActivity(Context c) {
//注意这个是8.0新API
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
c.startActivity(intent);
}
google 的解释
Generating the Content URI for a File
To share a file with another app using a content URI, your app has to generate the content URI. To generate the content URI, create a new File for the file, then pass the File to getUriForFile(). You can send the content URI returned by getUriForFile() to another app in an Intent. The client app that receives the content URI can open the file and access its contents by calling ContentResolver.openFileDescriptor to get a ParcelFileDescriptor.
For example, suppose your app is offering files to other apps with a FileProvider that has the authority com.mydomain.fileprovider. To get a content URI for the file default_image.jpg in the images/ subdirectory of your internal storage add the following code:
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
As a result of the previous snippet, getUriForFile() returns the content URI
content://com.mydomain.fileprovider/my_images/default_image.jpg.
Granting Temporary Permissions to a URI
To grant an access permission to a content URI returned from getUriForFile(), do one of the following:
- Call the method Context.grantUriPermission(package, Uri, mode_flags) for the content:// Uri, using the desired mode flags. This grants temporary access permission for the content URI to the specified package, according to the value of the the mode_flags parameter, which you can set to FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION or both. The permission remains in effect until you revoke it by calling revokeUriPermission() or until the device reboots.
- Put the content URI in an Intent by calling setData().
- Next, call the method Intent.setFlags() with either FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION or both.
- Finally, send the Intent to another app. Most often, you do this by calling setResult().
Permissions granted in an Intent remain in effect while the stack of the receiving Activity is active. When the stack finishes, the permissions are automatically removed. Permissions granted to one Activity in a client app are automatically extended to other components of that app.