转载请注明: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 临时访问权限。
标签里面是用来指定共享的路径。 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 *  * android:name="android.support.v4.content.FileProvider" * android:authorities="${applicationId}.provider" * android:exported="false" * android:grantUriPermissions="true"> *  * android:name="android.support.FILE_PROVIDER_PATHS" * android:resource="@xml/file_paths"/> *  * 

* 调用(需要先申请权限) * 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 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 * * android:name="android.support.v4.content.FileProvider" * android:authorities="${applicationId}.provider" * android:exported="false" * android:grantUriPermissions="true"> * * android:name="android.support.FILE_PROVIDER_PATHS" * android:resource="@xml/file_paths"/> * *

*

*

* filepaths.xml * * * name="my_images" * path="Pictures"/> * * 注意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. 在代码中实现按下Home键的效果
  2. Android(安卓)显示系统分析
  3. android编程如何让程序后台运行
  4. Android之监测database的改变--notifyChange
  5. 关于使用AccountManager的remove删除Android帐号的细节
  6. 2011.07.12(3)——— android ui的一些概念
  7. View绘制流程源码解析-第一篇
  8. Android的USB系统简单分析之一
  9. android webview js不执行原因解析

随机推荐

  1. 《Android系统学习》第十一章:Android应用
  2. title上左右按钮
  3. asss
  4. tools:context=".MainActivity的作用
  5. Android: Android(安卓)Reboot流程
  6. Androidの游戏源码下载地址
  7. Android监听网络变化
  8. android安装SDK时遇到的一些问题
  9. Android(安卓)Wear - Design Principles
  10. tools:context=".MainActivity的作用