官方文档说:
对于面向 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