安裝新版本的apk:android.os.FileUriExposedException
阿新 • • 發佈:2018-12-17
最近在做app更新升級功能的時候,又碰到因為android 7.0 引起的相容問題了。
android.os.FileUriExposedException: file:///storage/emulated/0/Download/appName-1.0.3.apk exposed beyond app through Intent.getData()
1. 原因:
- Android 7.0 以後,Google 移除掉了容易被濫用的“允許位置來源”應用的開關,取消了“允許未知來源”的檢查框,如果想安裝一些第三方商店的應用,需要手動開啟應用的“允許安裝未知來源程式”的許可權;
- 另外,7.0系統之後,Android不再允許在app中把file://Uri暴露給其他app(包括安裝app),包括但不侷限於通過Intent或ClipData 等方法。原因在於使用file://Uri會有一些風險,比如:
a. 檔案是私有的,接收file://Uri的app無法訪問該檔案。
b. 在Android6.0之後引入執行時許可權,如果接收file://Uri的app沒有申請
c. READ_EXTERNAL_STORAGE許可權,在讀取檔案時會引發崩潰。
因此,google提供了FileProvider,使用它可以生成content://Uri來替代file://Uri。
2. 解決方法
- 增加相應的Provider元件庫依賴:
implementation 'com.android.support:support-v4:26.1.0'
- 在AndroidManifest.xml檔案中配置Provider元件
<application> <provider android:name="android.support.v4.content.FileProvider" android:authorities="$application_id.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application>
註釋:
- 「authorities」:一般採用「包名」+".provider"的方式命名,避免與其他的Provider衝突;
- 「exported」:必須是「false」,否則會報錯的;
- 「grantUriPermission」: 「true」,表示授予 URI 臨時訪問許可權;
- 「meta-data」:設定需要授予URI許可權的檔案路徑。
- 在AndroidManifest.xml檔案增加相應的安裝以及檔案讀取許可權
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions"/>
- 增加xml資原始檔「provider_paths」
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_Files" path="."/>
</paths>
註釋:
「name」:uri路徑名稱,按照你的命名習慣,隨便起就可以。。。此值的子目錄名包含在路徑屬性中。
「path」:所共享的子目錄。注意是目錄,不是檔案!如“demo/apk”,“xxx”等,表示的是下的“demo/apk”,“xxx”子目錄。Attention:path=“.”表示所有子目錄。
1. 「external-path」:表示Environment.getExternalStorageDirectory()目錄
<external-path name="name" path="path" />
2. 「files-path」:表示Context.getFileDir()目錄
<files-path name="name" path="path" />
3. 「cache-path」:表示getCacheDir()目錄
<cache-path name="name" path="path" />
4. 「external-files-path」:表示Context#getExternalFilesDir(String) 和Context.getExternalFilesDir(null)目錄
<external-files-path name="name" path="path" />
5. 「external-cache-path」:表示Context.getExternalCacheDir()目錄
<external-cache-path name="name" path="path" />
- 最後,執行安裝過程
/**
* 安裝apk
*/
private fun installApk(apkFile: File) {
val intent = Intent()
//執行動作
intent.action = Intent.ACTION_VIEW
val apkUri: Uri?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //7.0以上版本,需要配置許可權才能安裝未知來源的程式:本程式碼的處理是使用FileProvider讀取Uri資源
//引數1-上下文, 引數2-Provider地址(與AndroidManifest.xml檔案中保持一致) 引數3-apk檔案
apkUri = FileProvider.getUriForFile(mContext, "demo.com.xxx.provider", apkFile)
//新增這一句表示對目標應用臨時授權該Uri所代表的檔案
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else {
apkUri = Uri.fromFile(apkFile)
}
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
mContext.startActivity(intent)
mContext.finish()
android.os.Process.killProcess(android.os.Process.myPid())
}
OK,解決完成!