Android開發中FileProvider的使用方法

Android開發中FileProvider的使用方法。

Android 7.0 在應用間共享文件

對於面向Android 7.0的應用,Android框架執行的StrictMode API政策禁止在您的應用外部公開file://URI。如果一項包含文件URI的intent離開您的應用,則應用出現故障,並出現FileUriExposedException異常。

解決方案:

要在應用間共享文件,您應發送一項content://URI,並授予URI臨時訪問權限。進行此授權的最簡單方式是使用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 指定有效的文件 從一個File得到一個對應的Content URI 對URI賦予臨時權限 分享這個URI給另一個App

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. 指定有效的文件

隻有事先指定瞭目錄,一個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

通常是通過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);
    }

You May Also Like