安卓开发——拍照、裁剪并保存为头像报错：裁剪图片无法保存的-布布扣-bubuko.com

- [首页]
- [新闻]
- [文章]
- [教程]

[登录] [注册]

- 首页
- Web开发
- Windows开发
- 编程语言
- 数据库技术
- 移动平台
- 系统服务
- 微信
- 设计
- 布布扣
- 其他
- 数据分析

首页 > 移动平台 > 详细

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

时间：2017-09-30 20:22:52 阅读：235 评论：0 收藏：0 [点我收藏+]

标签：结果 pen strong ica toa car 生命 detection xposed

在做学校大创项目的安卓开发时，需要从相册获取图片或者拍照，然后裁剪保存为头像。由于我是第一次弄安卓开发，也对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可能并不适用于现在了，比如现在越来越严格的权限问题。

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

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

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

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

标签：结果 pen strong ica toa car 生命 detection xposed

原文：http://www.cnblogs.com/Roe-Ross/p/7615881.html

踩
(0)
赞
(0)
举报
评论 一句话评论（0）
共0条
登录后才能评论！
分享档案
更多>
2018年05月01日 (1014)
2018年04月30日 (1327)
2018年04月29日 (1409)
2018年04月28日 (1466)
2018年04月27日 (1548)
2018年04月26日 (1298)
2018年04月25日 (1082)
2018年04月24日 (1144)
2018年04月23日 (999)
2018年04月22日 (1214)
最新文章
更多>

- web开发(二) Servlet中response、request乱码问题解决 2018-05-01
- 老王开枪源码(Mr.Wang_shooting.py) 2018-05-01
- 区块链vs传统数据库：分布式运行有何优势？ 2018-05-01
- Websocket 请求与订阅示例 2018-05-01
- pymysql 交互 2018-05-01
- python_迭代器协议 2018-05-01
- 字符型数组输入时不要有&符号 2018-05-01
- [BZOJ4942][NOI2017]整数(线段树+压位) 2018-05-01
- 服务器常用的状态码及其对应的含义 2018-05-01
- 面向对象的一些方法与属性 2018-05-01

最新新闻
更多>

- “数学王子”高斯诞辰241周年 谷歌涂鸦纪念 2018-05-01
- Whatsapp创始人离开FB 因保护数据隐私理念不合 2018-05-01
- 2018爱奇艺粉丝嘉年华开幕 泡泡社区独立APP即将上线 2018-04-30
- 支付宝发布五一境外移动支付数据：90后首次超80后 2018-05-01
- 下载：Windows 10 2018四月更新正式版ISO镜像发布 2018-05-01
- 一线 | 美图秀秀品牌升级 从图片处理工具升级为社区平台 2018-05-01
- 小猪佩奇遭抖音封杀，这只网红猪是如何成为“社会一姐”的？ 2018-04-30
- 阿里巴巴阻止迪拜加密货币公司使用“Alibaba”遭驳回 2018-05-01
- 盖茨道出大学期间最想做的事：应当让自己更善于社交 2018-04-30
- Microsoft .NET Framework 4.7.2 正式版发布 2018-05-01

教程昨日排行
更多>

- 1.form
- 2.选择器:disabled
- 3.PHP String
- 4.caption-side
- 5.padding-right
- 6.font-style
- 7.Python 多线程
- 8.Swift 环境搭建
- 9.jEasyUI 创建拖放的购物车
- 10.MySQL LIKE 子句

友情链接
兰亭集智 国之画 cnbeta CSDN 博客园 百度统计 站长统计
阳和移动开发 汇智网 天码营 HarriesBlog 程序员客栈 PHP教程 插件网
关于我们 - 联系我们 - 留言反馈
© 2014 bubuko.com 版权所有 鲁ICP备09046678号-4
打开技术之扣，分享程序人生！

鲁公网安备 37021202000002号

↑
↓
分享到 百度一下

分享到

更多...
分享到：
分享到

- 一键分享
- QQ空间
- 新浪微博
- 百度云收藏
- 微信
- 人人网
- 腾讯微博
- 百度相册
- 开心网
- 腾讯朋友
- 百度贴吧
- 豆瓣网
- 搜狐微博
- 百度新首页
- QQ好友
- 和讯微博

更多...