我想把 FileProvider 聊的更透徹一些

版權聲明:android

本帳號發佈文章均來自公衆號,承香墨影(cxmyDev),版權歸承香墨影全部。安全

未經容許,不得轉載。app

1、前言

從 Android N(7.0) 開始,將嚴格執行 StrictMode 模式,也就是說,將對安全作更嚴格的校驗。而從 Android N 開始,將不容許在 App 間,使用 file:// 的方式,傳遞一個 File ,否者會拋出 FileUriExposedException 的錯誤,會直接引起 Crash。ide

可是,既然官方對文件的分享作了一個這麼強硬的修改(直接拋出異常),實際上也提供瞭解決方案,那就是 FileProvider,經過 comtent:// 的模式替換掉 file:// ,同時,須要開發者主動升級 targetSdkVersion 到 24 纔會執行此策略,也留給了開發者升級的時間。code

有關 Android 爲了兼容而配置的 targetSdkVersion,不瞭解的能夠看看以前的文章《》。cdn

本文就 FileProvider 須要瞭解的全部細節,進行一個詳盡的說明。xml

2、如何使用 FileProvider

一、什麼是 FileProvider

FileProvider 是 Android support v4 包下,提供的一個 ContentProvider 的子類,用於向其餘 App 分享文件,而且是在 v4 包下,因此只要引入了 v4 包,就能夠作到全版本兼容。對象

既然 FileProvider 本質上就是一個 ContentProvider ,它其實也繼承了 ContentProvider 的特性。ContentProvider 其實就是在可控的範圍內,向外部其餘的 App 分享數據。而 FileProvider 將這樣的數據變成了一個 File 文件而已。blog

二、在什麼場景下須要使用 FileProvider

在 App 間對 file:// 的分享作了嚴格的校驗以後,其實也是出於安全考慮,這就致使了,全部包含 file:// 的URI 的 Intent 離開你的 App ,都受此限制。因此說,只要你的 App 內,經過一個 Intent 傳遞了一個 file:// 的 Uri ,就須要當心使用了。繼承

在實際開發過程當中,使用最多的場景有一下幾個:

  • 調用相機拍照。
  • 剪裁圖片。
  • 調用系統安裝器去安裝 Apk。

三、如何使用 FileProvider

1)在 AndroidManifest 中配置

前面提到,FileProvider 其實是一個 ContentProvider ,因此若是須要使用它,就須要在 AndroidManifest.xml 中聲明它。


能夠看到,provider 標籤下,配置了幾個屬性:

  • name :配置當前 FileProvider 的實現類。
  • authorities:配置一個 FileProvider 的名字,它在當前系統內須要是惟一值。
  • exported:表示該 FileProvider 是否須要公開出去,這裏不須要,因此是 false。
  • granUriPermissions:是否容許受權文件的臨時訪問權限。這裏須要,因此是 true。

能夠看到 name 屬性就是標記當前 FileProvider 的實現類,對於一個 App Module 而言,若是隻是本身使用,能夠直接使用 v4 包下的 FileProvider ,可是若是是做爲一個 Lib Module 來供其餘項目使用,最好仍是從新空繼承一個 FileProvider ,這裏填寫咱們的繼承類便可。

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

在配置 Provider 的時候,還須要額外配置一個 <meta-data/> 標籤,它用於配置 FileProvider 支持分享出去的目錄。這個 <meta-data/> 標籤的 name 值是固定的,resource 須要指向一個 根節點爲 paths 的 xml 資源文件。


而後就能夠對 provider_paths.xml 進行配置。


paths 標籤內,必須配置最少一個 xxx-path 標籤,上圖給出的例子,配置的是 files-path 這些配置的信息,都是能夠在官方文檔中找到答案的,這裏直接以查閱源碼的方式來查看他們分別表明的意思。

這些配置,在 FileProvider 的源碼內,都是以一個個 TAG_Xxx 標記的。


而他們分別表明的目錄,也能夠在源碼內找到答案。


能夠看到,不一樣的標籤,表明不一樣的目錄。

  • root-path:表示根目錄,『/』。
  • files-path:表示 content.getFileDir() 獲取到的目錄。
  • cache-path:表示 content.getCacheDir() 獲取到的目錄
  • external-path:表示Environment.getExternalStorageDirectory() 指向的目錄。
  • external-files-path:表示 ContextCompat.getExternalFilesDirs() 獲取到的目錄。
  • external-cache-path:表示 ContextCompat.getExternalCacheDirs() 獲取到的目錄。

注意,這裏 ContextCompat 只是對 Context 作了一個兼容處理,其實就是對 Api level 19 作了一個分解,分別表明不一樣的獲取方式,以 getExternalFilesDirs() 爲例。

3) 使用 content://

配置工做已經所有完成,後面就須要將以前傳遞的 file:// 替換成 FileProvider 須要的 content:// ,這就須要用到 FileProvider.getUriForFile() 方法了,如下是它的完整簽名。


getUriForFile() 方法,須要一個 authority 的參數,這正是前面在 AndroidManifest.xml 中 配置的 android:authorities

調用此方法,會自動獲得一個 file:// 轉換成 content:// 的 一個 Uri 對象,能夠供咱們直接使用。

4) 授予臨時的讀寫權限

在配置 provider 標籤的時候,有一個屬性 android:grantUriPermissions="true" ,它表示容許它授予 Uri 臨時的權限。

當咱們生成出一個 content:// 的 Uri 對象以後,其實也沒法對其直接使用,還須要對這個 Uri 接收的 App 賦予對應的權限才能夠。

受權類型的常量,被定義在 Intent 類中。


能夠看到,直接就是讀和寫的權限授予。

而這個受權的動做,提供了兩種方式來受權:

一、使用 Context.grantUriPermission() 爲其餘 App 授予 Uri 對象的訪問權限。

它的完整簽名以下:


grantUriPermission() 方法包含三個參數,這三個參數都很是的好理解。

  • toPackage :表示授予權限的 App 的包名。
  • uri:授予權限的 content:// 的 Uri。
  • modeFlags:前面提到的讀寫權限。

這種狀況下,受權的有效期限,從受權一刻開始,截止於設備重啓或者手動調用 Context.revokeUriPermission() 方法,纔會收回對此 Uri 的受權。

二、配合 Intent.addFlags() 受權。

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

這種方式相信你們都比較熟悉,就不細說了。而使用這種形式的受權,權限截止於該 App 所處的堆棧被銷燬。也就是說,一旦受權,直到該 App 被徹底退出,這段時間內,該 App 享有對此 Uri 指向的文件的對應權限,咱們沒法再主動收回此權限了。

雖然使用 Intent.addFlags() 的方式,一旦受權將沒法主動回收,可是大多數狀況下,也是會使用此種方式進行受權,除了操做起來方便以外,既然受權了也無需太擔憂對方會有破壞的行爲。有點切合 用人不疑,疑人不用 的道理。

擁有了受權權限的 content:// 的 Uri 以後,就能夠經過 startXxx 或者 setResult() 的方式,將 Uri 傳遞給其餘的 App。

5)舉個例子

到這裏,基本上關於 FileProvider 的使用,都作了一個詳盡的說明,接下來舉個簡單的例子來看看如何使用它。

調起系統安裝器來安裝一個 Apk 。

3、FileProvider 的注意事項

一、authorities 的惟一性

在 AndroidManifest.xml 中配置 provider 的時候,須要保證 android:authorities 的值,在整個系統中的惟一性。其實這也很好理解,看了 FileProvider.getUriForFile() 以後,發現它是經過 android:authorities 屬性配置的值,來惟一肯定由誰來響應這個 provider 的,因此它須要保證在系統內惟一,否者安裝的時候會拋出異常。


而在常規開發過程當中,若是是一個 App Module 在使用 FileProvider 的話,那麼只須要咱們本身規範不要寫同一個 authorities 便可。可是若是是做爲一個 Lib Module 發佈出去的話,是須要考慮使用者的如何使用的,因此爲了友好起見,最好使用 applicationId 來配置 provider 標籤。


這樣配置以後,就會使用 Gradle 中配置的 applicationId 的值替換這裏,而使用 FileProvider.getUriForFile() 的時候,只須要根據 applicationId 拼接一個 authorities 值便可,簡單修改一下上面調用系統去安裝 APK 的例子。

二、Lib 下的 targetSdkVersion

前面提到,若是不將 targatSdkVersion 升級到 24 的話,以前的方式依然是可用的,不會有 FileUriExposedException 的隱患。可是若是你的項目是做爲一個 Lib Module 這種 SDK 的形式發佈出去,供其餘人使用的話,這裏的 targetSdkVersion 就不受 Lib 的 targetSdkVersion 控制,而是主項目的 targetSdkVersion。

因此若是是以 SDK 的形式集成到別的 App 內使用的話,若是須要用發送一個 File 給其餘 App,必定要適配 FileProvider 。

三、不使用 v4 包

FIleProvider 是存在於的 Support v4 包下,因此想要使用 FileProvider 就必須集成 v4 包。可是對於一個自己無需使用 v4 包的項目來講,爲了 FileProvider 來集成 v4 包,無形中就增長了安裝包的體積。

可是仔細看 FileProvider ,其實並無引用到什麼更多的 package ,而 FileProvider 本質上也只是一個 ContentProvider ,因此咱們只須要將它的代碼複製出來,簡單修改一下保證能夠正確運行,就可使用,而不是必須繼承 v4 包。

4、小結

FileProvider 的核心就是提升安全性,讓開發者來限制本身本 App 的文件對外的訪問權限,以提升安全性。

因此在開發過程當中,只須要配合 FileProvider 將咱們可能須要第三方 App 用到的文件目錄加入到可受權的範圍,而後在發送 Intent 的時候,對其進行受權便可,其餘的操做和以前並沒有變化,這裏就不一一列舉了。

公衆號二維碼.jpg
公衆號二維碼.jpg
相關文章
相關標籤/搜索