Android工具介紹之文件提供者

原文鏈接:FileProvider

類概述

FileProvider是 ContentProvider 的一個特殊子類。通過對文件使用content:// Uri 替代file:// Uri,該類保證了應用之間共享文件的安全性。

一個content URI使用臨時訪問權限允許你授予讀寫訪問。當你在創建一個包含content URI 的 Intent 時,為了發送這個content URI給客戶端應用,你需要調用 Intent.setFlags() 來添加權限。對于一個客戶端應用來說,只要接收的 Activity 棧堆是存活的,權限就是可用的。對于一個發送到 ServiceIntent 來說,只要 Service 是在運行的,權限也就是可用的。

相比之下,為了獲取file:/// Uri 類型的訪問控制,你不得不修改底層文件的文件系統權限。直到你再次修改之前,你提供的這個權限對于任何應用都是可用的,并且一直會產生作用。這種程度的訪問基本上是不安全的。

由a content URI提供的文件安全訪問機制使FileProvider成為Android安全基礎框架的重要組成部分。

FileProvider類主要包括以下幾個主題

  1. 定義一個 FileProvider
  2. 指定可用的文件
  3. 生成一個文件的Content URI
  4. 給一個URI授予臨時權限
  5. 將一個URI傳輸給需要的應用

定義一個文件提供者

FileProvider 的默認功能已經包括了為文件生成content URI,因此你不需要再在代碼中定義一個子類來實現此功能。相反,你可以在你的應用中完全使用XML來加入一個 FileProvider。為了添加FileProvider,在你的應用的manifest文件中加入 <provider> 標簽。設置 android:name 屬性為 android.support.v4.content.FileProvider 。將android:authorities 屬性設置為基于你控制的域名的URI授權:例如,你控制的域名為 mydomain.com,你應該使用這個授權 com.mydomain.fileprovider。如果FileProvider 不需要是公開訪問,將 android:exported 屬性設置為 false。設置 android:grantUriPermissions 屬性為 true,允許你獲得這些文件的臨時訪問權限。例如:

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

注意: 需要在gradle中加入:

dependencies {    
   compile 'com.android.support:support-v4:23.3.0'
}

如果你想覆寫 FileProvider 方法中的任何默認行為,擴展 FileProvider 類,然后在 <provider>android:name 屬性中使用完全合格的類名。

指定可用的文件

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

這個 <paths> 元素必須包含一個或者以上的子元素。

<files-path name="name" path="path" />

files-path 代表你的應用內部存儲區域子目錄 files/ 下的文件。這個子目錄與 Context.getFilesDir() 獲取的目錄路徑相同。

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

代表你應用的外部存儲器的根路徑文件。這個路徑與通過 Context.getExternalFilesDir() 返回的路徑相同。

<cache-path name="name" path="path" />

代表了你應用內部存儲器區域緩存中的子路徑。這個路徑與 getCacheDir() 路徑相同。

這些子元素使用相同的屬性:

  • name="name"
    一個URI路徑段。為了保證執行安全,這個值隱藏了你需要分享的子目錄名稱。這個值的子目錄名稱包含在 path 屬性中。

  • path="path"
    你正在分享的子目錄名稱。注意這個值代表子目錄,而不僅僅是單獨的文件。你不能通過它的文件名來分享一個文件,也不能使用通配符來指定一個文件集。

對于每一個你希望使用content URIs訪問的包含文件的目錄,你必須指定 <paths> 對應的子元素。舉個例子,下面這個XML元素指定了兩個目錄:

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

在你的工程中加入 <paths> 和它的子元素。舉個例子:你能在 res/xml/file_paths.xml 文件中添加它們。為了將這個文件添加到 FileProvider 中,在 <provider> 的子元素中添加 <meta-data> 元素。設置 <meta-data> 元素的 android:name 屬性為 android.support.FILE_PROVIDER_PATHS 。設置該元素的 android:resource 的屬性為 @xml/file_paths (注意你不需要指定.xml擴展名)。例子如下:

<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_paths" />
</provider>

生成文件的Content URI

為了使用content URI方式給其他應用分享文件,你的應用必須生成Content URI。為了生成content URI,問這個文件創建一個新 File ,然后將 File 傳送給 [getUriForFile()](http://developer.android.com/reference/android/support/v4/content/FileProvider.html#getUriForFile(android.content.Context, java.lang.String, java.io.File))。你能將 [getUriForFile()](http://developer.android.com/reference/android/support/v4/content/FileProvider.html#getUriForFile(android.content.Context, java.lang.String, java.io.File)) 返回的content URI通過 Intent 發送給其他應用。收到這個content URI的客戶端調用[ContentResolver.openFileDescriptor](http://developer.android.com/reference/android/content/ContentResolver.html#openFileDescriptor(android.net.Uri, java.lang.String)) 得到一個 ParcelFileDescriptor 對象,使用這個對象該應用能夠打開這個文件并訪問它的內容。

例如,假設你的應用使用擁有 com.mydomain.fileprovider 授權的FileProvider向其他應用提供文件。獲取在你的內部存儲images/子目錄下的default_image.jpg圖片代碼如下:

File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);

[getUriForFile()](http://developer.android.com/reference/android/support/v4/content/FileProvider.html#getUriForFile(android.content.Context, java.lang.String, java.io.File)) 返回示例Uri如下:
content://com.mydomain.fileprovider/my_images/default_image.jpg

給一個URI授予臨時權限

通過 Intent 方式授權在 Activity 堆棧存在的時候會一直有用。當堆棧結束的時候,這個權限會自動移除。給一個應用中一個 Activity 授予的權限會自動擴展到這個應用的其他組件。

將一個URI傳輸給需要的應用

存在很多種不同的方式將一個文件的content URI傳輸給客戶端應用。對于一個客戶端應用來說,啟動你的應用的一種常見方式是調用 [startActivityResult()](http://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int, android.os.Bundle)),這將會發送一個 Intent 到你的應用開啟一個 Activity。相應的,你的應用能夠立即返回一個content URI到客戶端應用或者展示一個允許用戶選擇文件的用戶界面。在后一種情況下,一旦用戶選擇了某個文件,你的應用匯返回它與之對應的content URI。在這兩種情況下,你的應用會通過 Intent 的 [setResult()](http://developer.android.com/reference/android/app/Activity.html#setResult(int, android.content.Intent)) 方法返回這個content URI。

你也能將content URI放在 ClipData 對象中,然后將這個對象加入你要發送給客戶端應用的 Intent 中。為了完成這些,調用 Intent.setClipData()方法。當你使用這個方法時,你能加入多個 ClipData 對象到 Intent 中,并且每個 Intent 都可以有自己的content URI。當你調用 IntentIntent.setFlags() 方法設置臨時訪問權限時,相同的權限會應用到所有的content URIs上。

注意: Intent.setClipData() 方法只在版本16 (Android 4.1) 以上的版本支持。如果你想兼容前面的版本,你應該每次使用 Intent 發送一個content URI。設置Intent 動作為 ACTION_SEND,然后調用 setData() 放入content URI中。

更多信息

關于FileProvider的更多信息,參考Android最佳實踐 通過URIs安全共享文件.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,814評論 25 708
  • ¥開啟¥ 【iAPP實現進入界面執行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,497評論 0 17
  • Android N系列適配---FileProvider Android 7.0的適配,主要包含方面: Andro...
    25a58172fbb5閱讀 7,132評論 3 32
  • 其實一個人沒那么糟。無論是沒有戀人,還是沒有朋友。盡管有時會孤單,有時會無助,有時痛苦難以傾訴。但是這些都督促你成...
    奉孝大人閱讀 207評論 0 0
  • 葵花朝陽金霰空,飛崖墜泉若綢夢。當年頑劣猶記,陌上芳影,野曠紅蜻蜓。 三五片晚霞映火,六七棵棗樹聽風。而今蹉跎難...
    天外月明閱讀 159評論 0 0