今天在使用小米手機系統7.0做升級的時候遇到一個奇怪的錯誤android.os.FileUriExposedException,開始以為是小米手機的坑,後來使用模擬器依舊如此,但使用低版本的手機就沒有問題,這是悲催,後來在官網上看到了原因,安全問題。
異常
android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com.android.screen/cache/apk/app_v_1.1.8.apk exposed beyond app through Intent.getData()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
at android.net.Uri.checkFileUriExposed(Uri.java:2346)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8949)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8908)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1519)
at android.app.Activity.startActivityForResult(Activity.java:4288)
at android.app.Activity.startActivityForResult(Activity.java:4247)
at android.app.Activity.startActivity(Activity.java:4571)
at android.app.Activity.startActivity(Activity.java:4539)
at com.fit.android.ui.SplashActivity$4.onDownloadFinished(SplashActivity.java:145)
at com.fit.android.net.internal.DownLoadTask.onPostExecute(DownLoadTask.java:160)
at com.fit.android.net.internal.DownLoadTask.onPostExecute(DownLoadTask.java:22)
at android.os.AsyncTask.finish(AsyncTask.java:660)
at android.os.AsyncTask.-wrap1(AsyncTask.java)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:677)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6114)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:874)
at com.andr複製程式碼
看到這樣的錯誤是不是一臉懵逼,跟通常問題很不同。
TIPS:一般遇到這樣的問題,直接在官網搜尋最直接
官方解釋
意思大概是:app內可以使用file://這種形式,7.0以後跨應用只能使用content://這種形式,並且要宣告許可權,安全問題(英語好的自行翻譯)。
解決方案
官方示例(自備梯子):developer.android.com/reference/a…
擴充套件
注意並不僅僅是安裝app需要這樣,想相簿、拍照跨應用的檔案共享都需要這樣
例項
1、在AndroidManifest.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/provider_paths"/>
</provider>複製程式碼
說明:
authorities:app的包名.fileProvider (fileProvider可以隨便寫),上面採用的是gradle的替換直接寫成包名也可以,但是推薦這種方式,多渠道分包的時候不用關心了
grantUriPermissions:必須是true,表示授予 URI 臨時訪問許可權
exported:必須是false
resource:中的@xml/file_paths是我們接下來要新增的檔案
2、在res目錄下新建一個xml資料夾,並且新建一個provider_paths的xml檔案(如下圖)
說明:
檔名隨意寫,跟第一步中resource中定義的一樣即可,
android:resource="@xml/provider_paths"/>複製程式碼
3、開啟file_paths.xml檔案新增如下內容
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>複製程式碼
說明:
external-path中,
name:可以隨意寫,只是一個名字
path:表示檔案路徑,.表示所有
paths下可以有一下節點:
<external-cache-path name="name" path="path" />對應Context.getExternalCacheDir()得到的目錄
<external-files-path name="name" path="path" />對應Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)
<external-path name="name" path="path" /> 對應Environment.getExternalStorageDirectory()
<cache-path name="name" path="path" />對應 getCacheDir()得到的目錄
<files-path name="name" path="path" />對應Context.getFilesDir() 目錄複製程式碼
根據檔案的位置選擇。
4、修改安裝對應的程式碼
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
// 宣告需要的零時許可權
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 第二個引數,即第一步中配置的authorities
Uri contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", apkFile);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
}
startActivity(intent);複製程式碼
說明:
低於7.0的版本還使用老的方式,而高於的要用新的獲取uri的方式。
其中authorities使用的是BuildeConfig獲取的包名,也可以自己寫全,但是推薦使用這種方式,萬一改了包名也不用關心了。