在Android7.0系统上,Android 框架强制执行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用系统相机拍照,或裁切照片。
若要在应用间共享文件,可以发送 content:// URI类型的Uri,并授予 URI 临时访问权限。 进行此授权的最简单方式是使用 FileProvider类。 如需有关权限和共享文件的更多信息,请参阅共享文件。
在Android7.0上调用系统相机拍照,裁切照片
在Android7.0之前,如果你想调用系统相机拍照可以通过以下代码来进行:
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
Uri imageUri = Uri.fromFile(file);
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI
startActivityForResult(intent,1006);
而在Android7.0上使用上述方式调用系统相拍照则会抛出如下异常:
android.os.FileUriExposedException: file:////storage/emulated/0/temp/1474956193735.jpg exposed beyond app through Intent.getData()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
at android.net.Uri.checkFileUriExposed(Uri.java:2346)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8933)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
at android.app.Activity.startActivityForResult(Activity.java:4223)
...
at android.app.Activity.startActivityForResult(Activity.java:4182)
什么原因呢?
这是由于Android7.0执行了“StrictMode API 政策禁”的原因,这时,我们就可以用FileProvider来解决这一问题。
使用FileProvider的大致步骤如下:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.your.package.name"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
provider>
android:authorities表示授权列表,当有多个授权时,用分号隔开。名字填写你的应用包名。
android:exported表示该内容提供器(ContentProvider)是否能被第三方程序组件使用。一般填false,为 true则会报安全异常。
android:grantUriPermissions这个是为是否对ContentProvider中的内容无访问权限的用户提供临时权限。true,表示授予 URI 临时访问权限。false,表示不授予。
< meta-data >中的android:resource资源文件的路径。
由一步可知,为了指定共享的目录我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,内容如下:
<resources>
<paths>
<external-path path="" name="camera_photos" />
paths>
resources>
备注:
< files-path/>代表的根目录为: Context.getFilesDir()
< external-path/>代表的根目录为: Environment.getExternalStorageDirectory()
< cache-path/>代表的根目录为: getCacheDir()
上述代码中path=”“,是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了,如果你将path设为path=”pictures”,
那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。
上述准备工作做完之后,现在我们就可以使用FileProvider了。
还是以调用系统相机拍照为例,我们需要将上述拍照代码修改为如下:
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
String authorities = "com.your.package.name";
//通过FileProvider创建一个content类型的Uri
Uri imageUri =
FileProvider.getUriForFile(context, authorities, file);
Intent intent = new Intent();
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI
startActivityForResult(intent,1006);
相比Android7.0之前的代码,上述代码中主要有以下两处改变:
因此,上述代码通过FileProvider的Uri getUriForFile (Context context, String authority, File file)
静态方法来获取Uri,该方法中authority参数就是清单文件中注册provider的android:authorities=”com.your.package.name”。
对Web服务器如tomcat,IIS比较熟悉的小伙伴,都只知道为了网站内容的安全和高效,Web服务器都支持为网站内容设置一个虚拟目录,其实FileProvider也有异曲同工之处。
将getUriForFile方法获取的Uri打印出来如下:
content://com.your.package.name/camera_photos/temp/1474960080319.jpg。
其中camera_photos就是res/xml/资源文件夹下定义的file_paths.xml中paths的name。
因为上述指定的path为path=”“,所以
content://com.your.package.name/camera_photos/代表的真实路径就是根目录,
即:/storage/emulated/0/。
因此
content://com.your.package.name/camera_photos/temp/1474960080319.jpg
代表的真实路径是:/storage/emulated/0/temp/1474960080319.jpg。
在Android7.0之前,你可以通过如下方法来裁切照片:
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
Uri outputUri = Uri.fromFile(file);
Uri imageUri=Uri.fromFile(new File("/storage/emulated/0/temp/1474960080319.jpg"));
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent,1008);
和拍照一样,上述代码在Android7.0上同样会引起android.os.FileUriExposedException异常,解决办法就是上文说说的使用FileProvider。
参照拍照,将上述代码改为如下即可:
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
Uri outputUri = FileProvider.getUriForFile(context, "com.your.package.name",file);
Uri imageUri=FileProvider.getUriForFile(context, "com.your.package.name", new File("/storage/emulated/0/temp/1474960080319.jpg");//通过FileProvider创建一个content类型的Uri
Intent intent = new Intent("com.android.camera.action.CROP");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent,1008);
另外,裁切照片推荐大家使用开源工具库TakePhoto,
TakePhoto是一款在Android设备上获取照片(拍照或从相册、文件中选择)、裁剪图片、压缩图片的开源工具库。大家可以拿来进行快速开发。