我們知道從android 6.0開始 系統(tǒng)開始對權(quán)限把控,7.0權(quán)限更是對系統(tǒng)權(quán)限進(jìn)一步更改?主要就是三個方面
APP應(yīng)用程序的私有文件不再向使用者放寬
Intent組件傳遞file://URI的方式可能給接收器留下無法訪問的路徑,觸發(fā)FileUriExposedException異常,推薦使用FileProvider
DownloadManager不再按文件名分享私人存儲的文件。舊版應(yīng)用在訪問COLUMN_LOCAL_FILENAME時可能出現(xiàn)無法訪問的路徑。面向 Android 7.0 或更高版本的應(yīng)用在嘗試訪問 COLUMN_LOCAL_FILENAME 時會觸發(fā) SecurityException
一、深入理解FileProvider
FileProvider屬于Android 7.0新增的一個類,該類位于v4包下,詳情可見android.support.v4.content.FileProvider,使用方法類似與ContentProvider,簡單概括為三個步驟,這里以調(diào)用sdcard公共目錄安裝app為例,演示使用過程:
在資源文件夾res/xml下新建file_provider.xml文件,文件聲明權(quán)限請求的路徑,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
? ? <paths>
? ? ? ? <external-path path="" name="download"/>
? ? </paths>
</resources>
在AndroidManifest.xml添加組件provider相關(guān)信息,類似組件activity,指定resource屬性引用上一步創(chuàng)建的xml文件(后面會詳細(xì)介紹各個屬性的用法),代碼如下:
<provider
? ? android:name="android.support.v4.content.FileProvider"
? ? android:authorities="com.wisdomclass.clus.fileprovider"
? ? android:grantUriPermissions="true"
? ? android:exported="false">
? ? <meta-data
? ? ? ? android:name="android.support.FILE_PROVIDER_PATHS"
? ? ? ? android:resource="@xml/provider_paths" />
</provider>
最后一步,Java代碼申請權(quán)限,使用新增的方法getUriForFile()和grantUriPermission(),代碼如下(后面會詳細(xì)介紹方法對應(yīng)參數(shù)的使用):
public void InstallApk(){
? ? String filePath = getExternalFilesDir("Download").getAbsolutePath() + File.separator+"軟件名稱"+mVersion+".apk";
? ? Intent install = new Intent(Intent.ACTION_VIEW);
? ? if(Build.VERSION.SDK_INT>=24) {//判讀版本是否在7.0以上
? ? ? ? Uri apkUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID +".fileprovider", new File(filePath));
? ? ? ? install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
? ? ? ? install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//添加這一句表示對目標(biāo)應(yīng)用臨時授權(quán)該Uri所代表的文件
? ? ? ? install.setDataAndType(apkUri, "application/vnd.android.package-archive");
? ? }else {
? ? ? ? install.setDataAndType(Uri.parse("file://"+filePath), "application/vnd.android.package-archive");
? ? ? ? install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
? ? }
? ? startActivity(install);
}
1.1 定義一個FileProvider
直接使用FileProvider本身或者它的子類,需要在AndroidManifest.xml文件中聲明組件的相關(guān)屬性,包括:
android:name,對應(yīng)屬性值:android.support.v4.content.FileProvider或者子類完整路徑
android:authorities,對應(yīng)屬性值是一個常量,通常定義的方式packagename.fileprovider,例如:cn.teachcourse.fileprovider
android:exported,對應(yīng)屬性值是一個boolean變量,設(shè)置為false
android:grantUriPermissions,對應(yīng)屬性值也是一個boolean變量,設(shè)置為true,允許獲得文件臨時的訪問權(quán)限
<manifest>
? ? ...
? ? <application>
? ? ? ? ...
? ? ? ? <provider
? ? ? ? ? ? android:name="android.support.v4.content.FileProvider"
? ? ? ? ? ? android:authorities="com.mydomain.fileprovider"
? ? ? ? ? ? android:exported="false"
? ? ? ? ? ? android:grantUriPermissions="true">
? ? ? ? ? ? ...
? ? ? ? </provider>
? ? ? ? ...
? ? </application>
</manifest>
想要關(guān)聯(lián)res/xml文件夾下創(chuàng)建的file_provider.xml文件,需要在<provider>標(biāo)簽內(nèi),添加<meta-data>子標(biāo)簽,設(shè)置<meta-data>標(biāo)簽的屬性值,包括:
android:name,對應(yīng)屬性值是一個固定的系統(tǒng)常量android.support.FILE_PROVIDER_PATHS
android:resource,對應(yīng)屬性值指向我們的xml文件@xml/file_provider
<provider
? ? android:name="android.support.v4.content.FileProvider"
? ? android:authorities="com.mydomain.fileprovider"
? ? android:exported="false"
? ? android:grantUriPermissions="true">
? ? <meta-data
? ? ? ? android:name="android.support.FILE_PROVIDER_PATHS"
? ? ? ? android:resource="@xml/file_provider" />
</provider>
1.2 指定授予臨時訪問權(quán)限的文件目錄
上一步說明了怎么定義一個FileProvider,這一步主要說明怎么定義一個@xml/file_provider文件。Android Studio或Eclipse開發(fā)工具創(chuàng)建Android項目的時候默認(rèn)不會創(chuàng)建res/xml文件夾,需要開發(fā)者手動創(chuàng)建,點擊res文件夾新建目錄,命名xml,如下圖:
然后,在xml文件夾下新建一個xml文件,文件命名file_provider.xml,指定根標(biāo)簽為paths,如下圖:
在xml文件中指定文件存儲的區(qū)塊和區(qū)塊的相對路徑,在<paths>根標(biāo)簽中添加<files-path>子標(biāo)簽(稍后詳細(xì)列出所有子標(biāo)簽),設(shè)置子標(biāo)簽的屬性值,包括:
name,是一個虛設(shè)的文件名(可以自由命名),對外可見路徑的一部分,隱藏真實文件目錄
path,是一個相對目錄,相對于當(dāng)前的子標(biāo)簽<files-path>根目錄
<files-path>,表示內(nèi)部內(nèi)存卡根目錄,對應(yīng)根目錄等價于Context.getFilesDir(),查看完整路徑:
/data/user/0/cn.teachcourse.demos/files
代碼如下:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
? ? <files-path name="my_images" path="images/"/>
? ? ...
</paths>
<paths>根標(biāo)簽下可以添加的子標(biāo)簽也是有限的,參考官網(wǎng)的開發(fā)文檔,除了上述的提到的<files-path>這個子標(biāo)簽外,還包括下面幾個:
<cache-path>,表示應(yīng)用默認(rèn)緩存根目錄,對應(yīng)根目錄等價于getCacheDir(),查看完整路徑:/data/user/0/cn.teachcourse.demos/cache
<external-path>,表示外部內(nèi)存卡根目錄,對應(yīng)根目錄等價于
Environment.getExternalStorageDirectory(),
查看完整路徑:/storage/emulated/0
<external-files-path>,表示外部內(nèi)存卡根目錄下的APP公共目錄,對應(yīng)根目錄等價于
Context#getExternalFilesDir(String) Context.getExternalFilesDir(null),
查看完整路徑:
/storage/emulated/0/Android/data/cn.teachcourse.demos/files/Download
<external-cache-path>,表示外部內(nèi)存卡根目錄下的APP緩存目錄,對應(yīng)根目錄等價于Context.getExternalCacheDir(),查看完整路徑:
/storage/emulated/0/Android/data/cn.teachcourse.demos/cache