原文鏈接:FileProvider
類概述
FileProvider是 ContentProvider 的一個特殊子類。通過對文件使用content://
Uri 替代file://
Uri,該類保證了應用之間共享文件的安全性。
一個content URI使用臨時訪問權限允許你授予讀寫訪問。當你在創建一個包含content URI 的 Intent 時,為了發送這個content URI給客戶端應用,你需要調用 Intent.setFlags() 來添加權限。對于一個客戶端應用來說,只要接收的 Activity 棧堆是存活的,權限就是可用的。對于一個發送到 Service 的 Intent 來說,只要 Service 是在運行的,權限也就是可用的。
相比之下,為了獲取file:///
Uri 類型的訪問控制,你不得不修改底層文件的文件系統權限。直到你再次修改之前,你提供的這個權限對于任何應用都是可用的,并且一直會產生作用。這種程度的訪問基本上是不安全的。
由a content URI提供的文件安全訪問機制使FileProvider
成為Android安全基礎框架的重要組成部分。
FileProvider
類主要包括以下幾個主題
- 定義一個
FileProvider
- 指定可用的文件
- 生成一個文件的Content URI
- 給一個URI授予臨時權限
- 將一個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授予臨時權限
- 對
content://
Uri 調用 [Context.grantUriPermission(package, Uri, mode_flags)](http://developer.android.com/reference/android/content/Context.html#grantUriPermission(java.lang.String, android.net.Uri, int)),使用希望的模式標志。這種方式根據mode_flags
授予制定的程序臨時訪問的權限。mode_flags
可以設定為 FLAG_GRANT_READ_URI_PERMISSION 或者 FLAG_GRANT_WRITE_URI_PERMISSION,或者兩者都有。這種授權一直會存在,直到你調用 [revokeUriPermission()](http://developer.android.com/reference/android/content/Context.html#revokeUriPermission(android.net.Uri, int)) 或者手機重啟。 - 調用 [setData]((http://developer.android.com/reference/android/content/Intent.html#setData(android.net.Uri))將content URI放入Intent 當中。然后調用 Intent.setFlags() 方法設置 FLAG_GRANT_READ_URI_PERMISSION 或者 FLAG_GRANT_WRITE_URI_PERMISSION 權限,或者兩者都設置。最后,給其他應用發送這個 Intent 。大多數情況下,你使用[setResult()](http://developer.android.com/reference/android/app/Activity.html#setResult(int, android.content.Intent)) 完成整個調用。
通過 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。當你調用 Intent 的 Intent.setFlags() 方法設置臨時訪問權限時,相同的權限會應用到所有的content URIs上。
注意: Intent.setClipData() 方法只在版本16 (Android 4.1) 以上的版本支持。如果你想兼容前面的版本,你應該每次使用 Intent 發送一個content URI。設置Intent 動作為 ACTION_SEND,然后調用 setData() 放入content URI中。
更多信息
關于FileProvider的更多信息,參考Android最佳實踐 通過URIs安全共享文件.