轉(zhuǎn)載請(qǐng)注明出處:http://www.reibang.com/p/b566fa29a76e
本文出自Shawpoo的簡(jiǎn)書(shū)
我的博客:CSDN博客
前言
昨天在開(kāi)發(fā)的時(shí)候遇到這樣一個(gè)問(wèn)題瀑粥,在APP中更新版本下載完最新的apk之后沒(méi)有跳轉(zhuǎn)到應(yīng)用安裝頁(yè)面次泽。然后我換了個(gè)手機(jī)又進(jìn)行測(cè)試了一下是可以的,這就怪了匾委。我的代碼是這樣寫(xiě)的:
/**
* @param file
* @return
* @Description 安裝apk
*/
public void installApk(File file) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
打開(kāi)安裝頁(yè)面不就是這樣嗎?難道還有其他的方法廉嚼?正在趕項(xiàng)目的我遇到這個(gè)問(wèn)題真是一臉懵逼药磺,不知所措...
查看了Studio的loacat才發(fā)現(xiàn)拋了一個(gè)異常:
后來(lái)發(fā)現(xiàn)只有Android7.0的系統(tǒng)會(huì)報(bào)這個(gè)錯(cuò):
android.os.FileUriExposedException
為什么會(huì)這樣舟舒?難道是系統(tǒng)版本搞的鬼拉庶?沒(méi)錯(cuò),大胸弟秃励,你的猜想一點(diǎn)沒(méi)錯(cuò)氏仗,導(dǎo)致這個(gè)錯(cuò)誤就是由于Android7.0系統(tǒng)引起的。
查詢(xún)Android開(kāi)發(fā)官網(wǎng)可知:
了解:Android7.0 系統(tǒng)權(quán)限更改
為了提高私有文件的安全性夺鲜,面向 Android 7.0 或更高版本的應(yīng)用私有目錄被限制訪(fǎng)問(wèn) (0700)皆尔。此設(shè)置可防止私有文件的元數(shù)據(jù)泄漏呐舔,如它們的大小或存在性。此權(quán)限更改有多重副作用:
私有文件的文件權(quán)限不應(yīng)再由所有者放寬床佳,為使用 MODE_WORLD_READABLE
和/或 MODE_WORLD_WRITEABLE而進(jìn)行的此類(lèi)嘗試將觸發(fā) SecurityException滋早。傳遞軟件包網(wǎng)域外的 file:// URI 可能給接收器留下無(wú)法訪(fǎng)問(wèn)的路徑。因此砌们,嘗試傳遞 file://URI 會(huì)觸發(fā) FileUriExposedException杆麸。分享私有文件內(nèi)容的推薦方法是使用 FileProvider。
DownloadManager不再按文件名分享私人存儲(chǔ)的文件浪感。舊版應(yīng)用在訪(fǎng)問(wèn) COLUMN_LOCAL_FILENAME時(shí)可能出現(xiàn)無(wú)法訪(fǎng)問(wèn)的路徑昔头。面向 Android 7.0 或更高版本的應(yīng)用在嘗試訪(fǎng)問(wèn) COLUMN_LOCAL_FILENAME時(shí)會(huì)觸發(fā) SecurityException。通過(guò)使用 [DownloadManager.Request.setDestinationInExternalFilesDir()](https://developer.android.google.cn/reference/android/app/DownloadManager.Request.html#setDestinationInExternalFilesDir(android.content.Context, java.lang.String, java.lang.String)或 [DownloadManager.Request.setDestinationInExternalPublicDir()](https://developer.android.google.cn/reference/android/app/DownloadManager.Request.html#setDestinationInExternalPublicDir(java.lang.String, java.lang.String))將下載位置設(shè)置為公共位置的舊版應(yīng)用仍可以訪(fǎng)問(wèn) COLUMN_LOCAL_FILENAME中的路徑影兽,但是我們強(qiáng)烈反對(duì)使用這種方法揭斧。對(duì)于由 DownloadManager公開(kāi)的文件,首選的訪(fǎng)問(wèn)方式是使用[ContentResolver.openFileDescriptor()](https://developer.android.google.cn/reference/android/content/ContentResolver.html#openFileDescriptor(android.net.Uri, java.lang.String))峻堰。
上面三條更改總之就是讹开,對(duì)用戶(hù)私有的文件訪(fǎng)問(wèn)更加嚴(yán)格,在訪(fǎng)問(wèn)的時(shí)候需要使用特定的類(lèi)和方法捐名,而不像之前的系統(tǒng)那么容易旦万,雖然這種限制不能完全得到控制,但是官方強(qiáng)烈反對(duì)放寬私有目錄的權(quán)限镶蹋。
看到上面第二條成艘,貌似和安裝APK所報(bào)的異常一樣,根據(jù)提示贺归,看來(lái)需要使用FileProvider才能解決此異常的出現(xiàn)了淆两。
進(jìn)入正題:開(kāi)始解決異常
1、定義FileProvider
在Androidmanifest.xml文件中聲明:
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.shawpoo.app.fileprovider" //自定義名字 為避免重復(fù)建議設(shè)為:包名.fileprovider
android:exported="false"
android:grantUriPermissions="true">
...
</provider>
...
</application>
</manifest>
2拂酣、指定可用的文件路徑
在項(xiàng)目的res目錄下秋冰,創(chuàng)建xml文件夾,并新建一個(gè)file_paths.xml文件踱葛。通過(guò)這個(gè)文件來(lái)指定文件路徑:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/" />
...
</paths>
有多種指定路徑丹莲,在<paths>標(biāo)簽內(nèi)應(yīng)至少包含一種,或者多種尸诽。
- a甥材、表示應(yīng)用程序內(nèi)部存儲(chǔ)區(qū)中的文件/子目錄中的文件
<files-path name="name" path="image" />
等同于Context.getFileDir() : /data/data/com.xxx.app/files/image
- b、表示應(yīng)用程序內(nèi)部存儲(chǔ)區(qū)緩存子目錄中的文件
<cache-path name="name" path="image" />
等同于Context.getCacheDir() : /data/data/com.xxx.app/cache/image
- c性含、表示外部存儲(chǔ)區(qū)根目錄中的文件
<external-path name="name" path="image" />
等同于Environment.getExternalStorageDirectory() : /storage/emulated/image
- d洲赵、表示應(yīng)用程序外部存儲(chǔ)區(qū)根目錄中的文件
<external-files-path name="name" path="image" />
等同于Context.getExternalFilesDir(String) / Context.getExternalFilesDir(null) : /storage/emulated/0/Android/data/com.xxx.app/files/image
- e、表示應(yīng)用程序外部緩存區(qū)根目錄中的文件
<external-cache-path name="name" path="image" />
等同于Context.getExternalCacheDir() : /storage/emulated/0/Android/data/com.xxx.app/cache/image
3、引用指定的路徑
在剛才Androidmanifest.xml中聲明的provider進(jìn)行關(guān)聯(lián):
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.shawpoo.app.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
4叠萍、生成文件的Uri
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.shawpoo.app.fileprovider", newFile);
5芝发、給Uri授予臨時(shí)權(quán)限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1493228393690&di=14289b77b8b66ccd4831832f15e59df8&imgtype=0&src=http://img.nextcar.cn/picture/2016_04_16/e34cae3a6fe54db1eeab7097b8ab2505.jpg)
所以最終安裝apk的方法可以這么寫(xiě)了:
/**
* @param file
* @return
* @Description 安裝apk
*/
protected void installApk(File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 7.0+以上版本
Uri apkUri = FileProvider.getUriForFile(context, "com.shawpoo.app.fileprovider", file); //與manifest中定義的provider中的authorities="com.shawpoo.app.fileprovider"保持一致
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
context.startActivity(intent);
}
總結(jié):好多東西還是得多查API吶~雖然英文是硬傷...