Android笔记系列--Camera_FileProvider

源码例子:
https://github.com/StarsAaron/CameraDemo/tree/master

FileProvider

在Android7.0版本上,Android系统强制执行了StrictMode API 政策,禁止向你的应用外公开File://URI。如果一项包含文件File://URI类型的Intent离开你的应用,应用失败,并出现FileUriExposedException,比如系统相机拍照,裁剪照片。

使用FileProvider

FileProvider继承于ContentProvider,所以我们需要在manifest中注册Provider

1) 在manifest里注册provider

<application
             ...>
    
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"/>
    provider>
application>
  • authorities:标识provider 一般由 [packageName/applicationId + 自定义名称] 命名
  • exported:要求必须为false,具体信息不在本文讨论
  • grantUriPermissions:是否授予Uri临时访问权限
  • meta-data:定义FileProvider所授权的资源路径

exported必须要求为false,为true则会报安全异常。grantUriPermissions为true,表示授予URI临时访问权限。

2) 指定共享目录
res\ xml\ file_paths.xml


<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path path="Android/data/com.example.package.name/files/Pictures/OriPicture/" name="images" />
    <external-path path="Android/data/com.example.package.name/files/Pictures/OriPicture/" name="images" />
    <external-files-path path="files/Pictures/OriPicture" name="images"/>
    <root-path path="" name="images"/>
paths>

中可以定义以下子节点

子节点 对应路径
files-path Context.getFilesDir()
cache-path Context.getCacheDir()
external-path Environment.getExternalStorageDirectory()
external-files-path Context.getExternalFilesDir(null)
external-cache-path Context.getExternalCacheDir()

file://到content://的转换规则:

  • 替换前缀:把file://替换成content://${android:authorities}。
  • 匹配和替换
    • 遍历的子节点,找到最大能匹配上文件路径前缀的那个子节点。
    • 用path的值替换掉文件路径里所匹配的内容。
  • 文件路径剩余的部分保持不变。
file:///data/data/com.xxx/files/images/2016/pic.png
->
content://android:authorities
+
"images/" name="my_images"/>
->
content://com.xxx.fileprovider/my_images/2016/pic.png

需要注意的是,文件的路径必须包含在xml中,也就是2.1中必须能找到一个匹配的子节点,否则会抛出异常:java.lang.IllegalArgumentException

在打开相册的需求时,有的手机厂商会在系统根目录放入一些图像来供用户体验,所有当你选择了这些就会出现onFileUriExposed的异常,所以建议加上root-path标签

3) 使用FileProvider

FileProvider.getUriForFile(this, getPackageName() + “.provider”, oriPhotoFile);

有两种设置文件访问权限

  • 调用Context.grantUriPermission(package, uri, modeFlags)。这样设置的权限只有在手动调用Context.revokeUriPermission(uri, modeFlags)或系统重启后才会失效。

注意:如果manifest.xml没有定义android:grantUriPermissions=”true”或者定义为”false”,都需要在代码中动态定义

List resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    grantUriPermission(packageName, imgUriOri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
  • 调用Intent.setFlags()来设置权限。权限失效的时机:接收Intent的Activity所在的stack销毁时。
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

第一个是根据File获取FileProvider管理的Uri,第二个是对Intent授予读写私有目录的权限。

打开系统相册的Action有三种:

setType(“image/*”)情况下:

  • ACTION_PICK :直接打开图库,优点是不会出现类型混乱的情况,缺点仅能打开图库而不能从其他路径中获取资源且界面较单一。-
  • ACTION_GET_CONTENT:4.4以下的版本打开的是缩略图的图库,4.4以上是可选择的页面(图库/图像选择器)。-
  • ACTION_OPEN_DOCUMENT:直接打开图像选择器,不可以在4.4以下使用。(官方建议4.4+使用此Action)。

从图库选择图像返回的Uri:

<7.0 :content://media/external/images/media/…
>7.0 :content://com.android.providers.media.documents/document/image…

从图像选择器选择图像返回的Uri:

<4.4 :不可打开图像选择器
4.4 - 7.0 :content://media/external/images/media/…
>7.0 :content://com.android.providers.media.documents/document/image…

content://media/external/images/media/35144
这种格式,转换成路径后可以直接用

content://com.android.providers.media.documents/document/image%3A35144
这种格式,转换成路径后不能直接用。

坑1:4.4+ 系统打开相册的不同Action
坑2 :6.0+ 动态权限适配
坑3:7.0+ FileProvider对设备内容Uri的处理
坑4:小米手机的FileProvider共享目录配置(也可以说根目录下的资源文件目录配置)

打开相机

添加权限,6.0需要动态权限申请

<uses-feature android:name="android.hardware.camera"/>

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
private void openCamera() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 打开相机
    File oriPhotoFile = null;
    try {
        oriPhotoFile = createOriImageFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
    if (oriPhotoFile != null) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            imgUriOri = Uri.fromFile(oriPhotoFile);
        } else {
            imgUriOri = FileProvider.getUriForFile(this, getPackageName() + ".provider", oriPhotoFile);
        }
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUriOri);
        startActivityForResult(intent, REQUEST_OPEN_CAMERA);
        // 动态grant权限
        // 如果在xml中已经定义android:grantUriPermissions="true"
        // 则只需要intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);即可
        //            List resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        //            for (ResolveInfo resolveInfo : resInfoList) {
        //                String packageName = resolveInfo.activityInfo.packageName;
        //                grantUriPermission(packageName, imgUriOri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        //            }
        Log.i(TAG, "openCamera_imgPathOri:" + imgPathOri);
        Log.i(TAG, "openCamera_imgUriOri:" + imgUriOri.toString());
    }
}

打开相册

private void openGallery() {
    Intent intent = new Intent();
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {//4.4及以上
        intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
    } else {//4.4 以下
        intent.setAction(Intent.ACTION_GET_CONTENT);
        // intent.setAction(Intent.ACTION_PICK);
    }
    intent.setType("image/*");
    startActivityForResult(intent, REQUEST_OPEN_GALLERY);
}

裁剪图片

public void cropPhoto(Uri uri) {
    Intent intent = new Intent("com.android.camera.action.CROP");
    File cropPhotoFile = null;
    try {
        cropPhotoFile = createCropImageFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
    if (cropPhotoFile != null) {
//       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
//           imgUriCrop = Uri.fromFile(cropPhotoFile);
//       }else{
//           imgUriCrop = FileProvider.getUriForFile(this, getPackageName() + ".provider", cropPhotoFile);
//       }

        //7.0 安全机制下不允许保存裁剪后的图片
        //所以仅仅将File Uri传入MediaStore.EXTRA_OUTPUT来保存裁剪后的图像
        imgUriCrop = Uri.fromFile(cropPhotoFile);
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", true);
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", 300);
        intent.putExtra("outputY", 300);
        intent.putExtra("return-data", false);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUriCrop);
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        startActivityForResult(intent, REQUEST_CROP_PHOTO);
    }
}
附加选项 数据类型 描述
crop String 发送裁剪信号
aspectX int X方向上的比例
aspectY int Y方向上的比例
outputX int 裁剪区的宽
outputY int 裁剪区的高
scale boolean 是否保留比例
return-data boolean 是否将数据保留在Bitmap中返回
data Parcelable 相应的Bitmap数据
circleCrop String 圆形裁剪区域?
MediaStore.EXTRA_OUTPUT (“output”) URI 将URI指向相应的file:///…
outputFormat String 输出格式,一般设为Bitmap格式:Bitmap.CompressFormat.JPEG.toString()
noFaceDetection boolean 是否取消人脸识别功能

return-data:是将结果保存在data中返回,在onActivityResult中,直接调用intent.getdata()就可以获取值了,这里设为fase,就是不让它保存在data中

MediaStore.EXTRA_OUTPUT:由于我们不让它保存在Intent的data域中,但我们总要有地方来保存我们的图片啊,这个参数就是转移保存地址的,对应Value中保存的URI就是指定的保存地址。至于这两个参数能不能同时设为输出,这个我也不清楚……

实际数据:
camera content://com.danielfu.camerademo.provider/images/storage/emulated/0/Android/data/com.danielfu.camerademo/files/Pictures/OriPicture/HomePic_20180126_163642902806005.jpg

gallery
content://com.android.providers.media.documents/document/image%3A126047
/storage/emulated/0/tencent/MicroMsg/WeiXin/wx_camera_1516844243127.jpg

crop
file:///storage/emulated/0/Android/data/com.danielfu.camerademo/files/Pictures/CropPicture/HomePic_20180126_1637511666324121.jpg

你可能感兴趣的:(Android笔记系列,Camera,FileProvider)