Android 一起來看看 7.0 的新特性 FileProvider

本篇文章已授權(quán)為微信公眾號 code小生 發(fā)布

一、前言


對于 Android 7.0,提供了非常多的變化,不過和我們開發(fā)者關(guān)聯(lián)最大的,或者說必須要適配的就是去除項(xiàng)目中傳遞 file:// 類似格式的 Uri 了。

對于面向 Android 7.0 的應(yīng)用,Android 框架執(zhí)行的 StrictMode API 政策禁止在應(yīng)用外部公開 file:// URI , 如果一項(xiàng)包含文件 URI 的 intent 離開應(yīng)用,則應(yīng)用出現(xiàn)故障,并出現(xiàn) FileUriExposedException 異常。

要應(yīng)用間共享文件,您應(yīng)發(fā)送一項(xiàng) content:// URI,并授予 URI 臨時(shí)訪問權(quán)限。進(jìn)行此授權(quán)的最簡單方式是使用 FileProvider 類。如需了解有關(guān)權(quán)限和共享文件的詳細(xì)信息,請參閱 共享文件

FileProvider 實(shí)際上是 ContentProvider 的一個子類,它的作用也比較明顯,file://Uri 不給用,那么換個 Uri 為 content:// 來替代。

二、Provider 使用詳解


1、定義 FileProvider

我們先在 AndroidManifest 中進(jìn)行注冊

<manifest>
    ...
    <application>
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.developerhaoz.androidtrainingdemo.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            ...
        </provider>
        ...
    </application>
</manifest>

為什么要申明呢?當(dāng)然是因?yàn)?FileProvider 是 ContentProvider 的子類啊。

2、指定可分享的文件路徑

FileProvider 只能為指定的目錄中的文件生成內(nèi)容 URI。要指定目錄,就必須使用 <paths> 元素的子元素在 XML 中指定其存儲區(qū)域和路徑。

我們先創(chuàng)建一個名為 res/xml/filepaths.xml 的新文件

filepaths.xml

filepaths.xml 文件中,便可以指定文件存儲的區(qū)域和路徑。例如,以下路徑元素告訴 FileProvider,你打算為私有文件區(qū)域的 images/ 子目錄 請求內(nèi)容 URI

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    ...
</paths>

<paths> 必須包含以下元素中一個或者多個子元素

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <root-path name="root" path="" />
    <files-path name="files" path="" />
    <cache-path name="cache" path="" />
    <external-path name="external" path="" />
    <external-files-path name="name" path="path" />
    <external-cache-path name="name" path="path" />
</paths>

paths 節(jié)點(diǎn)內(nèi)部支持以下幾個子節(jié)點(diǎn),分別為:

子節(jié)點(diǎn) 含義
<root-path> 代表設(shè)備的根目錄 new File("/")
<files-path> 代表 context.getFileDir()
<cache-path> 代表 context.getCacheDir()
<external-path> 代表 Environment.getExternalStorageDirectory()
<external-files-path> 代表 context.getExternalFilesDirs()
<external-cache-path> 代表 getExternalCacheDirs()

每個節(jié)點(diǎn)都使用兩個屬性:

  • name
  • path

path 即為代表目錄下的子目錄,例如:

<external-path name="external" path="pics"/>

代表的目錄即為:Environment.getExternalStorageDirectory()/pics

當(dāng)這么聲明以后,代碼可以使用你所聲明的當(dāng)前文件夾以及其子文件夾

可能你會有疑問,為什么要寫這么個 xml 文件,有啥用呢?

我們剛才說了,現(xiàn)在要使用 content://uri 替換 file://uri,那么,content://uri 如何定義呢?總不能使用文件路徑吧,那不是騙自己么

所以,需要一個虛擬的路徑對文件路徑進(jìn)行映射,所以需要編寫個 xml 文件,通過 path 以及 xml 節(jié)點(diǎn)確定可訪問的目錄,通過 name 屬性來映射真實(shí)的文件路徑

寫好 filepaths.xml 文件之后,要將此文件鏈接到 FileProvider 中,就必須添加一個 <meta-data> 元素作為定義 FileProvider<provider> 元素的子元素。將 <meta-data> 元素的 android : name 屬性設(shè)置為 android.support.FILE_PROVIDER_PATHS, 將元素的 "android : resource" 屬性設(shè)置為 @xml / filepaths (注意不要指定 .xml 拓展名)。例如:

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.developerhaoz.androidtrainingdemo.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths"
                />
        </provider>

3、使用 FileProvider 生成內(nèi)容 URI

配置工作已經(jīng)全部完成了,后面就需要將之前傳遞的 file:// 替換成 FileProvoider 需要用到的 content://,這就需要用到 FileProvider.getUriForFile() 方法了

    public static Uri getUriForFile(Context context, String authority, File file) {
        final PathStrategy strategy = getPathStrategy(context, authority);
        return strategy.getUriForFile(file);
    }

可以看到 getUriForFile(),需要傳入 一個
authority 的參數(shù),這正是我們前面在 AndroidManifest.xml 文件中配置的 android:authorities 參數(shù)

調(diào)用這個方法會自動得到一個 file:// 轉(zhuǎn)換成 content:// 的一個 Uri 對象,可以供我們直接使用

4、給 Uri 授予臨時(shí)權(quán)限

當(dāng)我們生成一個 content:// 的 Uri 對象之后,其實(shí)還無法對其直接使用,還需要對這個 Uri 接收的 App 賦予對應(yīng)的權(quán)限才可以。

這個授權(quán)的動作,提供了兩種方式來授權(quán):

① 通過 Context 的 grantUriPermission() 方法授權(quán)

Context 提供了兩個方法

  • grantUriPermission(String toPackage, Uri uri, int modeFlags)
  • revokeUriPermission(Uri uri, int modeFlags);

可以看到 grantUriPermission() 方法需要傳遞一個包名,就是你給哪個應(yīng)用授權(quán),但是很多時(shí)候,比如分享,我們并不知道最終用戶會選擇哪個 app,所以我們可以這樣:

List<ResolveInfo> resInfoList = context.getPackageManager()
            .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, flag);
}

根據(jù) Intent 查詢出所有符合的應(yīng)用,都給他們授權(quán),然后在不需要的時(shí)候通過 revokeUriPermission 移除權(quán)限。

② 配合 Intent.addFlags() 授權(quán)

既然這是一個 Intent 的 Flag,Intent 也提供了另外一種比較方便的授權(quán)方式,那就是使用 Intent.setFlags() 或者 Intent.addFlag 的方式

使用這種形式的授權(quán),權(quán)限截止于該 App 所處的堆棧被銷毀。也就是說,一旦授權(quán),直到該 App 被完全退出,這段時(shí)間內(nèi),該 App 享有對此 Uri 指向的文件的對應(yīng)權(quán)限,我們無法主動收回該權(quán)限了。

三、總結(jié)

Android 7.0 禁止在應(yīng)用外部公開 file:// URI,所以我們必須使用 content:// 替代 file://,這時(shí)主要需要 FileProvider 的支持,而因?yàn)?FileProvider 是 ContentProvider 的子類,所以需要在 AndroidManifest.xml 文件中進(jìn)行注冊,而又因?yàn)樾枰獙φ鎸?shí)的 filepath 進(jìn)行映射,所以需要編寫一個 xml 文檔,用于描述可使用的文件夾目錄,以及通過 name 去映射該文件夾目錄。

當(dāng)我們生成一個 content:// 的 Uri 對象之后,還需要對這個 Uri 接收的 App 賦予對應(yīng)的權(quán)限,到此本文的內(nèi)容就基本結(jié)束了。


參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容