打开相机以及FileProvider适配

在打开相机获取图片时,大家最常用的是ACTION_IMAGE_CAPTURE,因为这个方法比较简单,直接打开系统相机,并且不需要获取相机权限,就能得到对应的图片。但自己再使用时,还是遇到了问题,因此在做总结一下。

文章目录:

  • ACTION_IMAGE_GAPTURE说明
  • 相机基本使用
  • Bitmap和Uri转换
  • Content Uri和File Uri相互转换(7.0适配)
  • 总结

1. ACTION_IMAGE_GAPTURE基本使用

先看看官方说明:


image.png

通过上面的说明,可以得出两点:

  1. 通过该action打开相机时,若设置了 EXTRA_OUTPUT参数,则可以获得全尺寸的图片;若没有设置EXTRA_OUTPUT参数,则可以在onActivityResult中intent字段附加参数中获得缩小尺寸的图片。
  2. 若targetVersion大于等于23(6.0),若在Manifest文件中声明了Camera权限,但没有自己申请,则会引发SecurityException异常。

打开相机操作

方式一:设置EXTRA_OUTPUT
  1. 指定文件,打开相机代码如下:(具体见注释)
    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);
  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
  1. 打开相机代码如下:
 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);

详细说明可以参考官方文档:https://developer.android.google.cn/reference/kotlin/android/provider/MediaStore.Images.Media.html#insertimage

Content Uri和File Uri转换以及7.0适配

android 7.0 以上,需要通过Content Uri来访问手机文件系统位置,并且可以为Uri设置临时目录访问权限,供其他应用访问。因此为了兼容性,打开相机代码得做一定得修改。

  1. 添加功能函数,转换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;
    }
  1. 然后修改打开相机代码:
    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 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 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;
    }

以上只是提供了一种获取真实路径的方式,其他方式大家可以自己百度。

总结

  1. 通过该action打开相机时,若设置了 EXTRA_OUTPUT参数,则可以获得全尺寸的图片,但onActivityResult 函数的Intent字段为null;
    若没有设置EXTRA_OUTPUT参数,则可以在onActivityResult中intent字段附加参数中获得缩小尺寸的图片
    (Bitmap)(intent.getExtras().getData("data"))。
  2. 若targetVersion大于等于23(6.0),若在Manifest文件中声明了Camera权限,但没有自己申请,则会引发SecurityException异常。若没有在Manifest中声明Camera权限,则无需申请权限。 但项目开发时,会引入其他的包,难保其他包里面声明了Camear权限,因此为了兼容性,建议大家手动申请权限。
  3. android 7.0适配,主要将file:///转换成content://,为了对应不同的需求,可以通过FileProvider提供的方法,进行相互转化。(getFileForUri方法需要听过反射获取)

你可能感兴趣的:(打开相机以及FileProvider适配)