Android android.uid.system的應用調用安裝apk失敗

背景

最近項目開發過程中用到安裝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用,所以應該怎麼辦呢。

  1. 去掉聲明“android.uid.system”。
  2. 在AMS判斷settings的地方加上你自己的uri。類似這樣:
if ("com.android.settings.files".equals(grantUri.uri.getAuthority()) || "com.***.***.fileprovider".equals(grantUri.uri.getAuthority())) {
  1. 如果有人告訴你又不能去掉systemuid,又不能改framwork代碼。告訴那個人:來來來,你來,你nb你來搞!

哈哈,第三條開個玩笑。這個問題當你知道答案後,感覺很簡單,但是當你無論怎麼調試,怎麼百度谷歌都找不到原因時候,氣的都要拔頭髮時候,不要放棄,當實在是沒有辦法時候就去源碼裏找答案,我相信總會找到答案的。希望本文可以幫到你。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章