Android開發中FileProvider的使用方法。
Android 7.0 在應用間共享文件
對於面向Android 7.0的應用,Android框架執行的StrictMode API政策禁止在您的應用外部公開file://URI。如果一項包含文件URI的intent離開您的應用,則應用出現故障,並出現
FileUriExposedException異常。
解決方案:
要在應用間共享文件,您應發送一項
content://URI,並授予URI臨時訪問權限。進行此授權的最簡單方式是使用FileProvider類。
FileProvider簡介
FileProvider簡介
FileProvider是ContentProvider的一個特殊的子類,它讓應用間共享文件變得更加容易,其通過創建一個Content URI來代替File URI。
一個Content URI 允許開發者可賦予一個臨時的讀或寫權限。當創建一個包含Content URI的Intent的時候,為瞭能夠讓另一個應用也可以使用這個URI,你需要調用
Intent.setFlags()來添加權限。隻要接收Activity的棧是活躍的,則客戶端應用就可以獲取到這些權限。如果該Intent是用來啟動一個Service,則隻要該Service是正在運行的,則也可以獲取到這些權限。
相比之下,如果想要通過File URI來控制權限,開發者必須修改底層文件系統的權限。這些權限會對任意的app開放,直到你修改瞭它。這種級別的訪問基本上是不安全的。
通過Content URI來提高文件訪問的安全性,使得FileProvider成為Android安全基礎設置的一個關鍵部分。
FileProvider的使用
FileProvider的使用
定義一個FileProvider 指定有效的文件 從一個File得到一個對應的Content URI 對URI賦予臨時權限 分享這個URI給另一個App
1. 定義一個FileProvider
1. 定義一個FileProvider
由於FileProvider中已經包含瞭為file生成Content URI的基本代碼瞭,所以開發者不必再去定義一個FileProvider的子類。你可以在XML文件中指定一個FileProvider:在manifest中使用
標簽來指定。
...
...
...
...
說明:
name的值一般都固定為
android.support.v4.content.FileProvider。如果開發者繼承瞭FileProvider,則可以寫上其絕對路徑。
authorities字段的值用來表明使用的使用者,在FileProvider的函數
getUriForFile需要傳入該參數。
exported 的值為false,表示該FileProvider隻能本應用使用,不是public的。
grantUriPermissions 的值為true,表示允許賦予臨時權限。
2. 指定有效的文件
2. 指定有效的文件
隻有事先指定瞭目錄,一個FileProvider才可以為文件生成一個對應的Content URI。要指定一個路徑,需要在XML文件中指定其存儲的路徑。使用
標簽。例如:
上邊的
paths 元素指定
/data/data//files/images/為共享的目錄。(file-path所對應的目錄與Context.getFilesDir()所對應,即
/data/data//files)。其中:name屬性表示在URI中的描述,path屬性表示文件實際存儲的位置。
裡邊的元素必須是一下的一個或者多個:
對應Context.getFilesDir() + “/path/”,即
/data/data//files/path/。
對應Context.getCacheDir() + “/path/”,即
/data/data//cache/path/。
對應Context.getExternalFilesDir(null) + “/path/”,即
/storage/emulated/0/Android/data//files/path/。
對應Context.getExternalCacheDir() + “/path/”,即
/storage/emulated/0/Android/data//cache/path/。
對應Environment.getExternalStorageDirectory() + “/path/”,即
/storage/emulated/0/path/。
這些paths裡邊有相同的子元素,即name和path。
name
這是URI的path。為瞭加強安全性,這個值隱藏瞭分享文件的子目錄,具體的文件真實路徑在path字段中保存。
A URI path segment. To enforce security, this value hides the name of the subdirectory you’re sharing. The subdirectory name for this value is contained in the path attribute.
path
分享文件的真實路徑。需要註意的是,這個值表示的是一個子目錄,不是一個具體的文件或者多個文件。開發者不能通過文件名來分享一個文件,也不能通過一個通配符來分享文件。
The subdirectory you’re sharing. While the name attribute is a URI path segment, the path value is an actual subdirectory name. Notice that the value refers to a subdirectory, not an inpidual file or files. You can’t share a single file by its file name, nor can you specify a subset of files using wildcards.
將paths裡邊的內容放在一個xml文件中,將該文件放在res/xml/目錄下,然後在manifest的provider標簽中指定它:
res/xml/file_paths.xml
AndroidManifest.xml
3. 從一個File得到一個對應的Content URI
File file = new File(mContext.getFilesDir() + "/text", "hello.txt");
Uri data;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
data = FileProvider.getUriForFile(mContext, "com.ysx.fileproviderserver.fileprovider", file);
} else {
data = Uri.fromFile(file);
}
需要註意的是,
文件的絕對路徑與第二步指定的文件目錄保持一致:(假設包名為com.ysx.fileproviderserver)。如上邊的代碼的文件的絕對路徑為
/data/data/com.ysx.fileproviderserver/files/text/hello.txt,對應paths中的內容為:
。 getUriForFile()的第二個參數是authority,與manifest文件中聲明的authorities保持一致。
這時候,我們得到的URI的串為:content://com.ysx.fileproviderserver.fileprovider/my_files/hello.txt。
4. 賦予臨時權限
兩種方法:(通常使用第2種)
Context.grantUriPermission(package, Uri, mode_flags) Intent.setFlags()
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
FLAG_GRANT_READ_URI_PERMISSION:表示讀取權限;
FLAG_GRANT_WRITE_URI_PERMISSION:表示寫入權限。
你可以同時或單獨使用這兩個權限,視你的需求而定。
5. 分享這個URI給另一個App
5. 分享這個URI給另一個App
通常是通過Intent來傳遞的。eg.
private void shareFile() {
Log.d(TAG, "shareFile: ");
Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.ysx.fileproviderclient",
"com.ysx.fileproviderclient.MainActivity");
intent.setComponent(componentName);
File file = new File(mContext.getFilesDir() + "/text", "hello.txt");
Uri data;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
data = FileProvider.getUriForFile(mContext, FILE_PROVIDER_AUTHORITIES, file);
// 給目標應用一個臨時授權
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
data = Uri.fromFile(file);
}
intent.setData(data);
startActivity(intent);
}
常見的使用場景:
常見的使用場景:
調用照相機,指定照片存儲路徑。 調用系統安裝器,傳遞apk文件。
調用照相機:
/**
* @param activity 當前activity
* @param authority FileProvider對應的authority
* @param file 拍照後照片存儲的文件
* @param requestCode 調用系統相機請求碼
*/
public static void takePicture(Activity activity, String authority, File file, int requestCode) {
Intent intent = new Intent();
Uri imageUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
imageUri = FileProvider.getUriForFile(activity, authority, file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
imageUri = Uri.fromFile(file);
}
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
activity.startActivityForResult(intent, requestCode);
}
調用安裝器:
/**
* 調用系統安裝器安裝apk
*
* @param context 上下文
* @param authority FileProvider對應的authority
* @param file apk文件
*/
public static void installApk(Context context, String authority, File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri data;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
data = FileProvider.getUriForFile(context, authority, file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
data = Uri.fromFile(file);
}
intent.setDataAndType(data, INSTALL_TYPE);
context.startActivity(intent);
}