知乎 Matisse 图片库在 Android10 上拍照,预览问题

用户头像
三爻
关注
发布于: 2020 年 06 月 12 日

前言

Android上的文件管理混乱一直是诟病,用户也基本上看着Android文件管理器的文件头疼,乱七八糟,随便一个app就在根目录上生成文件,虽然这方便了开发人员,但是对用户来说这些文件根本是无用的,而且用户也不关心这个文件是做什么的,所以Google 在Android10上做出了改进,Android10 文件管理 跟iOS 系统文件管理看齐,采用类似的沙盒机制。

背景

最近升级了 build.gradle 文件中的targetSdk从28升到29了,测试人员在测试拍照图片选择时,Android10上出现了crash,网上搜索上大部分说是在AndroidManifest.xml 中 application 标签中添加android:requestLegacyExternalStorage="true", 就选择退出分区存储,应用就可以继续运行了,但是这是个取巧的方法,在Android11 上这个方法就失效了,官网上也给出一个解决方案改用 preserveLegacyExternalStorage =“true”,也是可以的,但是还是要注意重新安装的应用,

为了以后能有更好的体验还是要去解决分区存储模型的使用。

排查的的过程

前提

APP上权限都是正确的,Matisse版本是0.5.3-beta3.

排查

我这边首先出现的是报错是:

java.lang.NullPointerException

空指针异常,在报错路径中,找到了报错地方 Matisse.obtainPathResult(data) 返回list里面为空了,无法保存拍照的图片位置了,在GitHub--Matisse库中issue 中找到了同样的一些问题,根据说法还是因为Android10上做了分区储存限制,媒体文件共享合集,Android10上拍照要用uri 加载图片了,解决方法是在

MediaStoreCompat类中加一些方法

MediaStoreCompat类
之前
....
public void dispatchCaptureIntent(Context context, int requestCode) {
....
if (captureIntent.resolveActivity(context.getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException e) {
e.printStackTrace();
}
....
}
}
.....
修改后
public void dispatchCaptureIntent(Context context, int requestCode) {
....
if (captureIntent.resolveActivity(context.getPackageManager()) != null) {
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
mCurrentPhotoUri = createImageUri(context);
} else {
File photoFile = null;
try {
photoFile = createImageFile(context);
} catch (IOException e) {
e.printStackTrace();
}
....
}
}
}
private Uri createImageUri(Context context) {
String status = Environment.getExternalStorageState();
if (status.equals(Environment.MEDIA_MOUNTED)) {
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new
ContentValues());
} else {
return context.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, new
ContentValues());
}
}

这样拍照就能用了,就能在Android10 上获取uri了,这个不能获取到path,这样就能愉快的拍照了,但是还没完,在预览图片的时候有可能出现应用ANR,应用无反应卡死,在GitHub--Matisse库中issue 中也找到了同样的一些问题,经过断点调试发现了原因getBitmapBound() 方法中decodeStream大文件耗时太久了

PhotoMetadataUtils类
之前
public static Point getBitmapBound(ContentResolver resolver, Uri uri) {
...
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
is = resolver.openInputStream(uri);
BitmapFactory.decodeStream(is, null, options);
...
}
之后
public static Point getBitmapBound(ContentResolver resolver, Uri uri) {
...
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
is = new FileInputStream(uri.getPath());;
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPreferQualityOverSpeed = false;
BitmapFactory.decodeFileDescriptor(is.getFD(), null, options);
...
}

可以看出,改用了decodeFileDescriptor 方法,并降低了图片质量。这样Matisse上的问题应该是解决了。但是在业务上我需要压缩图片并上传,在压缩上就出现问题了,Android10上是不允许去访问别的应用图片的。

BitmapUtil压缩类
之前
public static String compressImage(String filePath) {
...
//原文件
File oldFile = new File(filePath);
//压缩文件路径 照片路径/
String targetPath = oldFile.getPath();
...
}
之后
public static Uri compressImage(Context context, Uri fileUri) {
...
ParcelFileDescriptor parcelFileDescriptor = null;
//原文件
try {
parcelFileDescriptor = context.getContentResolver().openFileDescriptor(fileUri
, "r", null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (parcelFileDescriptor == null) {
return fileUri;
}
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
FileInputStream inputStream= new FileInputStream(fileDescriptor);
File file = new File(context.getCacheDir(),
ImageUtil.getImageAbsolutePath(context, fileUri));
FileOutputStream outputStream = null;
try {
outputStream=new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
IOUtils.Copy(inputStream,outputStream);
Logger.d(file.length());
//压缩文件路径 照片路径/
String targetPath = file.getPath();
...
}

这里主要是改用了FileDescriptor,把图片复制到应用缓存中,然后在缓存中压缩文件,需要注意的是IOUtils.Copy()方法 实现推荐用FileChannel ,我开始使用的是while循环读取的实现,但是Glide一直报错,在列表不显示我选择的图片,

setDataSource failed: status = 0xFFFFFFEA in setDataSource()

先去glide官方GitHub 上找找原因,无果,开始打印复制过来的file 的字节大小,说明也有,然后用Android studio 的Device File Explorer 工具找到了应用缓存中复制的file图片文件,下载出来是空字节,就尝试用了FileChannel实现,就完成了。

结束

上面可能有些说法不对,或理解错误的地方,大牛轻拍 = =,还有就是文章上有什么布局,结构上,措辞上的做得不够好的地方,希望大家能给点意见。



发布于: 2020 年 06 月 12 日 阅读数: 1837
用户头像

三爻

关注

千里之行,始于足下 2017.10.20 加入

人的所以拖沓都是并非真正的热爱

评论 (4 条评论)

发布
用户头像
文章的话建议加个头条,这样展示效果好。
2020 年 06 月 12 日 17:42
回复
好的,以后注意,头条是加个封面?
2020 年 06 月 12 日 18:28
回复
用户头像
感谢分享干货,InfoQ首页推荐。
2020 年 06 月 12 日 15:27
回复
用户头像
讲的非常好!很精辟
2020 年 06 月 12 日 11:47
回复
没有更多了
知乎Matisse图片库在Android10上拍照,预览问题