問題描述
現(xiàn)象
代碼執(zhí)行安裝Apk刽酱,出現(xiàn)系統(tǒng)彈框解析錯誤喳逛,解析包時出現(xiàn)錯誤
場景
在華為P20 Android 8.0 手機(jī)上,下載Apk并使用通知欄進(jìn)度條顯示棵里,開啟應(yīng)用鎖屏通知權(quán)限润文,下載過程在鎖屏情況下進(jìn)行,下載完成后自動執(zhí)行安裝Apk殿怜,在解鎖后出現(xiàn)系統(tǒng)彈框典蝌,解析包出現(xiàn)錯誤。
解決之前安裝Apk的方法
首先在AndroidManifest中聲明fileProvider
<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/file_path" />
</provider>
provider屬性說明
屬性 | 說明 |
---|---|
name | android V4 包中的類FileProvider |
authorities | 你的文件的Uri的域名一般以包名.fileprovider的格式头谜,防止重名 |
exported | 設(shè)置不允許導(dǎo)出骏掀,我們的FileProvider應(yīng)該是私有的 |
grantUriPermissions | 允許獲取文件的臨時訪問權(quán)限 |
resourse | 設(shè)置FileProvider訪問的文件路徑 |
res包下創(chuàng)建file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_path"
path="test" />
<cache-path
name="internal_path"
path="test" />
這里可以創(chuàng)建很多個paths,但是每個paths的name不能一樣
</paths>
path 說明
<files-path name="*name*" path="*path*" /> 對應(yīng)的是:Context.getFileDir()的路徑地址
對應(yīng)路徑:Context.getFileDir()+"/${path}/"
得到路徑:content://${applicationId}/&{name}/
<cache-path name="*name*" path="*path*" />
對應(yīng)路徑:Context.getCacheFir()+"/${path}/"
得到路徑:content://${applicationId}/&{name}/
<external-path name="*name*" path="*path*" />
對應(yīng)路徑:Environment.getExternalStorageDirectory()+"/${path}/"
得到路徑:content://${applicationId}/&{name}/
<external-files-path name="*name*" path="*path*" />
對應(yīng)路徑:Context.getExternalStorageDirectory()+"/${path}/"
得到路徑:content://${applicationId}/&{name}/
<external-cache-path name="*name*" path="*path*" />
對應(yīng)路徑: Context.getExternalCacheDir()+"/${path}/"
得到路徑:content://${applicationId}/&{name}/
舉個例子說明:
path做如下聲明
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
</paths>
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
contentUri值為:content://com.mydomain.fileprovider/my_images/default_image.jpg
安裝apk的方法(7.0版本兼容問題)
public static void installApk(Context context,File apkFile){
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri apkUri = null;
//判斷版本是否是 7.0 及 7.0 以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
apkUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", apkFile);
//添加對目標(biāo)應(yīng)用臨時授權(quán)該Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} else {
apkUri = Uri.fromFile(apkFile);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(apkUri,
"application/vnd.android.package-archive");
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
Android 8.0系統(tǒng)需要聲明權(quán)限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGE" />
OK,以上就是大家普遍解決7.0柱告,以及8.0版本兼容問題的方法截驮。
但是,在上文描述的場景中依然報出了錯誤:
java.lang.SecurityException: Permission Denial:
opening provider android.support.v4.content.FileProvider from ProcessRecord{cc3ad2316425:
com.android.packageinstaller/u0a21} (pid=16425, uid=10021) that is not exported from uid 10340
懵逼.jpg
問題定位
經(jīng)過短暫的懵逼后际度,開始通過各種方式侧纯,探索問題的原因。
根據(jù)系統(tǒng)log分析甲脏,猜測在鎖屏?xí)r眶熬,用于安裝Apk的service處于休眠或者不可用的狀態(tài),導(dǎo)致通過intent.addflags
方式賦予的臨時權(quán)限失效了块请。于是娜氏,再次仔細(xì)看了官方文檔后,發(fā)現(xiàn)還有一個方法墩新,可以生成權(quán)限且在主動調(diào)用方法或者手機(jī)重啟后才會失效贸弥。
改進(jìn)后的代碼
public static void installApk(Context context,File apkFile){
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri apkUri = null;
//判斷版本是否是 7.0 及 7.0 以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
apkUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", apkFile);
//添加對目標(biāo)應(yīng)用臨時授權(quán)該Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} else {
apkUri = Uri.fromFile(apkFile);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(apkUri,
"application/vnd.android.package-archive");
//查詢所有符合 intent 跳轉(zhuǎn)目標(biāo)應(yīng)用類型的應(yīng)用,注意此方法必須放置setDataAndType的方法之后
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
//然后全部授權(quán)
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, apkUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
//對應(yīng)取消權(quán)限的方法是
context.revokeUriPermission(uri, modeFlags)
再次嘗試海渊,此問題再沒有出現(xiàn)绵疲。