• 关于Android 7.0相机FileUriExposedException解决


    在开发Android项目的时候,我们会用到相机,有些时候只是开发一个普通的扫码,仅仅赋予一下 权限 就好了,但是有些时候是需要拍照和从相册中获取照片的。

    我们在Android 5.0以及5.0之前调用相机可以这样写

    Intent intent = new Intent();
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
    File savePhoto = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"/test/"+System.currentTimeMillis() + ".png");
    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(savePhoto));
    startActivityForResult(intent,200);

    这样写在6.0之前是完全没有问题的,拍照也可以按照指定的路径进行存储,一切的一切都是OK的,除非部分机型会有问题

    到了6.0,在调用相机就得这样写
    当我们开发者把sdk升级到了23后,这样写就会存在一点缺陷。那就是权限管理,需要动态进行获取了。OK,然后我们改成动态获取的,如下:

    if(checkSelfPermission(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
        requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},200);
        return;
    }
    Intent intent = new Intent();
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
    File savePhoto = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"/test/"+System.currentTimeMillis() + ".png");
    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(savePhoto));
    startActivityForResult(intent,200);

    这样写和5.0几乎没有区别,只是在前边加了一个权限校验。因为6.0到了动态权限时代

    然而到了7.0,一切的一切都不一样了,因为Android 7.0不允许intent带有file://的URI离开自身的应用了,要不然会抛出FileUriExposedException

    想要在自己应用和其他应用之间共享File数据,只能使用content://的方式
    所以我们在想按照5.0和6.0的方式去调用相机是不可行的了,我们需要在7.0的时候。把所有应用与应用之间的文件传递改成content://的方式,并且还需要把该URI赋予临时的访问权限,使用如下:

    1.现在清单文件里配置一个provider

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="应用包名"
        android:exported="false"
        android:grantUriPermissions="true">
    </provider>

    2.在xml目录下创建file_paths文件

    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <files-path name="my_images" path="images/"/>
    </paths>

    在file_paths文件夹内声明的都是需要对外共享的目录,比如上面的配置等于 new File(Context.getFilesDir(),"images") 路径

    paths内还可以声明很多种类型的标签,每一种标签都代表了一个路径,如下:

    <files-path /> = getFilesDir()
    <cache-path /> = getCacheDir()
    <external-path /> = Environment.getExternalStorageDirectory()
    <external-files-path /> = Context#getExternalFilesDir(String) 或 Context.getExternalFilesDir(null)
    <external-cache-path /> = Context.getExternalCacheDir()
    <external-media-path /> = Context.getExternalMediaDirs()

    我们在配置的时候。name的作用就是为了隐藏后边的真实路径,为了安全考虑
    而后边的path则是需要共享的路径,用标签所代表的路径加上path上的值,就是完整的路径。

    写好path文件后,我们在回到清单文件内继续更新 android.support.v4.content.FileProvider 这个Provider的配置,需要把刚刚的file_paths文件和这个provider关联起来,如下

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="应用包名"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

    3.写完这些配置信息后,我们就可以在应用内直接获取需要共享文件的content://URI了,如下

    File imagePath = new File(Context.getFilesDir(), "images");
    File newFile = new File(imagePath, "test.jpg");
    Uri contentUri = FileProvider.getUriForFile(getContext(), "应用包名", newFile);

    getUriForFile的第二个参数内也可以不是应用包名,只要和清单文件内的authorities一致即可

    这个contentUri的最后结果就是 content://应用包名/my_images/test.jpg

    之所以会生成这个uri,相信很多同学看到这里就明白了,因为这个uri是要对其他应用共享的,所以不能直接共享真实路径,便衍生了FileProvider这种东西来专门生成这个Uri,他的生成规则便是 content:// 应用包名(或者是其他字符串)/ 伪名 (path文件内配置的name属性) / 文件名

    4.然后赋予临时访问权限

    我们可以调用 Context.grantUriPermission 方法来给Uri赋予权限,调用 revokeUriPermission() 函数来撤销权限,也可以通过 Intent.setFlags() 的方式来赋予临时访问权限

    最终在7.0上调用相机就成了这个样子,前提是file_paths文件已经配置OK

    if(checkSelfPermission(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
        requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},200);
        return;
    }
    Intent intent = new Intent();
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
    File imagePath = new File(Context.getFilesDir(), "images");
    File newFile = new File(imagePath, "test.jpg");
    Uri contentUri = FileProvider.getUriForFile(getContext(), "应用包名", newFile);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    startActivityForResult(intent,200);

    到这里就结束了,我也是刚刚踩完相机的坑,所以来一篇博客来记录这个经历,由于该内容全部都是用记事本手敲的,所以或多或少可能会有一些拼写错误之类的,欢迎在评论区指出错误,谢谢!

    一个被程序耽误的画手
  • 相关阅读:
    20135316王剑桥 linux第十一周课实验笔记
    20135316王剑桥 linux第十周课实验笔记
    20135316王剑桥 linux第七周课实验笔记
    0
    KRPANO资源分析工具下载四方环视全景图
    KRPANO资源分析工具下载720YUN全景图
    使用JAVA调用KRPANO加密XML
    KRPano JS 场景编辑器源码
    KRPano多屏互动原理
    KRPANO PR10最新激活码(破解)分享
  • 原文地址:https://www.cnblogs.com/kezhuang/p/8706988.html
Copyright © 2020-2023  润新知