安卓调用系统相机拍照、从相册选择图片并裁剪保存到SD卡(适配7.0)

最近在看郭神(郭霖)的第一行代码(第二版),也就是大家说的第二行代码,也算是巩固一下自己的基础吧(暴露 了自己基础不好/😏),现在已将看了一半,真的是收益良多。看到使用相机拍照的时候,自己 也就跟着demo敲了一下,觉得自己写的拍照真的是不忍直视啊,哈哈,不过demo里面没有裁剪并保存到SD卡的部分,所以这篇文章是结合郭神的demo跟自己的项目整理的一份代码,先看下效果图:</p>

最终效果图
界面很简单,两个Button一个imageView。
接下来就是枯燥的代码了,挑主要的说,源码最后会有下载地址。
1.先看下点击拍照,因为6.0以上安卓增加了权限管理,我们这里我先做了一个权限申请:
//检查权限(6.0以上做权限判断)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {    
    if (mPermissionsChecker.lacksPermissions(PERMISSIONS)) {        
        startPermissionsActivity();    
    } else {        
      openCamera();    
    }
   } else {    
      openCamera();
  }

如果有权限,则直接打开系统相机:

/** 
* 打开系统相机 
*/
private void openCamera() {    
   File file = new FileStorage().createIconFile();    
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {        
   imageUri = FileProvider.getUriForFile(MainActivity.this, "com.bugull.cameratakedemo.fileprovider", file);//通过FileProvider创建一个content类型的Uri    
   } else {        
   imageUri = Uri.fromFile(file);    
   }    
   Intent intent = new Intent();    
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {        
   intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件    
   }    
   intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照    
   intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI    
   startActivityForResult(intent, REQUEST_CAPTURE);}

这里指定保存图片路径的时候,我们做了一个判断,如果运行设备的版本低于7.0,则调用Uri.fromFile(),可以获取到图片的本地真实路径。否则,就调用FileProvider.getUriForFile(),获取一个封装过的uri对象,这是因为从安卓7.0开始,直接使用本地真实路径被认为是不安全的,会抛出FileUriExposedExCeption异常,而FileProvider则是一种特殊的内容提供其,它使用了和内容提供器类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。

上面我们提到了内容提供器,作为安卓四大组件之一,肯定是要在xml中进行注册的:

<provider    
   android:name="android.support.v4.content.FileProvider"    
   android:authorities="com.bugull.cameratakedemo.fileprovider"    
   android:grantUriPermissions="true"    
   android:exported="false">    
      <meta-data        
      android:name="android.support.FILE_PROVIDER_PATHS"        
      android:resource="@xml/file_paths" />
</provider>

provider标签里的 android:name的值是固定的。android:authorities的值要跟我们上面获取uri的时候一直,这里我使用的是:包名.fileprovider。exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 临时访问权限。<meta-data />标签里面是用来指定共享的路径。 android:resource="@xml/file_paths"就是我们的共享路径配置的xml文件,


位置如图所示

接下来看看这个file_paths是如何配置的

<?xml version="1.0" encoding="utf-8"?>
<paths    
   <paths>        
      <external-path path="demo" name="camera_photos" />    
   </paths>
</paths>

external-path就是用来指定Uri共享的,name属性的值可以随便填写,path属性的值表示共享的具体位置,设置为空,就表示共享整个SD卡,由于我在sd上保存图片的时候创建了一个名字叫demo的文件夹,所以这里我写一个demo就OK了,具体情况,自己再配置哈,这里我就不多提了。
拍照完成了就是裁剪了,我们看下裁剪的代码:

/** 
* 裁剪 
*/
private void cropPhoto() {    
File file = new FileStorage().createCropFile();    
Uri outputUri = Uri.fromFile(file);    
Intent intent = new Intent("com.android.camera.action.CROP");    
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {        
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);    
}    
intent.setDataAndType(uri, "image/*");    
intent.putExtra("crop", "true");    
intent.putExtra("aspectX", 1);    
intent.putExtra("aspectY", 1);    
intent.putExtra("scale", true);    
intent.putExtra("return-data", false);    
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);    
intent.putExtra("outputFormat", 
Bitmap.CompressFormat.JPEG.toString());    
intent.putExtra("noFaceDetection", true);     
startActivityForResult(intent, REQUEST_PICTURE_CUT);}
参数 数据类型 描述
crop String 发送裁剪信号
aspectX int X方向上的比例
aspectY int Y方向上的比例
outputX int 裁剪区的宽
outputY int 裁剪区的高
scale boolean 是否保留裁剪比例
scale return-data 是否将数据保留在bitmap中返回
MediaStore.EXTRA_OUTPUT URI 裁剪的图片保存的地址
outputFormat String 图片输出格式
noFaceDetection boolean 是否取消人脸识别

调用手机拍照的差不多就是上面这些,下面我们如何看看获取系统相册的图片。

2.首先还是来获取权限,上面已经贴过代码了,这里就不再贴出来啦!
下面看看如何打开系统相册
/** 
* 从相册选择 
*/
private void selectFromAlbum() {    
Intent intent = new Intent(Intent.ACTION_PICK);    
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");    
startActivityForResult(intent, REQUEST_PICK_IMAGE);}
为了兼容老版本,下面我们做了一些判断,如果系统是4.4以上的手机就调用handleImageOnKitKat(),否则就调用

handleImageBeforeKitKat(),之所以这样子处理,是因为4.4之后选取中的图片不再返回真实的Uri了,而是封装过的Uri,所以在4.4以上,就要对这个Uri进行解析。

4.4以上的处理方式
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {    
   imagePath = null;    
   imageUri = data.getData();    
   if (DocumentsContract.isDocumentUri(this, imageUri)) {        
   //如果是document类型的uri,则通过document id处理        
   String docId = DocumentsContract.getDocumentId(imageUri);        
   if("com.android.providers.media.documents".equals(imageUri.getAuthority())) {            
   String id = docId.split(":")[1];//解析出数字格式的id            
   String selection = MediaStore.Images.Media._ID + "=" + id;            
   imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);        
} else if ("com.android.downloads.documents".equals(imageUri.getAuthority())) {           
   Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));            
   imagePath = getImagePath(contentUri, null);        
}    
} else if ("content".equalsIgnoreCase(imageUri.getScheme())) {       
 //如果是content类型的Uri,则使用普通方式处理        
   imagePath = getImagePath(imageUri, null);    
} else if ("file".equalsIgnoreCase(imageUri.getScheme())) {        
//如果是file类型的Uri,直接获取图片路径即可        
   imagePath = imageUri.getPath();    
}    
   cropPhoto();
}
4.4以下的处理方式
private void handleImageBeforeKitKat(Intent intent) {    
   imageUri = intent.getData();    
   imagePath = getImagePath(imageUri, null);    
   cropPhoto();
}

下面附上源码,如果喜欢,记得点赞😁,如有问题,欢迎指正!!!大家一起进步。
源码下载地址:
https://pan.baidu.com/s/1Ot7YUuR2ICQOZMqN0kbmbw

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容