背景
最近項目開發過程中用到安裝apk的功能。把從服務器下載下來的apk安裝到機器中。安裝過程中遇到的問題記錄一下。
問題
安裝過程中提示解析軟件包時出現問題。
說明:本文是針對運行的apk在Manifest中聲明瞭systemuid。如果你的應用聲明瞭"android.uid.system",而且也遇到了這個問題,恭喜你找到了本文。
android:sharedUserId="android.uid.system"
沒有聲明的小夥伴可以去找別的博客了。
安裝apk
首先回顧一下安裝的方法,android O中大家一般這樣寫的。
private void installApk(String path){
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = FileProvider.getUriForFile(context,"com.****.****.fileprovider",new File(path));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
context.startActivity(intent);
}
然後再Manifest中聲明provider
<provider
android:authorities="com.honeywell.depponservice.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
在xml中建一個filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path path="." name="cmexternal" />
</paths>
這種方式是沒問題的,但是關機問題出在了我的app聲明瞭SYSTEM_UID。爲什麼這麼說呢,下面繼續講。
原因
我就是按照上面的方法去安裝apk,然而總是提示解析軟件包時出現問題。第一反應是從服務器下載的安裝包有問題,打開文件管理器,點擊下載後的安裝包。發現可以安裝,那就說明不是安裝包的問題。沒辦法看log吧。
03-27 18:15:16.005 1378 2744 W ActivityManager: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{f1ea483 11298:com.android.packageinstaller/u0a19} (pid=11298, uid=10019) that is not exported from UID 1000
03-27 18:15:16.006 11298 12569 W InstallStaging: Error staging apk from content URI
03-27 18:15:16.006 11298 12569 W InstallStaging: java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{f1ea483 11298:com.android.packageinstaller/u0a19} (pid=11298, uid=10019) that is not exported from UID 1000
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.os.Parcel.readException(Parcel.java:2013)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.os.Parcel.readException(Parcel.java:1959)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:4758)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.app.ActivityThread.acquireProvider(ActivityThread.java:5860)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2530)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1783)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1396)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1249)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.content.ContentResolver.openInputStream(ContentResolver.java:969)
03-27 18:15:16.006 11298 12569 W InstallStaging: at com.android.packageinstaller.InstallStaging$StagingAsyncTask.doInBackground(InstallStaging.java:180)
03-27 18:15:16.006 11298 12569 W InstallStaging: at com.android.packageinstaller.InstallStaging$StagingAsyncTask.doInBackground(InstallStaging.java:174)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.os.AsyncTask$2.call(AsyncTask.java:333)
03-27 18:15:16.006 11298 12569 W InstallStaging: at java.util.concurrent.FutureTask.run(FutureTask.java:266)
03-27 18:15:16.006 11298 12569 W InstallStaging: at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
03-27 18:15:16.006 11298 12569 W InstallStaging: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
03-27 18:15:16.006 11298 12569 W InstallStaging: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
03-27 18:15:16.006 11298 12569 W InstallStaging: at java.lang.Thread.run(Thread.java:764)
第一眼看到log時感覺是權限問題,因爲有Permission Denial:字樣。檢查了一遍讀寫外部存儲權限都開了啊,安裝未知應用權限也打開了。可還是不行,最後索性把selinux都給關了依舊不行。無限抓狂,總之無論怎麼做總會打出上面的log。在各種百度谷歌後還沒有解決。一臉無奈的繼續看log,看還有什麼有用信息沒。誒,突然發現了一行log:
ActivityManager: For security reasons, the system cannot issue a Uri permission grant to content://com.***.***.fileprovider/cmexternal/PDADownload/pda_client(4).apk [user 0]; use startActivityAsCaller() instead
最終在確認代碼沒問題的情況下,放大招,去源碼中找答案。搜一下“For security reasons, the system cannot issue a Uri permission grant to”這段log是哪裏打出來的。進到framework/base下grep.
./services/core/java/com/android/server/am/ActivityManagerService.java:9078: Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
十幾秒後還真給搜到了。打開AMS第9078行看看。
這段log是在int checkGrantUriPermissionLocked方法中打印的,看下代碼
// Bail early if system is trying to hand out permissions directly; it
// must always grant permissions on behalf of someone explicit.
final int callingAppId = UserHandle.getAppId(callingUid);
if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) {
// Exempted authority for cropping user photos in Settings app
} else {
Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
+ " grant to " + grantUri + "; use startActivityAsCaller() instead");
return -1;
}
}
如果你也被這個問題折磨的很痛苦,想必看到這段代碼就明白原因了吧。
這尼瑪被return -1了。這段代碼的意思就是如果你的應用是SYSTEM_UID或者ROOT_UID就不能用content://加fileprovider的Uri。只有settings可以用。
我們可以去看下settings裏面到底有沒有com.android.settings.files,這樣的provider。打開settings 的AndroidManifest文件。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.android.settings.files"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
你會發現settings真的有。這也太坑了吧,憑什麼只讓settings用,所以應該怎麼辦呢。
- 去掉聲明“android.uid.system”。
- 在AMS判斷settings的地方加上你自己的uri。類似這樣:
if ("com.android.settings.files".equals(grantUri.uri.getAuthority()) || "com.***.***.fileprovider".equals(grantUri.uri.getAuthority())) {
- 如果有人告訴你又不能去掉systemuid,又不能改framwork代碼。告訴那個人:來來來,你來,你nb你來搞!
哈哈,第三條開個玩笑。這個問題當你知道答案後,感覺很簡單,但是當你無論怎麼調試,怎麼百度谷歌都找不到原因時候,氣的都要拔頭髮時候,不要放棄,當實在是沒有辦法時候就去源碼裏找答案,我相信總會找到答案的。希望本文可以幫到你。