官方文檔說:
對于面向 Android 7.0 的應用,Android 架構執行的 StrictMode API 政策禁止在您的應用外部公開 file:// URI。如果一項包含檔案 URI 的 intent 離開您的應用,則應用出現故障,并出現 FileUriExposedException 異常。
要在應用間共享檔案,您應發送一項 content:// URI,并授予 URI 臨時通路權限。進行此授權的最簡單方式是使用 FileProvider 類。
在所有情況下,要将應用中的檔案提供給其他應用,唯一安全的做法就是向接收方應用發送檔案的内容 URI,并授予對該 URI 的臨時通路權限。具有臨時 URI 通路權限的内容 URI 之是以安全,是因為它們僅供接收該 URI 的應用使用,并且會自動過期。Android FileProvider 元件提供了 getUriForFile() 方法,用于生成檔案的内容 URI。
使用FileProvider
1、在清單中聲明provider标簽,如下:
由于FileProvider的預設功能包括檔案的内容URI生成,是以不需要在代碼中定義子類。相反,您可以在應用程式中包含一個檔案提供程式,方法是完全用XML指定它。要指定FileProvider元件本身,請在應用程式清單中添加< provider>元素。
将android:name屬性設定為androidx.core.content.FileProvider。
将android:authorities屬性設定為基于您控制的域的URI權限;例如,如果您控制域mydomain.com,則應使用authority com.mydomain.fileprovider。
将android:exported屬性設定為false;檔案提供程式不需要是公共的。
将android:grantUriPermissions屬性設定為true,以允許您授予對檔案的臨時通路權限
<provider
android:authorities="com.example.broadcast_force_offline.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
//android:authorities表示授權者,這裡的格式一般是[appId].fileprovider
//android:exported隻能為false,檔案提供程式不需要是公共的
//android:grantUriPermissions="true"表示授權Uri權限 ,且必須為true
meta-data裡設定指定的檔案目錄,為引用某個xml檔案,格式如下:
< meta-data >
直譯為“中繼資料”,該标簽可為< activity>、< activity-alias>、< application>、< provider>、< receiver>、< service>等元件提供附加資料項。
元件元素可以包含任意數量的< meta-data >子元素。系統将meta-data配置的資料存儲于一個Bundle對象中,可以通過PackageItemInfo.metaData字段擷取。
文法配置:
1、android:name 配置設定給該标簽的鍵,即唯一名稱。
2、android:resource 對資源的引用,如“@string/app_name”。該資源ID可以通過該metaData.getInt()方法擷取 。
3、android:value 配置設定給該标簽的值,如String、Boolean等。
例如:file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="my_images"
path=""/>
<!--path屬性的值表示共享的的具體路徑,為空代表将整個SD卡共享-->
</paths>
< root-path/> 代表裝置的根目錄new File("/");
< files-path/> 代表context.getFilesDir()
< cache-path/> 代表context.getCacheDir()
< external-path/> 代表Environment.getExternalStorageDirectory()
< external-files-path>代表context.getExternalFilesDirs()
< external-cache-path>代表getExternalCacheDirs()
參考代碼案例:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--存儲卡的Pictures目錄放圖檔檔案-->
<external-path
name="my_images"
path="Pictures"/>
<!--存儲卡的flyaudiosmart2019/apk目錄放安裝封包件-->
<external-path
name="my_download"
path="flyaudiosmart2019/apk"/>
</paths>
2、使用FileProvider API
調用系統拍照,構造Intent就需要傳入一個Uri,那麼Uri就必須使用FileProvider來擷取:
Uri imageUri;
protected void onCreate(Bundle savedInstanceState){
......
takePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//建立File對象,用于存儲拍照後的照片,将圖檔命名為output_image.jpg,并将它存放在目前應用緩存資料的位置,4.4系統以前通路SD卡關聯目錄需要聲明通路SD卡權限
File outputImage=new File(getExternalCacheDir(),"output_image.jpg");
try {
if (outputImage.exists()){
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT>=24)
{
//生成Uri
imageUri= FileProvider.getUriForFile(CameraAlbumActivity.this,"com.example.broadcast_force_offline.fileprovider",outputImage);
}
else
{
imageUri=Uri.fromFile(outputImage);
}
//啟動相機程式
Intent intent=new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
//調用putExtra()方法指定圖檔的輸出位址
startActivityForResult(intent,1);
}
});
......
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode)
{
case 1:
if(resultCode==RESULT_OK)
{
try {
//将照片顯示出來
Bitmap bitmap= BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
這裡在ImageView裡直接顯示Bitmap。如果需要上傳圖檔到伺服器,需要把圖檔儲存為臨時檔案。建立檔案需要聲明對外部存儲的寫權限
參考資料:
Android适配總結之FileProvider
第一行代碼(第二版)
淺談Android中的meta-data及其應用
官方文檔:FileProvider