Android7.0行為變更:適配File Provider

TheShy_發表於2018-04-14
兩個小解釋:

FileProvider是ContentProvider特殊的子類,ContentProvider通過建立content:// Uri來替代file:/// Uri。

在Android 7.0的以上的系統中,嘗試傳遞file://URI可能會觸發FileUriExposedException
複製程式碼

FileProvider的這個概述包括以下主題:

1.定義FileProvider

2.指定可用檔案

3.檢索檔案的Content URI

4.授予URI的臨時許可權

5.將內容URI提供給其他應用程式
複製程式碼

第一步:定義FileProvider:

//清單檔案中
 <provider
        android:name="android.support.v4.content.FileProvider"//固定
        android:authorities="${applicationId}.yourname"//根據您控制的域將屬性設定為URI許可權
        android:exported="false"//FileProvider不需要公開
        android:grantUriPermissions="true">//允許您授予對檔案的臨時訪問許可權
        ...
</provider>
複製程式碼

第二步:指定可用檔案

//新建一個xml檔案用於存放應用需要共享的目錄檔案
//以下paths元素告訴FileProvider您打算為images/私有檔案區域的子目錄請求內容URI
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    ...
</paths>
複製程式碼

該元素必須包含一個或多個以下子元素:

//代表內部儲存空間應用私有目錄下的 files/ 目錄,等同於 Context.getFilesDir() 所獲取的目錄路徑;
<files-path name = “ name ” path = “ path ” />   
複製程式碼
//代表內部儲存空間應用私有目錄下的 cache/ 目錄,等同於 Context.getCacheDir() 所獲取的目錄路徑;
<cache-path name = “ name ” path = “ path ” />  
複製程式碼
//代表外部儲存空間根目錄,等同於 Environment.getExternalStorageDirectory() 所獲取的目錄路徑;
<external-path name = “ name ” path = “ path ” />   
複製程式碼
//代表外部儲存空間應用私有目錄下的 files/ 目錄,等同於 Context.getExternalFilesDir(null) 所獲取的目錄路徑;
<external-files-path name = “ name ” path = “ path ” />  
複製程式碼
//代表外部儲存空間應用私有目錄下的 cache/ 目錄,等同於 Context.getExternalCacheDir();
<external-cache-path name = “ name ” path = “ path ” />  
複製程式碼
//代表外部媒體區域根目錄中的檔案。等同於Context.getExternalMediaDirs()。
<external-media-path name = “ name ” path = “ path ” /> 
複製程式碼

這些子元素都使用兩個相同的屬性:

name="name"
一個URI路徑段。 用於給 path 屬性所指定的子目錄名稱取一個別名 為了提高安全性,此值將隱藏您要共享的子目錄的名稱。該值的子目錄名稱包含在該 path屬性中。
複製程式碼

path="path"
你正在分享的子目錄。雖然該name屬性是一個URI路徑段,但該path值是實際的子目錄名稱。請注意,該值是指一個子目錄,而不是獨立檔名。您無法通過檔名共享單個檔案,也無法使用萬用字元指定檔案的子集。 
複製程式碼

第三步:檢索檔案的 Content URI

//使用 FileProvider 類提供的公有靜態方法 getUriForFile 生成 Content URI
//第一個引數:context上下文
//第二個引數: Manifest 檔案中註冊 FileProvider 時設定的 authorities 屬性值
//第三個引數:要共享的檔案,並且這個檔案一定位於第二步我們在 path 檔案中新增的子目錄裡面
Uri contentUri = FileProvider.getUriForFile(this,
     BuildConfig.APPLICATION_ID + ".myprovider", myFile);
複製程式碼

第四步:授予URI的臨時許可權

授權方式有兩種:

第一種方式:

//呼叫方法:
//引數1:授權訪問 URI 物件的其他應用包名
//引數2:授權訪問的 Uri 物件
//引數3:授權型別FLAG_GRANT_READ_URI_PERMISSION 或者 FLAG_GRANT_WRITE_URI_PERMISSION
       (或者二者同時授權。這種形式的授權方式,許可權有效期截止至發生裝置重啟或者手動呼叫 revokeUriPermission() 方法撤銷授權時)
grantUriPermission(package, Uri, mode_flags) 
複製程式碼

第二種方式:

//配合intent使用
//許可權有效期截止至其它應用所處的堆疊銷燬,並且一旦授權給某一個元件後,該應用的其它元件擁有相同的訪問許可權。
Intent.setFlags() 或者 Intent.addFlags()
Intent.setData(Uri uri);
複製程式碼

第五步:將內容URI提供給其他應用程式

//通過以下方法啟動其他應用並傳遞授權過的 Content URI 資料。當然,也有其他方式提供服務。
startActivity() 
或者 
startActivityResult()
或者
setResult() 
複製程式碼

官方原文(需要自備梯子,想自己搭的教程點選這裡): Google Develpers - FileProvider


以下是一個我這邊的例子:

場景:版本更新完成時開啟新版本 apk 檔案實現自動安裝
複製程式碼
//在 res/xml 目錄下新建一個filepath檔案 並指定子目錄路徑資訊
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_path" path="."/>
    <cache-path name="cache_path" path="."/>
</paths>
複製程式碼
//Manifest 檔案中註冊 FileProvider 物件,並連結上面的 path 路徑檔案
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.xxx.FileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepath"/>
</provider>
複製程式碼
//授權 開啟安裝管理器安裝apk包
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addCategory("android.intent.category.DEFAULT");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Uri uri = UriUtil.getUriForFile(BitZApplication.mContext.get(), new File((String) msg.obj));
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(intent);
複製程式碼
//UriUtil工具類:
public static Uri getUriForFile(Context context, File file) {
        if (context == null || file == null) {
            throw new NullPointerException();
        }
        Uri uri;
        if (Build.VERSION.SDK_INT >= 24) {
            uri = FileProvider.getUriForFile(context, "com.xxx.FileProvider", file);
        } else {
            uri = Uri.fromFile(file);
        }
        return uri;
    }
複製程式碼

相關文章