在上篇文章了解到應用級文件只能被其所創建的應用程序所訪問,那么其他應用程序是不是就無論如何都無法訪問了呢?肯定不是的,只要文件經過其創建的應用程序授權,還是可以被其他應用程序所訪問的。這也就是應用級文件的共享。
系統只允許共享包含實際數據的純文件類型,而不推薦共享包含文件的目錄類型。
對於文件的訪問可以使用java.io.File系統文件類,但是如果想將該文件分享出去,則需要借助android.net.Uri路徑定位符類。Uri
類是Android系統下自定義的路徑定位符規則,其符合Java中定義的java.net.URI標准資源定位符規范,並新增了getPathSegments()
獲取分割路徑列表的方法和getQueryParameter(String key)
獲取指定的額外參數方法等。
通常思路是將要分享的File
文件對象轉換成特定的Uri
路徑定位符對象。在Android7即API 24版本及以后的版本中,系統對Uri
增加了授權處理,這將在后文詳細解釋。
之后將該Uri
對象可以作android.os.Bundle數據對象通過android.content.Intent意圖類傳遞。
最終在接收到意圖的應用程序中,可以收到Uri
路徑並對其做不同處理,即可訪問該路徑下的文件,針對Uri
中的內容不同,一般需要做不同的轉換處理,這將在后文詳細解釋。
文件分享方
目標版本級別小於24的應用程序
在Android7即API 24版本以前,應用程序內的任何File
對象,都可以通過靜態方法Uri.fromFile(File file)
將參數 file 直接轉換為Uri
對象。
得到的Uri
對象可直接操作賦值給Intent
意圖對象的setData(Uri data)
方法中的參數 data ,或putExtra(String key, Parcelable value)
方法中的參數 value,從而分享傳遞出去。
目標版本級別大於等於24的應用程序
從Android7即API 24版本開始,應用程序內部共享文件,仍然可以使用Uri.fromFile(File file)
方法,但是如果想在應用程序中對得到的Uri
對象直接傳遞給其他應用程序使用,會拋出android.os.FileUriExposedException異常。因此,在使用androidx依賴包的應用程序中可以借助androidx.core.content.FileProvider文件分享提供類,在使用support-v4依賴包的應用程序中可以借助android.support.v4.content.FileProvider
文件分享提供類,獲取相關Uri
對象,並為其授權,之后才可在不同應用程序間共享訪問。
對於FileProvider
的使用,要先做准備工作。
首先需要在應用程序的清單文件中注冊。在<application></application>
標簽中增加<provider></provider>
標簽。
在該標簽中指定屬性android:name="androidx.core.content.FileProvider"
以綁定文件分享提供類;
另外在該標簽中指定屬性android:authorities="xxx"
,這里的屬性值xxx
是應用程序所在系統內唯一的,其定義規則通常建議以應用程序包名為前綴的域名,且在后文代碼中有使用;
同時在該標簽中指定屬性android:grantUriPermissions="true"
,屬性值為 true 時,表示允許對得到的Uri
所定位的File
文件授予臨時讀寫權限。
最后在該標簽中增加<meta-data/>
標簽,以指定所使用的數據信息。在該數據標簽中,必須定義其屬性為android:name="android.support.FILE_PROVIDER_PATHS"
,同時定義屬性android:resource="@xml/file_paths"
,這里的屬性值@xml/file_paths
是指向定義的資源文件file_paths.xml。
在自定義的資源目錄 res 下創建子目錄 xml,該目錄中創建文件 file_paths.xml 。定義<paths></paths>
根標簽以指定要分享的文件路徑。在根標簽中可以根據要分享的文件所屬路徑不同而使用不同類型的標簽名作為子標簽插入,子標簽中包含有name
和path
兩個屬性,分別設置該路徑的別名和子目錄。其中子標簽可用類型與代碼中獲取路徑的對應關系可參考下表。
代碼獲取路徑 | 子標簽名 |
---|---|
context.getFilesDir() |
<files-path> |
context.getCacheDir() |
<cache-path> |
Environment.getExternalStorageDirectory() |
<external-path> |
context.getExternalMediaDirs() |
<external-media-path> |
context.getExternalFilesDir() |
<external-file-path> |
context.getExternalCacheDir() |
<external-cache-path> |
在清單文件中注冊FileProvider
之后,就可以在代碼獲取Uri
的地方,與低版本中獲取Uri
的方法Uri.fromFile(File file)
所不同的是,替換為FileProvider
的靜態方法getUriForFile(Context context, String authority, File file)
。參數 context 是當前應用程序所在的上下文環境;參數 authority 則是在清單文件中注冊FileProvider
時,屬性android:authorities
所表示的值;參數 file
依然是應用程序中要分享的文件對象。
在使用FileProvider.getUriForFile(Context context, String authority, File file)
獲取到Uri
之后,需要授權給要使用該路徑的應用程序,在能獲取到上下文環境Context
對象的地方,調用grantUriPermission (String toPackage, Uri uri, int modeFlags)
方法授權。參數 toPackage 即為要授權使用該路徑的應用程序所在包名;參數 uri 是上文獲取到的路徑定位符對象;參數 modeFlags 是用來表示權限類型的標志位,其值目前包括四種:Intent.FLAG_GRANT_READ_URI_PERMISSION=1
的讀權限,Intent.FLAG_GRANT_WRITE_URI_PERMISSION=2
的寫權限,Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION=4
表示長久授權,和Intent.FLAG_GRANT_PREFIX_URI_PERMISSION=8
對匹配參數 uri 的前綴的所有路徑定位符均授權。
在對Uri
對象授權之后,也就可以像老版本一樣將其封裝到Intent
意圖對象中發送給其他應用了。
文件接收方
一般地,在分享文件的接收方,如果只是對接收到的文件內容做讀寫處理,可以借助java.io.FileDescriptor文件描述類,使用官方推薦的簡單方式操作。
在接收方收到的Intent
意圖對象調用getData()
方法,得到要接收的文件所對應的Uri
路徑定位符對象。
在可以使用上下文環境Context
對象的位置,調用其getContentResolver()
方法,獲取上下文環境中的ContentResolver
內容解析類對象。
借助ContentResolver
對象的openFileDescriptor (Uri uri, String mode)
方法,返回android.os.ParcelFileDescriptor系統封裝的文件描述類對象。其中參數 uri 就是上文接收到的路徑定位符對象;參數 mode 則標記了文件的打開方式,包括只讀權限"r",只寫權限"w",追加寫入權限"wa",讀寫權限"rw"等。
在得到ParcelFileDescriptor
對象后,調用其getFileDescriptor()
方法,可以獲得FileDescriptor
文件描述對象。
最終通過得到的文件描述對象,創建FileInputStream(FileDescriptor fdObj)
構造方法或FileOutputStream(FileDescriptor fdObj)
構造方法獲取文件輸入流對象或文件輸出流對象,以此操作文件內容。
還有些特殊需求,對於分享文件接收方來說,是要對收到的文件獲取其所在目錄並做進一步操作,也就是需要對得到的Uri
路徑定位符對象轉換為File
文件對象。這方面官方並沒有像上文那樣簡單統一的調用方式,而需要開發者針對不同的版本做單獨處理。
目標版本級別小於29的應用程序
在Android10即API 29之前的版本中,網上有一堆代碼可參考,其思路就是查詢系統數據庫中不同媒體文件是否與Uri
對象所指向位置匹配,進而確定該Uri
對象指向的媒體文件目錄,在應用程序內借助上下文環境獲取其媒體文件所在目錄File
對象,從而匹配要查詢的Uri
路徑定位符對象。
目標版本級別大於等於29的應用程序
從Android10及API 29版本開始,由於應用程序僅可訪問自己分區存儲內部的文件,對於收到的分享文件路徑定位符Uri
對象,也只能先使用上文官方推薦的讀取文件方式,將Uri
對象所指向的文件先保存一份到自己應用程序內部的私有目錄下,再將這份私有目錄下的保存文件轉為File
對象使用。