在做学校大创项目的安卓开发时,需要从相册获取图片或者拍照,然后裁剪保存为头像。由于我是第一次弄安卓开发,也对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中进行注册:

<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="com.example.write.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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可能并不适用于现在了,比如现在越来越严格的权限问题。

  有时候,在网上瞎找,不如好好看书,搞清楚到底是怎么一个流程,哪里会出错,反而会更快一些解决问题,也更有收获。

  总而言之,要高效率解决问题,还是得清楚整个代码的流程。

  好了,现在我要换种方式浪费生命了……

 

转载于:https://www.cnblogs.com/Roe-Ross/p/7615881.html

安卓开发——拍照、裁剪并保存为头像报错:裁剪图片无法保存的相关推荐

  1. mpvue开发微信小程序,分享按钮报错:`Cannot read property 'apply' of null`

    用mpvue开发微信小程序,分享按钮报错:Cannot read property 'apply' of null thirdScriptError Cannot read property 'app ...

  2. PLB配置:PLB中新建负载均衡-新增server,点击“保存”,页面报错:介质包上传失败。后台异常:com.primeton.appserver.common.exception.BaseExcep

    [问题描述] PLB V6中新建负载均衡-新增server,点击"保存",页面报错:介质包上传失败.后台异常:com.primeton.appserver.common.excep ...

  3. android 华为裁剪全图,华为手机头像上传裁剪操作 报错

    华为手机头像上传操作, 裁剪点存储--进入裁剪后--点确定, 就直接报错退出程序了,其他机型测都是成功的,求大神指教 报错代码 @Override protected void onActivityR ...

  4. android4.0支持m3u8格式,【报Bug】安卓下无法播放M3U8格式音频,报错

    详细问题描述 (DCloud产品不会有明显的bug,所以你遇到的问题大都是在特定环境下才能重现的问题,请仔细描述你的环境和重现方式,否则DCloud很难排查解决你的问题) [内容] 安卓下小程序音频播 ...

  5. 安卓:No static method decodeBase64,commen-code包里报错的信息

    安卓:No static method decodeBase64 造成主要原因 1.导入的commons-codec的jar包与项目里的第三方sdk(比如微信和qq第三方登陆重复) 2.安卓frame ...

  6. 微信小程序开发工具npm用不了,报错“npm不是内部或外部命令,也不是可运行的程序”

    微信小程序开发工具npm用不了,报"npm不是内部或外部命令,也不是可运行的程序"错时. 1.首先查看是否下载了Node.js.如果没有下载下载一个,最新版本的已经添加了环境变量, ...

  7. 【Android开发日记】jsonObject = new JSONObject(info)报错 A JSONObject text must begin with '{' at character

    问题描述: JSONObject jsonObject = new JSONObject(json);报错:A JSONObject text must begin with '{' at chara ...

  8. flutter 开发ios升级到iOS13之后运行报错

    android studio中跑Flutter项目,运行iOS13系统报错 2019-10-29 14:45:16.875 ios-deploy[38706:203108] [ !! ] Error ...

  9. 使用videojs,安卓可以正常播放视频,ios浏览器报错The media could not be loaded,either because the server or network ...

    写在开头 video标签在各个浏览器中表现各异,存在很多兼容性问题,所以我尝试使用了videojs这个框架 传送门:videojs起步 传送门:videojs文档 直入主题 问题:按照videojs文 ...

最新文章

  1. JavaScript 各种遍历方式详解
  2. 计算机书籍-前谷歌工程师撰写-机器学习实战
  3. 访问SharePoint站点时,提示:Service Unavailable
  4. 【翻译】Programming Ruby——正则表达式
  5. wps右键新建里面没有word和excel_wps常用的基本操作方法
  6. Fiddler抓包使用教程-模拟低速网络环境
  7. 错误fatal error: zlib.h: 没有那个文件或目录的解决办法
  8. 怎么导出插件_不会插画没关系,插件帮你快速搞定
  9. 《深入理解Nginx:模块开发与架构解析》一1.6 Nginx的命令行控制
  10. 看懂Oracle执行计划(转载)
  11. 【LeetCode】剑指 Offer 27. 二叉树的镜像
  12. 构建一个ASP.NET Wiki来解释TDD
  13. 基于JAVA+Servlet+JSP+MYSQL的心理咨询预约管理系统
  14. 【图像去噪】基于matlab中值+均值+高斯+Laplacian+Sobel+Prewitt图像去噪【含Matlab源码 025期】
  15. 使用文档检查器后,think-cell 元素损坏
  16. 【MM32F5270开发板试用】+RFID门禁
  17. ERROR in Entry module not found: Error: Can‘t resolve ‘./src/index.js‘ in‘xxx.js‘
  18. 如何在Foobar2000中使用iPod
  19. WHMCS V7.60 新增短信宝短信插件
  20. 使用Zend studio+WAMP来调试Wordpress后台的PHP程序的一些非常关键的信息(原创)

热门文章

  1. [转]Form中控制Tab画布不同标签间切换的方法
  2. 笔记之零基础入门金融风控-贷款违约预测
  3. SQL获取当前月份的第一天
  4. 备考系统集成项目管理工程师经验
  5. 7-1 厘米换算英尺英寸(基础编程题)
  6. 有参组装新转录本cufflinks_转录组组装软件stringtie
  7. 哇!8款帮你轻松瘦脸的美食
  8. C++面试进阶之咖啡罐问题
  9. 张钹院士:人工智能技术已进入第三代
  10. 无法连接虚拟设备 sata0:1,因为主机上没有相对应的设备——解决方案