打开相机以及FileProvider适配
2019-06-06 本文已影响0人
边走边玩FREE
在打开相机获取图片时,大家最常用的是ACTION_IMAGE_CAPTURE,因为这个方法比较简单,直接打开系统相机,并且不需要获取相机权限,就能得到对应的图片。但自己再使用时,还是遇到了问题,因此在做总结一下。
文章目录:
- ACTION_IMAGE_GAPTURE说明
- 相机基本使用
- Bitmap和Uri转换
- Content Uri和File Uri相互转换(7.0适配)
- 总结
1. ACTION_IMAGE_GAPTURE基本使用
先看看官方说明:
image.png
通过上面的说明,可以得出两点:
- 通过该action打开相机时,若设置了 EXTRA_OUTPUT参数,则可以获得全尺寸的图片;若没有设置EXTRA_OUTPUT参数,则可以在onActivityResult中intent字段附加参数中获得缩小尺寸的图片。
- 若targetVersion大于等于23(6.0),若在Manifest文件中声明了Camera权限,但没有自己申请,则会引发SecurityException异常。
打开相机操作
方式一:设置EXTRA_OUTPUT
- 指定文件,打开相机代码如下:(具体见注释)
File outputImage = new File(getExternalCacheDir(), "test.jpg");
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
imageUri = Uri.fromFile(outputImage); //根据文件获取uri ,7.0以前处理 imageUri为全局
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); //无需相机权限
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri); //指定图片存放位置
startActivityForResult(intent,1);
- 在onActivityResult获取相机拍摄的相片,此时 data为null,代码如下:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
try {
//因为指定了文件位置,因此直接从指定uri中获取即可。
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
//获取bitmap后,可以设置给imageView等
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
方式二:没有设置EXTRA_OUTPUT
- 打开相机代码如下:
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); //无需相机权限
startActivityForResult(intent,1);
2.在onActivityResult获取相机拍摄的相片,代码如下:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
Bitmap bitmap = (Bitmap) bundle.get("data");
imageView.setImageBitmap(bitmap);
}
break;
default:
break;
}
}
Bitmap和Uri相互转换
没有设置写入路径时,我们通过intent附加字段获取图片,有时需要通过Bitmap来获取图片的Uri做相关操作,就可以通过一下方式处理。
- 通过Bitmap获取Uri,该Uri为 content:// 开始,并且需要文件系统读写权限
Uri uri = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, null,null));
- 通过Uri 获取Bitmap ,该Uri为 content:// 开始,并且需要文件系统读写权限
Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
详细说明可以参考官方文档:<u>https://developer.android.google.cn/reference/kotlin/android/provider/MediaStore.Images.Media.html#insertimage</u>
Content Uri和File Uri转换以及7.0适配
android 7.0 以上,需要通过Content Uri来访问手机文件系统位置,并且可以为Uri设置临时目录访问权限,供其他应用访问。因此为了兼容性,打开相机代码得做一定得修改。
- 添加功能函数,转换Uri
/**
* 7.0适配,获取 不同环境下的 sd卡的文件uri
* @param context
* @param file
* @return
*/
public Uri getUri(Context context, File file) {
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //7.0 以上 通过FileProvider获取文件路径
uri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file);
} else {
uri = Uri.fromFile(file); //7.0 以下
}
return uri;
}
- 然后修改打开相机代码:
File outputImage = new File(getExternalCacheDir(), "test.jpg");
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//imageUri = Uri.fromFile(outputImage); 7.0以下处理
imageUri = getUri(MainActivity.this, outputImage); //兼容7.0
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); //无需相机权限
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,1);
有了Content Uri 并不能完全满足我们的需求,有时需要获取文件的path来进行相关处理,因此需要把Content Uri转换成我们需要path。
在7.0 适配时,通过 FileProvider.getUriForFile,将File转换成Content Uri,其实FileProvider 也封装了 getFileForUri函数,只是没有对我们提供,因此我们可以通过反射,拿到该函数,满足我们的需求。具体如下:
//根据 传入的 Content Uri 来获取对应的 file path
public String getPathFromURi(Uri uri) {
String filePath = null;
List<PackageInfo> packs = this.getPackageManager().getInstalledPackages(PackageManager.GET_PROVIDERS);
if (packs != null) {
String fileProviderClassName = FileProvider.class.getName();
for (PackageInfo pack : packs) {
ProviderInfo[] providers = pack.providers;
if (providers != null) {
for (ProviderInfo provider : providers) {
if (uri.getAuthority().equals(provider.authority)) {
if (provider.name.equalsIgnoreCase(fileProviderClassName)) {
Class<FileProvider> fileProviderClass = FileProvider.class;
try {
Method getPathStrategy = fileProviderClass.getDeclaredMethod("getPathStrategy", Context.class, String.class);
getPathStrategy.setAccessible(true);
Object invoke = getPathStrategy.invoke(null, this, uri.getAuthority());
if (invoke != null) {
String PathStrategyStringClass = FileProvider.class.getName() + "$PathStrategy";
Class<?> PathStrategy = Class.forName(PathStrategyStringClass);
Method getFileForUri = PathStrategy.getDeclaredMethod("getFileForUri", Uri.class);
getFileForUri.setAccessible(true);
Object invoke1 = getFileForUri.invoke(invoke, uri);
if (invoke1 instanceof File) {
filePath = ((File) invoke1).getAbsolutePath();
return filePath;
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
break;
}
break;
}
}
}
}
}
return filePath;
}
以上只是提供了一种获取真实路径的方式,其他方式大家可以自己百度。
总结
- 通过该action打开相机时,若设置了 EXTRA_OUTPUT参数,则可以获得全尺寸的图片,但onActivityResult 函数的Intent字段为null;
若没有设置EXTRA_OUTPUT参数,则可以在onActivityResult中intent字段附加参数中获得缩小尺寸的图片
(Bitmap)(intent.getExtras().getData("data"))。 - 若targetVersion大于等于23(6.0),若在Manifest文件中声明了Camera权限,但没有自己申请,则会引发SecurityException异常。若没有在Manifest中声明Camera权限,则无需申请权限。 但项目开发时,会引入其他的包,难保其他包里面声明了Camear权限,因此为了兼容性,建议大家手动申请权限。
- android 7.0适配,主要将file:///转换成content://,为了对应不同的需求,可以通过FileProvider提供的方法,进行相互转化。(getFileForUri方法需要听过反射获取)