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

已标记关键词 清除标记
【为什么还需要学习C++?】 你是否接触很多语言,但从来没有了解过编程语言的本质? 你是否想成为一名资深开发人员,想开发别人做不了的高性能程序? 你是否经常想要窥探大型企业级开发工程的思路,但苦于没有基础只能望洋兴叹?   那么C++就是你个人能力提升,职业之路进阶的不二之选。 【课程特色】 1.课程共19大章节,239课时内容,涵盖数据结构、函数、类、指针、标准库全部知识体系。 2.带你从知识与思想的层面从0构建C++知识框架,分析大型项目实践思路,为你打下坚实的基础。 3.李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。 【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署; 2.吊打一切关于C++的笔试面试题; 3.面向物联网的“嵌入式”和面向大型化的“分布式”开发,掌握职业钥匙,把握行业先机。 【面向人群】 1.希望一站式快速入门的C++初学者; 2.希望快速学习 C++、掌握编程要义、修炼内功的开发者; 3.有志于挑战更高级的开发项目,成为资深开发的工程师。 【课程设计】 本课程包含3大模块 基础篇 本篇主要讲解c++的基础概念,包含数据类型、运算符等基本语法,数组、指针、字符串等基本词法,循环、函数、类等基本句法等。 进阶篇 本篇主要讲解编程中常用的一些技能,包含类的高级技术、类的继承、编译链接和命名空间等。 提升篇: 本篇可以帮助学员更加高效的进行c++开发,其中包含类型转换、文件操作、异常处理、代码重用等内容。
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页