據昨天作app更新升級功能的時候補充一下由於android 7.0 引發的兼容問題了。(可是一個奇怪的緣由是一個項目兼容,一個項目不兼容,正在找緣由中。。。。)android
android.os.FileUriExposedException: file:///storage/emulated/0/Download/appName-1.0.3.apk exposed beyond app through Intent.getData()
複製代碼
1. 緣由:bash
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權限,在讀取文件時會引起崩潰。app
所以,google提供了FileProvider,使用它能夠生成content://Uri來替代file://Uri。ide
2. 解決方法ui
增長相應的Provider組件庫依賴:google
implementation 'com.android.support:support-v4:26.1.0'
複製代碼
在AndroidManifest.xml文件中配置Provider組件spa
<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>
複製代碼
註釋:code
「authorities」:通常採用「包名」+".provider"的方式命名,避免與其餘的Provider衝突;xml
「exported」:必須是「false」,不然會報錯的;ip
「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=「.」表示全部子目錄。
<external-path name="name" path="path" />
複製代碼
<files-path name="name" path="path" />
複製代碼
<cache-path name="name" path="path" />
複製代碼
<external-files-path name="name" path="path" />
複製代碼
<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,解決完成!