全部代码:点击下载
注意:如果你原先的应用的targetSdkVersion本来就小与27。那就拍照。什么都不修改。也不会崩溃。但是、一旦你修改了你的targetSdkVersion为27.或者28。那你的应用就会报出这些问题。。具体原因。请自行百度下targetSdkVersion的意义。
Android 7.0以上的系统。在拍照的时候。报错:
android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/XXX/files/avatar.jpg exposed beyond app through ClipData.Item.getUri()
在网上查一下就可以知道。这是Android7.0的“私有目录被限制访问”。具体的解释简书的一篇文章:
https://www.jianshu/p/2275bb552327
里面讲的很仔细。
然后我们知道问题的解决办法就是通过FileProvider.可是我们应该怎么用。在哪个地方使用。
A:FileProvider的使用步骤:
1:在资源(res)目录下创建一个xml目录,然后创建一个名为"file_paths"的资源文件
(res-->new -->Directory.然后输入xml。在xml上右击--》new--》XML--->ValuesXmL-->输入名字file_paths
不过会跑到values下面去。。不慌我们剪切到xml下面即可)
xml里面的内容如下:
具体解释:借用上面链接文章里面的一段文字:
所以:我上面的路径应该是:/storage/emulated/0/Android/data/com.example.zongm.testapplication/ 之所以这样写。是想要在应用删除的时候。在应用里面拍的照片也能够被删除掉。
2:在manifest清单文件中注册provider(放在application节点里面)
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.zongm.testapplication.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
解释:
android:authorities="com.example.zongm.testapplication.provider" 这个值可以随便写。我用的是appid。这个值决定了fileProVider生成的uri的路径。后面详细介绍
exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 临时访问权限。
然后我们就可以在代码中使用FileProvider了。
使用:
private Uri getImageUri() { if (isSdCardExist()) { photo_image = new SimpleDateFormat("yyyy_MMdd_hhmmss").format(new Date()); File[] dirs = ContextCompat.getExternalFilesDirs(this, null); if (dirs != null && dirs.length > 0) { File dir = dirs[0]; File file = new File(dir, photo_image); takePath = file.getAbsolutePath(); Log.e("zmm", "图片的路径---》" + takePath); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //FileProvider.getUriForFile();第一个参数是context. // 第二个值。比较关键、这个也就是我们在manifest里面的provider里面的 //android:authorities="com.example.zongm.testapplication.provider" //因为我用的就是AppId.所以。这里就直接用BuildConfig.APPLICATION_ID了。 //如果你的android:authorities="test.provider"。那这里第二个参数就应该是test.provider return FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file); } else { return Uri.fromFile(file); } } } return Uri.EMPTY; }
下面通过一个例子来看看我们如何使用。代码注释写的很清楚了应该:
public class Main3Activity extends AppCompatActivity { //打开相机的返回码 private static final int CAMERA_REQUEST_CODE = 1; //选择图片的返回码 private static final int IMAGE_REQUEST_CODE = 2; //剪切图片的返回码 public static final int CROP_REREQUEST_CODE = 3; private ImageView iv; //相机 public static final int REQUEST_CODE_PERMISSION_CAMERA = 100; public static final int REQUEST_CODE_PERMISSION_GALLERY = 101; //照片图片名 private String photo_image; //截图图片名 private String crop_image; //拍摄的图片的真实路径 private String takePath; //拍摄的图片的虚拟路径 private Uri imageUri; private Uri cropUri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main3); iv = findViewById(R.id.iv); } /** * 拍照 * * @param view */ public void onClickTakePhoto(View view) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { checkPermission(REQUEST_CODE_PERMISSION_CAMERA); return; } openCamera(); } /** * 打开系统的相机的时候。我们需要传入一个uri。该uri就是拍摄的照片的地址。 * 也就是:cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, getImageUri()); * 这里就用到了FileProvider */ private void openCamera() { if (isSdCardExist()) { Intent cameraIntent = new Intent( "android.media.action.IMAGE_CAPTURE"); photo_image = new SimpleDateFormat("yyyy_MMdd_hhmmss").format(new Date()) + ".jpg"; imageUri = getImageUri(photo_image); //Log.e("zmm", "图片存储的uri---------->" + imageUri); cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT , imageUri); //添加这一句表示对目标应用临时授权该Uri所代表的文件 cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); cameraIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); startActivityForResult(cameraIntent, CAMERA_REQUEST_CODE); } else { Toast.makeText(this, "SD卡不存在", Toast.LENGTH_SHORT).show(); } } /** * 打开图库 * 不需要用FileProvider * * @param view */ public void onClickOpenGallery(View view) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { checkPermission(REQUEST_CODE_PERMISSION_GALLERY); return; } openGallery(); } private void openGallery() { Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT); galleryIntent.addCategory(Intent.CATEGORY_OPENABLE); galleryIntent.setType("image/*"); startActivityForResult(galleryIntent, IMAGE_REQUEST_CODE); } /** * @param path 原始图片的路径 */ public void cropPhoto(String path) { crop_image = new SimpleDateFormat("yyyy_MMdd_hhmmss").format(new Date()) + "_crop" + ".jpg"; File cropFile = createFile(crop_image); File file = new File(path); Intent intent = new Intent("com.android.camera.action.CROP"); //TODO:访问相册需要被限制,需要通过FileProvider创建一个content类型的Uri if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //添加这一句表示对目标应用临时授权该Uri所代表的文件 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //TODO:访问相册需要被限制,需要通过FileProvider创建一个content类型的Uri imageUri = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file); cropUri = Uri.fromFile(cropFile); //TODO:cropUri 是裁剪以后的图片保存的地方。也就是我们要写入此Uri.故不需要用FileProvider //cropUri = FileProvider.getUriForFile(getApplicationContext(), // BuildConfig.APPLICATION_ID + ".provider", cropFile); } else { imageUri = Uri.fromFile(file); cropUri = Uri.fromFile(cropFile); } intent.setDataAndType(imageUri, "image/*"); intent.putExtra("crop", "true"); //设置宽高比例 intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); //设置裁剪图片宽高 intent.putExtra("outputX", 400); intent.putExtra("outputY", 400); intent.putExtra("scale", true); //裁剪成功以后保存的位置 intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); startActivityForResult(intent, CROP_REREQUEST_CODE); } /** * 获得一个uri。该uri就是将要拍摄的照片的uri * * @return */ private Uri getImageUri(String name) { if (isSdCardExist()) { File file = createFile(name); if (file != null) { takePath = file.getAbsolutePath(); Log.e("zmm", "图片的路径---》" + takePath); // 输出是/storage/emulated/0/Android/data/com.example.zongm.testapplication/files/2018_0713_111455.jpg // 根据这个path。拿到的Uri是:content://com.example.zongm.testapplication.provider/files_root/files/2018_0713_111455.jpg //我们可以看到真实路径:/Android/data/com.example.zongm.testapplication这一部分被files_root替代了 //也就是我们在file_path里面写的<external-path // name="files_root" // path="Android/data/com.example.zongm.testapplication/" /> //其中external-path代表的是 Environment.getExternalStorageDirectory() 也就是/storage/emulated/0 //。。。。我说的有点乱。大家还是看那篇简书文章吧。:链接:https://www.jianshu/p/56b9fb319310 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //FileProvider.getUriForFile();第一个参数是context. // 第二个值。比较关键、这个也就是我们在manifest里面的provider里面的 //android:authorities="com.example.zongm.testapplication.provider" //因为我用的就是AppId.所以。这里就直接用BuildConfig.APPLICATION_ID了。 //如果你的android:authorities="test.provider"。那这里第二个参数就应该是test.provider return FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file); } else { return Uri.fromFile(file); } } } return Uri.EMPTY; } public File createFile(String name) { if (isSdCardExist()) { File[] dirs = ContextCompat.getExternalFilesDirs(this, null); if (dirs != null && dirs.length > 0) { File dir = dirs[0]; return new File(dir, name); } } return null; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { switch (requestCode) { case CAMERA_REQUEST_CODE://拍照成功并且返回 //注意这里。可以直接用takePath。也可以直接用imageUri。 //因为Glide直接加载Uri。也可以加载地址。 //Glide.with(this) // .asBitmap() // .load(imageUri) // .into(iv); //但是。这里加载的都是拍摄的原图。一般我们都会根据uri。或者path.找到文件。把bitmap取出来。然后做压缩等其他的二次处理。 //decodeImage(imageUri);//显示照片 //或者直接去裁剪 //这里有个坑。就是我们如果想要根据Uri---》图片的真实path.然后拿到File.一般的Uri. // 例如是从图库选择照片并且回来的图片。我们拿到的Uri是这样的: //content://com.android.providers.media.documents/document/image%3A732871 //或者这样:content://media/external/images/media/694091 //这样我们可以用ImageUtils.getPath()这个里面的一系列方法拿到真实路径。 //但是。如果是通过我们的FileProvider拿到的Uri.是这样的: //content://com.example.zongm.testapplication.provider/files_root/files/2018_0713_020952.jpg //这样的路径我们是用ImageUtils.getPath()这个里面的一系列方法是拿不到真实路径的。会报错: //报错信息是:GetDataColumnFail java.lang.IllegalArgumentException: column '_data'does not exist //我们在网上查一下。就可以知道。要想拿到FileProvider得到的Uri的真实图片路径。需要用到反射: //这里大家可以去查一下:这里随便给一个博客地址。:https://blog.csdn/u010853225/article/details/80191880 // 故这里我们不能用此方法拿到真实路径 String path = ImageUtils.getPath(this, imageUri); cropPhoto(takePath); break; case IMAGE_REQUEST_CODE://选择图片成功返回 if (data != null && data.getData() != null) { imageUri = data.getData(); //直接显示出来 //decodeImage(data.getData()); //或者去裁剪 String path = ImageUtils.getPath(this, imageUri); Log.e("zmm", "选择的图片的虚拟地址是------------>" + data.getData() + "--->" + path); cropPhoto(path); } break; case CROP_REREQUEST_CODE: Log.e("zmm", "裁剪以后的地址是------------>" + cropUri); decodeImage(cropUri); break; } } } /** * 根据uri拿到bitmap * * @param imageUri 这个Uri是 */ private void decodeImage(Uri imageUri) { //这样是可以正常拿到bitmap。但是我们知道。这样写。很有可能会oom //Bitmap bitmap = ImageUtils.decodeUriAsBitmap(this, imageUri); //Log.e("zmm", "初始大小-------------->" + bitmap.getByteCount());//原始大小是47235072 //iv.setImageBitmap(bitmap); //所以我们一般都是把bitmap 进行一次压缩 try { Bitmap bitmapFormUri = ImageUtils.getBitmapFormUri(this, imageUri); //Log.e("zmm", "压缩过后------------->" + bitmapFormUri // .getByteCount());//压缩过后2952192 iv.setImageBitmap(bitmapFormUri); } catch (IOException e) { e.printStackTrace(); } } /** * 检查权限 * * @param requestCode */ private void checkPermission(int requestCode) { boolean granted = PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, Manifest.permission_group.CAMERA); if (granted) {//有权限 if (requestCode == REQUEST_CODE_PERMISSION_CAMERA) { openCamera();//打开相机 } else { openGallery();//打开图库 } return; } //没有权限的要去申请权限 //注意:如果是在Fragment中申请权限,不要使用ActivityCompat.requestPermissions, // 直接使用Fragment的requestPermissions方法,否则会回调到Activity的onRequestPermissionsResult ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest .permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length > 0) { boolean flag = true; for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] != PERMISSION_GRANTED) { flag = false; break; } } //权限通过以后。自动回调拍照 if (flag) { if (requestCode == REQUEST_CODE_PERMISSION_CAMERA) { openCamera();//打开相机 } else { openGallery();//打开图库 } } else { Toast.makeText(this, "请开启权限", Toast.LENGTH_SHORT).show(); } } } /** * 检查SD卡是否存在 */ public boolean isSdCardExist() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } }
嗯。写了很多东西。也很杂乱。总结一下
A:在用相机拍照的时候。报错:
Caused by: android.os.FileUriExposedException: file:///storage/emulated/0/tencent/MicroMsg/WeiXin/mmexport1530703314161.jpg exposed beyond app through Intent.getData()
说明。你需要用 FileProvider.getUriForFile()来代替原本的Uri.fromFile();
解决办法就是参照网上的方法用FileProvider.
B:如果在过程中报错:
GetDataColumnFail java.lang.IllegalArgumentException: column '_data'does not exist
那可能是因为你在Uri转Path的过程中用的Uri是由FileProvider得到的Uri.
解决办法:看看是不是可以换成普通的Uri.如果非得用FileProvider得到的Uri.。你就得用反射的方法来拿到真实路径:具体实现请自行搜索。
C:如果在过程中报错:
Writing exception to parcel
java.lang.SecurityException: Permission Denial: writing android.support.v4.content.FileProvider uri content://com.example.zongm.testapplication.provider/files_root/files/2018_0713_033250_crop.jpg from pid=10330, uid=10013 requires the provider be exported, or grantUriPermission()
看看是不是在不该使用FileProvider的时候使用了FileproVider。例如裁剪的时候。输出的路径。
至此问题就解决了。
单曲循环《绝口不提!爱你》
每日语录:
在一回首间,才忽然发现,原来,我一生的种种努力,不过只为了周遭的人对我满意而已。为了搏得他人的称许与微笑,我战战兢兢地将自己套入所有的模式所有的桎梏。走到途中才忽然发现,我只剩下一副模糊的面目,和一条不能回头的路。---席慕容《独白》更多推荐
Android 7.0及其以上系统拍照,打开相册,裁剪,报错: android.os.FileUriExposedException: file:///stor
发布评论