我的问题已经解决,查阅了几十篇相关文章,换了数种解决方案,最终解决,虽然不是按照本文来的,但关闭窗口的时候发现本文算是系统,特此转载。

最近在自己的项目里实现了一个头像选择的功能,就是先从相册里选取一张图片再调用系统的裁剪功能来制作头像,效果就像下面这样:

本以为很小的一个功能,却远远没有我想的那样简单,可以说每一步都暗藏玄机,下面就让我带大家看看这里面究竟有哪些坑。

Android 4.4之存储访问框架

首先,让我们从图片选择开始,使用隐式Intent跳转到图片选择:

private void routeToGallery() {

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

intent.addCategory(Intent.CATEGORY_OPENABLE);

intent.setType(“image/*”);

startActivityForResult(intent, GALLERY_REQUSET_CODE);

}

在回调中处理返回的图片,继而跳转至图片裁剪:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == GALLERY_REQUSET_CODE && resultCode == RESULT_OK) {

String path = data.getData().getPath();

Bitmap image = BitmapFactory.decodeFile(path);

File faceFile;

try {

faceFile = saveBitmap(image);

} catch (IOException e) {

e.printStackTrace();

return;

}

Uri fileUri = Uri.fromFile(faceFile);

routeToCrop(fileUri); //跳转到图片裁剪

}

}

private void routeToCrop(Uri uri){

Intent intent = new Intent(“com.android.camera.action.CROP”);

intent.setDataAndType(uri, “image/*”);

intent.putExtra(“crop”, true);

intent.putExtra(“aspectX”, 1);

intent.putExtra(“aspectY”, 1);

intent.putExtra(“outputX”, 150);

intent.putExtra(“outputY”, 150);

intent.putExtra(“return-data”, true);

startActivityForResult(intent, CROP_REQUEST_CODE);

}

private File saveBitmap(Bitmap bitmap) throws IOException{

File file = new File(getExternalCacheDir(), “face-cache”);

if (!file.exists()) file.createNewFile();

try (OutputStream out = new FileOutputStream(file)) {

bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);

}

return file;

}

这一段代码看似正常,但问题就出在String path = data.getData().getPath();这一句。这一段代码在Android 4.4以下是可以正常运行的,不过从Android 4.4开始这里获取到的将为一个无效的路径,这是为什么呢?

Android从4.4开始引入了一个概念:存储访问框架,简单来说就是Android提供了一个专门供用户访问资源的软件,将设备上所有可以访问资源的软件接口都整合到了一起,避免了用户只能选择一个特定软件的尴尬,在Android 4.4以下,我们发送刚才选取图片的隐式Intent,效果是这样的,需要用户去选择使用哪个应用:

Android 4.4以下

而从Android 4.4开始,就变成了这样:

Android 4.4及以上

直接打开一个资源选取的软件(这个软件平时是隐藏的,不会显示在软件列表中),其中包含了访问设备上所有可访问资源软件的接口,这个改变极大的提高的用户操作的便捷性。

不过这也带来了一个问题,从Android 4.4开始,在onActivityResult()方法的Intent中所包含的uri不再是file://类型,而是变成了content://类型,这也是为什么在Android 4.4以后调用data.getData.getPath()获取到的结果是无效的。因此,我们必须对Android 4.4以上的版本进行特殊的处理:

private void routeToGallery() {

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

intent.addCategory(Intent.CATEGORY_OPENABLE);

intent.setType(“image/*”);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

startActivityForResult(intent, GALLERY_REQUSET_CODE_KITKAT);

} else {

startActivityForResult(intent, GALLERY_REQUSET_CODE);

}

}

在回调中对不同版本分别进行处理:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

switch (requestCode) {

case GALLERY_REQUSET_CODE:

handleGalleryResult(resultCode, data);

break;

case GALLERY_REQUSET_CODE_KITKAT:

handleGalleryKitKatResult(resultCode, data);

break;

}

}

private void handleGalleryResult(int resultCode, Intent data){

// 跟之前一样

}

// Result uri is “content://” after Android 4.4

private void handleGalleryKitKatResult(int resultCode, Intent data){

File faceFile;

try {

ParcelFileDescriptor parcelFileDescriptor =

getContentResolver().openFileDescriptor(contentUri, “r”);

FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();

Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);

faceFile = saveBitmap(image);

} catch (IOException e) {

e.printStackTrace();

return;

}

Uri fileUri = Uri.fromFile(faceFile);

routeToCrop(fileUri);

}

Android 7.0之FileProvider

完成了图片的选择功能,转眼又碰到了一个问题:

Android为了提高私有文件的安全性,从7.0开始对外传递file://类型的uri会触发FileUriExposedException。因此,在分享私有文件时必须使用FileProvider。

对Android的这一改变还不太了解的同学可以看一下这两篇文章:Android 7.0 行为变更和Setting Up File Sharing。

第一步

在manifest文件中加入FileProvider:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package=“gavinli.translator”>

…>

android:name=“android.support.v4.content.FileProvider”

android:authorities=“gavinli.translator”

android:grantUriPermissions=“true”

android:exported=“false”>

android:name=“android.support.FILE_PROVIDER_PATHS”

android:resource=“@xml/filepaths” />

第二步

在xml文件夹中创建filepaths.xml文件,并声明所要分享的文件目录:

<resources>

这里的path就代表你想要分享的文件目录,而name就是具体显示在uri中的信息,最终生成的uri就像下面这样:

这种经过处理的uri可以很好的隐藏掉实际的文件路径。

第三步

在代码中对Android 7.0以上的版本进行特殊处理:

private void handleGalleryKitKatResult(int resultCode, Intent data) {

Uri fileUri;

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

// Android 7.0 “file://” uri权限适配

fileUri = FileProvider.getUriForFile(this,

“gavinli.translator”, faceFile);

} else {

fileUri = Uri.fromFile(faceFile);

}

routeToCrop(fileUri);

}

这里传入的"gavinli.translator",需要与之前在manifest文件中声明的android:authorities一致。

第四步

在裁剪图片的Intent中加入对该图片的访问权限:

private void routeToCrop(Uri uri) {

Intent intent = new Intent(“com.android.camera.action.CROP”);

intent.setDataAndType(uri, “image/*”);

// 加入访问权限

intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION

| Intent.FLAG_GRANT_READ_URI_PERMISSION);

}

最后一步

在回调中获取裁剪后的图片:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

switch (requestCode) {

case CROP_REQUEST_CODE:

Bundle bundle = data.getExtras();

Bitmap face = bundle.getParcelable(“data”);

break;

}

}

Intent的限制

你以为到这里就结束了吗?其实还远远没有。我们这里裁剪的图片是用作头像的,所以大小一般都比较小。可以当图片的大小变大后就会发现,每次裁剪后在Intent中获取到的图片其实都是缩略图。

这是因为Android对Intent中所包含数据的大小是有限制的,一般不能超过1M,否则应用就会崩溃,这就是Intent中的图片数据只能是缩略图的原因。而解决的办法也很简单,我们需要给图片裁剪应用指定一个输出文件,用来存放裁剪后的图片:

private void routeToCrop(Uri uri) {

intent.putExtra(“return-data”, false);

intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(

new File(getExternalCacheDir(), “face-cropped”)));

startActivityForResult(intent, CROP_REQUEST_CODE);

}

现在,在回调中的图片就不能再直接从Intent中获取了,而是需要先拿到Intent中的uri,再使用uri进行获取,具体的过程和之前处理uri的方式一样,这里就不再赘述了。当然,直接从之前指定的文件中读取数据也是可以的。

Android 6.0之运行时权限

不知道大家发现了没有,之前保存图片的目录都是使用的Context.getExternalCacheDir(),这个方法获取到的目录为/sdcard/Android/data/gavinli.translator/cache,是应用专属的外部存储空间,不需要声明权限。而要想使用公共的存储空间,就势必要面对一个问题:Android 6.0的运行时权限。

首先,在manifest文件中声明读取外置存储的权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package=“gavinli.translator”>

之后,在代码中加入运行时的权限申请:

private void request() {

String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};

if(ContextCompat.checkSelfPermission(this, permisson)

!= PackageManager.PERMISSION_GRANTED) {

requestPermissions(permissions, REQUEST_CODE);

} else {

// 存储图片

}

}

public void onRequestPermissionsResult(int requestCode, String[] permissions,

int[] grantResults) {

if(requestCode == REQUEST_CODE) {

if(grantResults[i] == PackageManager.PERMISSION_GRANTED) {

// 存储图片

}

}

}

后记

到这里,这一次的踩坑之旅就全部结束了,我们也看到了Android这几个版本以来一步步对权限的限制,虽然这对我们的开发产生一定的影响,但只要能提高用户的使用体验,这点困难又算的了什么呢?

android 图片自动裁剪图片,Android图片选择到裁剪之步步深坑 – 简书相关推荐

  1. ecshop图片自动本地化插件,ECSHOP下载商品图片,ECSHOP商品详情远程图片,ecshop商品远程图片保存,ecshop图片自动本地化插件,商品图片本地化

    ecshop图片自动本地化插件,ECSHOP下载商品图片,ECSHOP商品详情远程图片,ecshop商品远程图片保存,ecshop图片自动本地化插件,商品图片本地化,图片远程本地化插件,ecshop远 ...

  2. Word图片自动编号,调整图片顺序自动更新图片编号,引用该图片的地方也对应更新

    Word图片自动编号,调整图片顺序自动更新图片编号,引用该图片的地方也对应更新 1.软件环境⚙️ 2.问题描述

  3. Java做图片自动切换,简单的图片自动切换效果实现

    我们经常看见很多软件有图片自动切换的效果,看上去很高大上.其实做一个简单的效果很简单,用ViewPager实现 先是简单的布局文件 xmlns:tools="http://schemas.a ...

  4. html5 背景图片自动换,网站背景图片自动切换特效css代码

    背景图片自动切换特效-sbkko.com body { background: #000; background-attachment: fixed; word-wrap: break-word; - ...

  5. android系统自动构建,[系统集成] Android 自动构建系统

    一.简介 android app 自动构建服务器用于自动下载app代码.自动打包.发布,要建立这样的服务器,关键要解决以下几个问题: 1. android app 自动化打包 android 的打包一 ...

  6. Android Studio自动排版格式化(android排版和xml排版)

    Android Studio自动排版格式化 今天自己想让Android Studio软件自动格式化排版,结果搜了一下,都说是Ctrl + Alt + L 结果不行 后面自已经过网上一些提示和自己探索终 ...

  7. android app自动锁屏,Android开机自动启动app 不锁屏

    Android开机自动启动app 不锁屏 发布时间:2020-07-17 03:22:20 来源:51CTO 阅读:2086 作者:清水禅石 主要参考:http://life173.blog.51ct ...

  8. android程序自动卸载软件,Android应用静默安装和卸载

    App的静默安装和卸载 Android系统本身提供了安装卸载功能,但是api接口是@hide的,不是公开的接口,所以在应用级别 是无法实现静默安装和卸载的,要实现静默安装和卸载需要是系统应用,要有系统 ...

  9. html 图片自动切换插件,jquery图片切换插件

    /** * 图片切换插件 * Dependence jquery-1.7.2.min.js **/ (function ($) { //调用方式 $('#silder').imgSilder({s_w ...

最新文章

  1. python画熊猫代码_用Python Turtle 画可爱的熊猫
  2. 2019编译ffepeg vs_2020/5-Win10下ffmpeg最简编译方法
  3. 从零开始攻略PHP(8)——面向对象(下)
  4. 廖雪峰 练习 把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字...
  5. Codeforces.1129E.Legendary Tree(交互 二分)
  6. ISA Server 2004软件防火墙相关配置
  7. 51nod-1065:最小正子段和
  8. 一步步学习微软InfoPath2010和SP2010--第十二章节--管理和监控InfoPath Form Services(IPFS)(4)--监控含图片控件的Products表单...
  9. Nginx面试题(总结最全面的面试题!!!)
  10. 操作系统课后答案第三章
  11. 国际品牌拧紧工具的优缺点
  12. Windows最简单的重装系统
  13. java验证13位数书号_ISBN(国际标准书号)的校验
  14. 啦啦啦啦啦 报道 报道
  15. 【总结】1026- 一文读懂 base64
  16. 谭的c语言,c语言 谭
  17. 服务器老被攻击,该如何解决?
  18. 考研线性代数手写笔记1 行列式
  19. android黑色背景图片,Android png透明图片转jpg时背景变黑的解决方法
  20. Linux命令 ps --sort,如何对Linux ps命令输出进行排序

热门文章

  1. ppt如何转换pdf
  2. asp.net入门书籍
  3. linux服务器配置与管理_你需要知道什么才能成为系统管理员? | Linux 中国
  4. Latent Variables的理解
  5. 电子商务平台简介——Makingware
  6. 成功拿下猿辅导+斗鱼+滴滴+字节+腾讯offer,实战解析
  7. Springboot+mysql+基于VUE框架的商城综合项目设计与实现 毕业设计-附源码111612
  8. STM32标准外设库
  9. ZHS16GBK编码中汉字缺失
  10. GPRS模块与STM32的数据传输