Android 7.0及以上调用系统相机拍照、访问相册问题

转载请注明:https://blog.csdn.net/u012854870/article/details/80893783

Android7.0以后中尝试传递 file:// URI 会触发 FileUriExposedException,因为在Android7.0之后Google认为直接使用本地的根目录即file:// URI是不安全的操作,直接访问会抛出FileUriExposedExCeption异常,这就意味着在Android7.0以后我们访问相机拍照存储时,如果使用URI的方式直接存储剪裁图片就会造成这个异常,那么如何解决这个问题呢?

方案:

Google为我们提供了FileProvider类,进行一种特殊的内容提供,FileProvider时ContentProvide的子类,它使用了和内容提供器类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。下面就让我们看一下如何使用这个内容提供者进行数据访问的:

使用FileProvider获取Uri就会将以前的file:// URI准换成content:// URI,实现一种安全的应用间数据访问,内容提供者作为Android的四大组件之一,使用同样需要在清单文件AndroidManifest.xml中进行注册的,注册方法如下:

<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>

provider标签里的 

android:name的值是FileProvider的包名+类名为固定值。
android:authorities的值相当于一个标志,当我们使用FileProvider的getUriForFile方法时的一个参数需和清单文                                 件注册时的保持一致,这里我使用的是:${applicationld}直接获取app包名,可自行定义。
exported:要求必须为false,为true则会报安全异常。
grantUriPermissions:true,表示授予 URI 临时访问权限。
<meta-data />标签里面是用来指定共享的路径。 android:resource="@xml/file_paths"就是我们的共享路径配                          置的xml文件。
关于xml文件的配置如下:在res目录下创建xml文件夹,file_paths.xml文件内容如下:
file_paths.xml相关问题可参考:点击打开链接
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!--拍照-->
    <external-path
        name="image"
        path="Pictures"/>
   
</paths>

external-path标签用来指定Uri共享的,name属性的值可以自定义,path属性的值表示共享的具体位置,设置为空,就表示共享整个SD卡,也可指定对应的SDcard下的文件目录,根据需求自行定义。

接下来就是调用系统相机进行拍照了

注:调用相机拍照前需要先动态申请相机和存储权限(Android 6.0以上)

/**
 * 调用系统相机
 *
 * @param activityCompat
 * @return
 */
public static File OpenCamera(AppCompatActivity activityCompat) {
    Intent takeIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    //下面这句指定调用相机拍照后的照片存储的路径
    //String cameraPath = savePhotoJpgPath();
    File cameraFile = createImageFile();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判读版本是否在7.0以上
        takeIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件
        Uri apkUri = FileProvider.getUriForFile(activityCompat, activityCompat. getPackageName() + ".provider", cameraFile);
        takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, apkUri);
    } else {
        takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(cameraFile));
    }
    activityCompat.startActivityForResult(takeIntent, CAMERA_REQUEST_CODE);
    return cameraFile;
}

完整拍照相册选择照片工具类:

/**
 * 注册manifest
 * <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>
 * <p>
 * 调用(需要先申请权限)
 * String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA};
 * String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
 *
 * @Override public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
 * super.onPermissionsGranted(requestCode, perms);
 * switch (requestCode) {
 * case 101:
 * cameraPath = CallSystemCameraOrAlbum.OpenCamera(this);
 * break;
 * case 102:
 * CallSystemCameraOrAlbum.OpenAlbum(this);
 * break;
 * default:
 * break;
 * }
 * }
 * 返回接收
 * @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 * if (resultCode == RESULT_OK) {
 * switch (requestCode) {
 * case CallSystemCameraOrAlbum.CAMERA_REQUEST_CODE:
 * cropResultPath = CallSystemCameraOrAlbum.getCameraUri(this, cameraPath);
 * break;
 * case CallSystemCameraOrAlbum.GALLERY_REQUEST_CODE:
 * cropResultPath = CallSystemCameraOrAlbum.getAlbumUri(this, data);
 * break;
 * case CallSystemCameraOrAlbum.CROP_REQUEST_CODE:
 * ImageUtil.displayUserIcon(mContext, ivIcon, cropResultPath);
 * break;
 * default:
 * break;
 * }
 * }
 * super.onActivityResult(requestCode, resultCode, data);
 * }
 * Created by pangli on 2018/4/23 11:50
 * 备注: 调用系统相册或相机拍照裁剪
 */
public class CallSystemCameraOrAlbum {
    public final static int CAMERA_REQUEST_CODE = 1;
    public final static int GALLERY_REQUEST_CODE = 2;
    public final static int CROP_REQUEST_CODE = 3;
    private static String path = "";
    private static final File parentPath = Environment.getExternalStorageDirectory();
    private static String EDITOR_PHOTO_NAME = "Core";

    /**
     * 调用系统相册
     *
     * @param activityCompat
     */
    public static void OpenAlbum(AppCompatActivity activityCompat) {
        Intent pickIntent = new Intent(Intent.ACTION_PICK, null);
        // 如果限制上传到服务器的图片类型时可以直接写如:"image/jpeg  image/png等的类型"
        pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
        activityCompat.startActivityForResult(pickIntent, GALLERY_REQUEST_CODE);
    }

    /**
     * 调用系统相机
     *
     * @param activityCompat
     * @return
     */
    public static File OpenCamera(AppCompatActivity activityCompat) {
        Intent takeIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        //下面这句指定调用相机拍照后的照片存储的路径
        //String cameraPath = savePhotoJpgPath();
        File cameraFile = createImageFile();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判读版本是否在7.0以上
            takeIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件
            Uri apkUri = FileProvider.getUriForFile(activityCompat, activityCompat. getPackageName() + ".provider", cameraFile);
            takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, apkUri);
        } else {
            takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(cameraFile));
        }
        activityCompat.startActivityForResult(takeIntent, CAMERA_REQUEST_CODE);
        return cameraFile;
    }

    /**
     * 拍照裁剪返回图片地址
     *
     * @param activityCompat
     * @param cameraFile
     * @return
     */
    public static String getCameraUri(AppCompatActivity activityCompat, File cameraFile) {
        //用相机返回的照片去调用剪裁也需要对Uri进行处理
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri contentUri = FileProvider.getUriForFile(activityCompat, activityCompat. getPackageName() + ".provider", cameraFile);
            return cropPhoto(activityCompat, contentUri);
        } else {
            return cropPhoto(activityCompat, Uri.fromFile(cameraFile));
        }
    }

    /**
     * 相册裁剪返回地址
     *
     * @param activityCompat
     * @param data
     * @return
     */
    public static String getAlbumUri(AppCompatActivity activityCompat, Intent data) {
        Uri contentUri = data.getData();
        return cropPhoto(activityCompat, contentUri);
    }

    /**
     * 调用系统裁剪方法
     *
     * @param activityCompat
     * @param uri
     * @return
     */
    private static String cropPhoto(AppCompatActivity activityCompat, Uri uri) {
        String lastPictureName = savePhotoJpgPath();
        Intent intent = new Intent("com.android.camera.action.CROP");
        //7.0以上权限
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(uri, "image/*");
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(lastPictureName)));//定义输出的File Uri
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", 200);
        intent.putExtra("outputY", 200);
        intent.putExtra("scale", true);
        intent.putExtra("return-data", false);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        intent.putExtra("noFaceDetection", true);
        activityCompat.startActivityForResult(intent, CROP_REQUEST_CODE);
        return lastPictureName;
    }


    private static String initPath() {
        if (path.equals("")) {
            path = parentPath.getAbsolutePath() + File.separator + EDITOR_PHOTO_NAME;
            File file = new File(path);
            if (!file.exists()) {
                file.mkdir();
            }
        }
        return path;
    }

    private static String savePhotoJpgPath() {
        return initPath() + File.separator + "avatar_" + System.currentTimeMillis() + ".jpg";
    }

    /**
     * 注册manifest
     * <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>
     * <p>
     * <p>
     * <p>
     * filepaths.xml
     * <paths>
     * <external-path
     * name="my_images"
     * path="Pictures"/>
     * </paths>
     * 注意file地址和filepaths中的external-path的对应关系
     *
     * @return
     */
    private static File createImageFile() {
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
        String imageFileName = String.format("JPEG_%s.jpg", timeStamp);
        File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        // Avoid joining path components manually
        File tempFile = new File(storageDir, imageFileName);
        // Handle the situation that user's external storage is not ready
        if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
            return null;
        }
        return tempFile;
    }
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值