1. 程式人生 > >Android FileProvider 屬性配置詳解及FileProvider多節點問題

Android FileProvider 屬性配置詳解及FileProvider多節點問題

眾所周知在android7.0,修改了對私有儲存的限制,導致在獲取資源的時候,不能通過Uri.fromFile來獲取uri了我們需要適配7.0+的機型需要這樣寫:

1:程式碼適配

  if (Build.VERSION.SDK_INT > 23) {//
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                Uri contentUri = FileProvider.getUriForFile(context, SysInfo.packageName + ".fileProvider", outputFile);
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            } else {
                intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
            }

2:建立provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path name="beta_external_path" path="Download/"/>
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path name="beta_external_files_path" path="Android/data/"/>

</paths>

其中 provider_path屬性詳解

 name和path

name:uri路徑片段。為了執行安全,這個值隱藏你所共享的子目錄名。此值的子目錄名包含在路徑屬性中。
path:你所共享的子目錄。雖然name屬性是一個URI路徑片段,但是path是一個真實的子目錄名。注意,path是一個子目錄,而不是單個檔案或者多個檔案。
1.files-path
[html] view plain copy
  1. 代表與Context.getFileDir()相同的檔案路徑

2.cache-path

[html] view plain copy
  1. <cache-path name="name" path="path" />  

代表與getCacheDir()相同的檔案路徑


3.external-path

[html] view plain copy
  1. <external-path name="name" path="path" />  
代表與Environment.getExternalStorageDirectory()相同的檔案路徑

4.external-files-path

[html] view plain copy
  1. <external-files-path name="name" path="path" />  
代表與Context#getExternalFilesDir(String) 和Context.getExternalFilesDir(null)相同的檔案路徑

5.external-cache-path

[html] view plain copy
  1. <external-cache-path name="name" path="path" />  

代表與Context.getExternalCacheDir()相同的檔案路徑

6:配置AndroidManifest.xml

android:authorities在FileProvider中使用

[html]  view plain  copy
  1. <provider  
  2.     android:name="android.support.v4.content.FileProvider"  
  3.     android:authorities="com.mydomain.fileprovider"  
  4.     android:exported="false"  
  5.     android:grantUriPermissions="true">  
  6.     <meta-data  
  7.         android:name="android.support.FILE_PROVIDER_PATHS"  
  8.         android:resource="@xml/file_paths" />  
  9. </provider>  

7:使用FileProvider

*** 返回URI:content://com.mydomain.fileprovider/my_images/default_image.jpg.

[java]  view plain  copy
  1. File imagePath = new File(Context.getFilesDir(), "images");  
  2. File newFile = new File(imagePath, "default_image.jpg");  
  3. Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);  

8.自定義FileProvider


[java]  view plain  copy
  1. class MyFileProvider extends FileProvider {}  
  2. AndroidMenifest.xml中配置  android:authorities即可  

3:我們專案中可能會用到其他一些第三方sdk有用到拍照功能的話,他也為了適配android7.0也添加了這個節點,此時有些人可能就不知道如何下手了,其實很簡單我們只要重寫一個類 繼承自FileProvider,然後就按上述方法在新增一個節點就可以了:

<provider
    android:name="com.blueZhang.MyFileProvider"
    android:authorities="${applicationId}.provider"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/cust_file_paths" />
</provider>

如果你不想自定義FileProvider,那麼還有一種方法,那就是把第三方sdk中的路徑配置copy到provider_paths.xml即可。

如下所示:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path name="beta_external_path" path="Download/"/>
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path name="beta_external_files_path" path="Android/data/"/>

    <external-path name="external_storage_root" path="."/>
    <files-path name="files" path="."/>

</paths>

注意⚠️:在使用provider時 配置路徑 path="."代表所有路徑 


生成 Content URI

在 Android 7.0 出現之前,我們通常使用 Uri.fromFile() 方法生成一個 File URI。這裡,我們需要使用 FileProvider 類提供的公有靜態方法 getUriForFile 生成 Content URI。比如:

Uri contentUri = FileProvider.getUriForFile(this,
                BuildConfig.APPLICATION_ID + ".fileProvider", myFile);
  • 1
  • 2

需要傳遞三個引數。第二個引數便是 Manifest 檔案中註冊 FileProvider 時設定的 authorities 屬性值,第三個引數為要共享的檔案,並且這個檔案一定位於第二步我們在 path 檔案中新增的子目錄裡面。

舉個例子:

String filePath = Environment.getExternalStorageDirectory() + "/images/"+System.currentTimeMillis()+".jpg";
File outputFile = new File(filePath);
if (!outputFile.getParentFile().exists()) {
    outputFile.getParentFile().mkdir();
}
Uri contentUri = FileProvider.getUriForFile(this,
        BuildConfig.APPLICATION_ID + ".fileProvider", outputFile);
  • 生成的 Content URI 是這樣的:
content://com.yifeng.samples.myprovider/my_images/1493715330339.jpg

其中,構成 URI 的 host 部分為 <provider> 元素的 authorities 屬性值(applicationId + customname),path 片段 my_images 為 res/xml 檔案中指定的子目錄別名(真實目錄名為:images)。

第四步,授予 Content URI 訪問許可權

生成 Content URI 物件後,需要對其授權訪問許可權。授權方式有兩種:

第一種方式,使用 Context 提供的 grantUriPermission(package, Uri, mode_flags) 方法向其他應用授權訪問 URI 物件。三個引數分別表示授權訪問 URI 物件的其他應用包名,授權訪問的 Uri 物件,和授權型別。其中,授權型別為 Intent 類提供的讀寫型別常量:

  • FLAG_GRANT_READ_URI_PERMISSION

  • FLAG_GRANT_WRITE_URI_PERMISSION

或者二者同時授權。這種形式的授權方式,許可權有效期截止至發生裝置重啟或者手動呼叫 revokeUriPermission() 方法撤銷授權時。

第二種方式,配合 Intent 使用。通過 setData() 方法向 intent 物件新增 Content URI。然後使用 setFlags() 或者 addFlags() 方法設定讀寫許可權,可選常量值同上。這種形式的授權方式,許可權有效期截止至其它應用所處的堆疊銷燬,並且一旦授權給某一個元件後,該應用的其它元件擁有相同的訪問許可權。

第五步,提供 Content URI 給其它應用

擁有授予許可權的 Content URI 後,便可以通過 startActivity() 或者 setResult() 方法啟動其他應用並傳遞授權過的 Content URI 資料。當然,也有其他方式提供服務。

如果你需要一次性傳遞多個 URI 物件,可以使用 intent 物件提供的 setClipData() 方法,並且 setFlags() 方法設定的許可權適用於所有 Content URIs。

常見使用場景

前面介紹的內容都是理論部分,在 開發者官方 FileProvider 部分 都有所介紹。接下來我們看看,實際開發一款應用的過程中,會經常遇見哪些 FileProvider 的使用場景。

自動安裝檔案

版本更新完成時開啟新版本 apk 檔案實現自動安裝的功能,應該是最常見的使用場景,也是每個應用必備功能之一。常見操作為,通知欄顯示下載新版本完畢,使用者點選或者監聽下載過程自動開啟新版本 apk 檔案。適配 Android 7.0 版本之前,我們程式碼可能是這樣:

File apkFile = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_sample.apk");

Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
startActivity(installIntent)

現在為了適配 7.0 及以上版本的系統,必須使用 Content URI 代替 File URI。

在 res/xml 目錄下新建一個 file_provider_paths.xml 檔案(檔名自由定義),並新增子目錄路徑資訊:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">

    <external-files-path name="my_download" path="Download"/>

</paths>

然後在 Manifest 檔案中註冊 FileProvider 物件,並連結上面的 path 路徑檔案:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.yifeng.samples.myprovider"
    android:exported="false"
    android:grantUriPermissions="true">

    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_provider_paths"/>

</provider>

修改 java 程式碼,根據 File 物件生成 Content URI 物件,並授權訪問:

File apkFile = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_sample.apk");
Uri apkUri = FileProvider.getUriForFile(this,
        BuildConfig.APPLICATION_ID+".fileProvider", apkFile);

Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(installIntent);

好了 有不明白的 及時聯絡