版權聲明:android
本帳號發佈文章均來自公衆號,承香墨影(cxmyDev),版權歸承香墨影全部。安全
未經容許,不得轉載。app
從 Android N(7.0) 開始,將嚴格執行 StrictMode 模式,也就是說,將對安全作更嚴格的校驗。而從 Android N 開始,將不容許在 App 間,使用 file://
的方式,傳遞一個 File ,否者會拋出 FileUriExposedException 的錯誤,會直接引起 Crash。ide
可是,既然官方對文件的分享作了一個這麼強硬的修改(直接拋出異常),實際上也提供瞭解決方案,那就是 FileProvider,經過 comtent://
的模式替換掉 file://
,同時,須要開發者主動升級 targetSdkVersion 到 24 纔會執行此策略,也留給了開發者升級的時間。code
有關 Android 爲了兼容而配置的 targetSdkVersion,不瞭解的能夠看看以前的文章《》。cdn
本文就 FileProvider 須要瞭解的全部細節,進行一個詳盡的說明。xml
FileProvider 是 Android support v4 包下,提供的一個 ContentProvider 的子類,用於向其餘 App 分享文件,而且是在 v4 包下,因此只要引入了 v4 包,就能夠作到全版本兼容。對象
既然 FileProvider 本質上就是一個 ContentProvider ,它其實也繼承了 ContentProvider 的特性。ContentProvider 其實就是在可控的範圍內,向外部其餘的 App 分享數據。而 FileProvider 將這樣的數據變成了一個 File 文件而已。blog
在 App 間對 file://
的分享作了嚴格的校驗以後,其實也是出於安全考慮,這就致使了,全部包含 file://
的URI 的 Intent 離開你的 App ,都受此限制。因此說,只要你的 App 內,經過一個 Intent 傳遞了一個 file://
的 Uri ,就須要當心使用了。繼承
在實際開發過程當中,使用最多的場景有一下幾個:
前面提到,FileProvider 其實是一個 ContentProvider ,因此若是須要使用它,就須要在 AndroidManifest.xml 中聲明它。
能夠看到 name 屬性就是標記當前 FileProvider 的實現類,對於一個 App Module 而言,若是隻是本身使用,能夠直接使用 v4 包下的 FileProvider ,可是若是是做爲一個 Lib Module 來供其餘項目使用,最好仍是從新空繼承一個 FileProvider ,這裏填寫咱們的繼承類便可。
在配置 Provider 的時候,還須要額外配置一個 <meta-data/>
標籤,它用於配置 FileProvider 支持分享出去的目錄。這個 <meta-data/>
標籤的 name 值是固定的,resource 須要指向一個 根節點爲 paths
的 xml 資源文件。
files-path
這些配置的信息,都是能夠在官方文檔中找到答案的,這裏直接以查閱源碼的方式來查看他們分別表明的意思。
這些配置,在 FileProvider 的源碼內,都是以一個個 TAG_Xxx 標記的。
注意,這裏 ContextCompat 只是對 Context 作了一個兼容處理,其實就是對 Api level 19 作了一個分解,分別表明不一樣的獲取方式,以 getExternalFilesDirs()
爲例。
配置工做已經所有完成,後面就須要將以前傳遞的 file://
替換成 FileProvider 須要的 content://
,這就須要用到 FileProvider.getUriForFile()
方法了,如下是它的完整簽名。
getUriForFile()
方法,須要一個
authority 的參數,這正是前面在 AndroidManifest.xml 中 配置的
android:authorities
。
調用此方法,會自動獲得一個 file://
轉換成 content://
的 一個 Uri 對象,能夠供咱們直接使用。
在配置 provider 標籤的時候,有一個屬性 android:grantUriPermissions="true"
,它表示容許它授予 Uri 臨時的權限。
當咱們生成出一個 content://
的 Uri 對象以後,其實也沒法對其直接使用,還須要對這個 Uri 接收的 App 賦予對應的權限才能夠。
受權類型的常量,被定義在 Intent 類中。
而這個受權的動做,提供了兩種方式來受權:
一、使用 Context.grantUriPermission()
爲其餘 App 授予 Uri 對象的訪問權限。
它的完整簽名以下:
grantUriPermission()
方法包含三個參數,這三個參數都很是的好理解。
這種狀況下,受權的有效期限,從受權一刻開始,截止於設備重啓或者手動調用 Context.revokeUriPermission()
方法,纔會收回對此 Uri 的受權。
二、配合 Intent.addFlags() 受權。
既然這是一個 Intent 的 Flag,Intent 也提供了另一種比較方便的受權方式,那就是使用 Intent.setFlags()
或者 Intent.addFlag
的方式。
這種方式相信你們都比較熟悉,就不細說了。而使用這種形式的受權,權限截止於該 App 所處的堆棧被銷燬。也就是說,一旦受權,直到該 App 被徹底退出,這段時間內,該 App 享有對此 Uri 指向的文件的對應權限,咱們沒法再主動收回此權限了。
雖然使用 Intent.addFlags()
的方式,一旦受權將沒法主動回收,可是大多數狀況下,也是會使用此種方式進行受權,除了操做起來方便以外,既然受權了也無需太擔憂對方會有破壞的行爲。有點切合 用人不疑,疑人不用 的道理。
擁有了受權權限的 content://
的 Uri 以後,就能夠經過 startXxx 或者 setResult() 的方式,將 Uri 傳遞給其餘的 App。
到這裏,基本上關於 FileProvider 的使用,都作了一個詳盡的說明,接下來舉個簡單的例子來看看如何使用它。
調起系統安裝器來安裝一個 Apk 。
在 AndroidManifest.xml 中配置 provider 的時候,須要保證 android:authorities
的值,在整個系統中的惟一性。其實這也很好理解,看了 FileProvider.getUriForFile()
以後,發現它是經過 android:authorities
屬性配置的值,來惟一肯定由誰來響應這個 provider 的,因此它須要保證在系統內惟一,否者安裝的時候會拋出異常。
FileProvider.getUriForFile()
的時候,只須要根據 applicationId 拼接一個 authorities 值便可,簡單修改一下上面調用系統去安裝 APK 的例子。
前面提到,若是不將 targatSdkVersion 升級到 24 的話,以前的方式依然是可用的,不會有 FileUriExposedException 的隱患。可是若是你的項目是做爲一個 Lib Module 這種 SDK 的形式發佈出去,供其餘人使用的話,這裏的 targetSdkVersion 就不受 Lib 的 targetSdkVersion 控制,而是主項目的 targetSdkVersion。
因此若是是以 SDK 的形式集成到別的 App 內使用的話,若是須要用發送一個 File 給其餘 App,必定要適配 FileProvider 。
FIleProvider 是存在於的 Support v4 包下,因此想要使用 FileProvider 就必須集成 v4 包。可是對於一個自己無需使用 v4 包的項目來講,爲了 FileProvider 來集成 v4 包,無形中就增長了安裝包的體積。
可是仔細看 FileProvider ,其實並無引用到什麼更多的 package ,而 FileProvider 本質上也只是一個 ContentProvider ,因此咱們只須要將它的代碼複製出來,簡單修改一下保證能夠正確運行,就可使用,而不是必須繼承 v4 包。
FileProvider 的核心就是提升安全性,讓開發者來限制本身本 App 的文件對外的訪問權限,以提升安全性。
因此在開發過程當中,只須要配合 FileProvider 將咱們可能須要第三方 App 用到的文件目錄加入到可受權的範圍,而後在發送 Intent 的時候,對其進行受權便可,其餘的操做和以前並沒有變化,這裏就不一一列舉了。