安卓开发——拍照裁剪并保存为头像报错(裁剪图片无法保存的)

提兵百万西湖上,立马吴山第一峰!这篇文章主要讲述安卓开发——拍照裁剪并保存为头像报错:裁剪图片无法保存的相关的知识,希望能为你提供帮助。

在做学校大创项目的安卓开发时,需要从相册获取图片或者拍照,然后裁剪保存为头像。由于我是第一次弄安卓开发,也对Android现在越来越多的权限限制不了解,debug过程真的是异常心塞啊。
闲话不说(文末慢慢话痨),我开始是在网上找了一些代码打算用到项目上试试,但是连个拍照或者从相册选择图片都频繁报错(应该还是因为sd卡权限之类的吧),折腾了一晚上没有解决,第二天还是老老实实的看《第一行代码》,边学边写。在这里我简单梳理一下流程(关于裁剪后图片无法保存的问题的解释请直接跳到水平线之后):

  • 调用手机摄像头拍照:

//创建file文件,用于存储相机拍下的照片,这里我命名为my_head_image.jpg,并将它放在 //手机SD卡的应用关联缓存中。 /*File outputImage = new File(getExternalCacheDir(), "my_head_image.jpg"); try { if (outputImage.exists()) { outputImage.delete(); } outputImage.createNewFile(); } catch (IOException e) { e.printStackTrace(); } //将File对象转换为Uri对象,先进行系统版本的判定,android7.0以后的版本和之前的版本不 //太一样 if (Build.VERSION.SDK_INT > = 24) { imageUri = FileProvider.getUriForFile(ChangeMyDetails.this, "com.example.write.fileprovider", outputImage); } else { imageUri = Uri.fromFile(outputImage); */

File outputImage = new File(Environment.getExternalStorageDirectory(), "my_head_image.jpg");
String path = outputImage.getAbsolutePath();
Log.i("ChangeMyDetails", "outputImage路径为 "+path);
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}

imageUri = Uri.fromFile(outputImage);

//启动相机程序
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);

橙色部分的代码是《第一行代码》上的,如果拍照后的图片直接作为头像不裁剪的话这段是没问题的,但是你懂得,后来就崩了。在这里首先创建fFile对象,用于存放拍下的照片,并将它保存在SD卡的应用关联缓存目录下,调用getCacheDir()得到这个目录,具体路径是/sdcard/Android/data/< 你的package name> /cache。为什么要放在这里呢?  因为从Android6.0系统开始,读写SD卡被视为危险权限,如何放在其他目录,都要在运行时进行权限处理,而使用应用关联目录则可以跳过这一步。注意:在这里我就种下了裁剪后无法保存的隐患。(橙色下面的更正代码是后话了,因为还要涉及权限问题,我后面会讲,所以你看到这里欣喜的粘贴到你的项目里还是会报错的)
接着进行系统版本判断,FileProvider的getUriForFile()方法将File对象封装为Uri对象。getUriForFile()方法接收3个参数,第一个是要求传入的context对象,第二个可以是任意唯一的字符串(后面manifest.xml中注册< procider> 的android:authority要用到),第三个是要封装的这个File对象。之所以添加这一步,因为Android7.0系统开始,直接使用本地Uri被认为是不安全的,会抛出FIleURIExposedException异常。FileProvider则是一种特殊的得内容提供器,可以选择性的将封装过的Uri共享给外部,更加安全。
关于内容提供器,还要在manifest,xml中进行注册:
 
< provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.write.fileprovider" android:exported="false" android:grantUriPermissions="true"> < meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> < /provider>

 
其中,android:authorities属性值必须与FileProvider.getUriForFile()方法中的第二个参数一致,另外,用< meta-data> 来指定Uri的共享路径,并引用@xml/provider_paths资源,这个资源需要自己创建。
右击res目录→New→Directory,创建一个xml目录,然后右击xml目录→New→File,创建一个provider_paths.xml的文件:
< ?xml version="1.0" encoding="utf-8"?> < paths xmlns:android="http://schemas.android.com/apk/res/android"> < external-path name="my_images" path="."/> < /paths>

其中,external-path 就是用来指定Uri共享的,name属性值自定义就可以了,path属性为空表示整个SD卡进行共享。
 
  • 裁剪照片:
拍照时,使用startActivityForResult(intent, TAKE_PHOTO)来启动活动,因此拍完照会有结果返回到onActivityResult()方法中,拍照成功后执行接下来的裁剪。  onActivityResult()方法中还有从相册选择图片、裁剪成功后返回执行的操作,我就一起贴出来了。
 

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) { //用户没有进行有效的操作,返回 if (requestCode == RESULT_CANCELED) { Toast.makeText(getApplication(), "取消", Toast.LENGTH_LONG).show(); return; } switch (requestCode) { case FROM_GALLERY: if (resultCode == RESULT_OK) { if (Build.VERSION.SDK_INT > = 19) { //4.4以上系统使用 handleImageOnKitKat(data); } else { handleImageBeforeKitKat(data); } } break; case TAKE_PHOTO://裁剪照片 cropRawPhoto(imageUri); break; case RESULT_REQUEST_CODE: if (cropImgUri !=null) { try { Bitmap headImage = BitmapFactory.decodeStream(getContentResolver().openInputStream(cropImgUri)); headImageButton.setImageBitmap(headImage); } catch (Exception e) { e.printStackTrace(); }}else { Toast.makeText(this,"cropImgUri为空!",Toast.LENGTH_SHORT).show(); } break; } }


public void cropRawPhoto(Uri uri) {
//创建file文件,用于存储剪裁后的照片
File cropImage = new File(Environment.getExternalStorageDirectory(), "crop_image.jpg");
String path = cropImage.getAbsolutePath();
try {
if (cropImage.exists()) {
cropImage.delete();
}
cropImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
cropImgUri = Uri.fromFile(cropImage);
Intent intent = new Intent("com.android.camera.action.CROP");
//设置源地址uri
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 200);
intent.putExtra("outputY", 200);
intent.putExtra("scale", true);
//设置目的地址uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropImgUri);
//设置图片格式
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("return-data", false);
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent, RESULT_REQUEST_CODE);
}

 

startActivityForResult(intent, RESULT_REQUEST_CODE); 执行后,跳转到onActivityResult()中执行case RESULT_REQUEST_CODE:部分,代码已经贴出来了。就是调用BitmapFactory.decodeStream()方法将cropImage解析为bitmap对象。
然后,开始运行。
那么,问题来了(猜测是:由于裁剪后的图片保存到Cache里会耗费大量内存,Android是不允许你这样做的):
这里有一篇一篇博文进行了解释:http://www.cnblogs.com/tianzhijiexian/p/4059006.html
 
 
最开始,我是把相机拍下的照片和裁剪后的照片都存在关联应用缓存里,就是前面橙色部分代码的操作(贴上的代码是我后来改正过的没问题的代码)。启动相机程序拍照并存储是正常的,图片也保存了。但是,裁剪之后的图片无法保存到Cache目录里,我沿着/sdcard/Android/data/< 你的package name> /cache路径打开看了看,是0kb。
 
安卓开发——拍照裁剪并保存为头像报错(裁剪图片无法保存的)

文章图片

 
 
本来最开始我就怀疑这个Cache存储可能会有问题,但是又想,拍下照片都可以好好保存为什么裁剪后的就不能保存呢?这不公平啊!于是乎,我着手改其他的我也怀疑的地方,在网上搜寻相关解答折腾很久还是解决不了。最后,我决定验证最后一个猜想:裁剪后的图片以某种诡异不明的方式,无法保存到Cache里面。说干就干:
step1:把File路径换成普通的,也就是把File outputImage = new File(getExternalCacheDir(), "my_head_image.jpg")换成File outputImage = new File(Environment.getExternalStorageDirectory(), "my_head_image.jpg"); 同理更改File cropImage = new File(Environment.getExternalStorageDirectory(), "crop_image.jpg");
step2:在manifest.xml中注册权限:< uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  step3:进行到这里,我运行了一次,还是异常,应该还是权限问题没有处理完,我在onCreate()方法里添加了这样一段:
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); if (Build.VERSION.SDK_INT > = Build.VERSION_CODES.JELLY_BEAN_MR2) { builder.detectFileUriExposure(); }

 
  再运行,It works!
 
文末唠叨
关于StrictMode我就不细讲了(心累+懒)。
你懂得,在网上找解决bug的方法需要技巧、运气、时间,兜了一大圈,对于我来说,我在网上找solution八成都会花掉大堆时间,很多问题那都是别人遇到的麻烦和解决方法,对自己不一定适用,但是自己还是得作死的去多尝试,然后折腾一下午或者一晚上,心想着还不如在这个时间里换种方式浪费生命,比如看剧、睡觉、和朋友闲聊、以及吃……
对于安卓开发来说,太久之前的solution可能并不适用于现在了,比如现在越来越严格的权限问题。
有时候,在网上瞎找,不如好好看书,搞清楚到底是怎么一个流程,哪里会出错,反而会更快一些解决问题,也更有收获。
总而言之,要高效率解决问题,还是得清楚整个代码的流程。
好了,现在我要换种方式浪费生命了……
 
 
 

【安卓开发——拍照裁剪并保存为头像报错(裁剪图片无法保存的)】
安卓开发——拍照裁剪并保存为头像报错(裁剪图片无法保存的)

文章图片


    推荐阅读