在开发 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);
到这里就结束了, 我也是刚刚踩完相机的坑, 所以来一篇博客来记录这个经历, 由于该内容全部都是用记事本手敲的, 所以或多或少可能会有一些拼写错误之类的, 欢迎在评论区指出错误, 谢谢!
来源: https://www.cnblogs.com/kezhuang/p/8706988.html