PictureSelector 2.0 Android Q 适配之旅。
PictureSelector 至从2016年12月底提交第一个版本以来时至今日总共也已提交80多个版本,经过不断的改良和适配现在也相对趋于稳定了并在Github上收获超7900个star,再这也要感谢各位同学的鼓励和提供的一些意见。
由于Google Android Q正式版预计在2019年8月份前后推出,所以针对AndroidQ的适配已经迫在眉睫了,其中Android Q一项比较重要的变更就是在外部存储设备中为每个应用提供了一个“隔离存储沙盒”。任何其他应用都无法直接访问您应用的沙盒文件,由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。
由于PictureSelector是一款图片选择器所以自然只对存储这一块需要适配,如您需要了解更多Q相关的特性请参考 官网地址
先介绍一下沙盒机制下的三种存储方式
1、context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);// 图片存储路径
2、context.getExternalFilesDir(Environment.DIRECTORY_MOVIES);// 视频存储路径
3、context.getExternalFilesDir(Environment.DIRECTORY_DIRECTORY_MUSIC);//音频存储路径
作为一款图片选择器,沙盒机制这个操作对PictureSelector肯定是影响比较大,为了验证有哪些问题方便适配我们把targetSdkVersion改成>=29然后在Q版本机型上运行后发现果然PictureSelector基本是处于不能使用的尴尬局面,然后也收到了很多小伙伴发来的邮件和信息报bug那么废话就不多说了,下面就讲一下PictureSelector 2.0是如何适配Android Q;问题一、图片加载失败无法正常显示图片 因为MediaStore.MediaColumns.DATA 字段在Q版本被标记已过时,原因上面有提到过任何其他应用都无法直接访问您应用的沙盒文件;所以在Q上此字段返回的类似/storage/emulated/0/DCIM/Camera/IMG_20200105_19064545.jpg将无权限访问,即使你用Glide这些优秀的第三方框架也显示失败,你在Logcat过滤一下Glide日志就会发现会报如下异常:
```java.io.FileNotFoundException: open failed: EACCES (Permission
denied) ```意思是找不到文件&无权限访问
这个解决方案也很简单我们只需要通过``` MediaStore.Files.FileColumns._ID```拿到资源的Id进行拼接转成Uri的方式content://media/external/file/20246就能正常显示图片,代码如下:
long id = data.getLong(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));String path = isAndroidQ ? getRealPathAndroid_Q(id) : data.getString(data.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA));/*** 适配Android Q** @param id* @return*/private String getRealPathAndroid_Q(long id) {return QUERY_URI.buildUpon().appendPath(ValueOf.toString(id)).build().toString();}
问题二、Q版本中调用系统拍照或录视频如何保存至系统相册?
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);File cameraFile = new File("存储路径");Uri imageUri = PictureFileUtils.parUri(this, cameraFile);cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
在非Android Q版本下我们调用系统拍照按上面方式就可以了,但在Q版本上这个```File cameraFile = new File("存储路径");```就有讲究了,如果你直接传公共目录地址肯定是不行的因为你没权限访问,你只能传应用内地址```ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES);```把图片存储在应用沙盒内,但我们是需要存储到系统相册中所以显然这也不是我们所期望的,所以我们只能通过Uri来处理,这个Uri就不能通过parUri(this, cameraFile);来获得了,可以通过以下方式创建拍照或录视频Uri;
/*** 创建一条图片地址uri,用于保存拍照后的照片** @param context* @return 图片的uri*/@Nullablepublic static Uri createImageUri(final Context context) {final Uri[] imageFilePath = {null};String status = Environment.getExternalStorageState();String time = ValueOf.toString(System.currentTimeMillis());// ContentValues是我们希望这条记录被创建时包含的数据信息ContentValues values = new ContentValues(3);values.put(MediaStore.Images.Media.DISPLAY_NAME, DateUtils.getCreateFileName("IMG_"));values.put(MediaStore.Images.Media.DATE_TAKEN, time);values.put(MediaStore.Images.Media.MIME_TYPE, PictureMimeType.MIME_TYPE_IMAGE);// 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储if (status.equals(Environment.MEDIA_MOUNTED)) {values.put(MediaStore.Images.Media.RELATIVE_PATH, PictureMimeType.DCIM);imageFilePath[0] = context.getContentResolver().insert(MediaStore.Images.Media.getContentUri("external"), values);} else {imageFilePath[0] = context.getContentResolver().insert(MediaStore.Images.Media.getContentUri("internal"), values);}return imageFilePath[0];}
/*** 创建一条视频地址uri,用于保存录制的视频** @param context* @return 视频的uri*/@Nullablepublic static Uri createVideoUri(final Context context) {final Uri[] imageFilePath = {null};String status = Environment.getExternalStorageState();String time = ValueOf.toString(System.currentTimeMillis());// ContentValues是我们希望这条记录被创建时包含的数据信息ContentValues values = new ContentValues(3);values.put(MediaStore.Video.Media.DISPLAY_NAME, DateUtils.getCreateFileName("VID_"));values.put(MediaStore.Video.Media.DATE_TAKEN, time);values.put(MediaStore.Video.Media.MIME_TYPE, PictureMimeType.MIME_TYPE_VIDEO);// 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储if (status.equals(Environment.MEDIA_MOUNTED)) {values.put(MediaStore.Video.Media.RELATIVE_PATH, PictureMimeType.DCIM);imageFilePath[0] = context.getContentResolver().insert(MediaStore.Video.Media.getContentUri("external"), values);} else {imageFilePath[0] = context.getContentResolver().insert(MediaStore.Video.Media.getContentUri("internal"), values);}return imageFilePath[0];}
将生成的Uri传入 cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);这样在Q手机上拍照或录的视频则可以保存到/storage/emulated/0/DCIM/Camera/目录下了。
问题三、使用PictureSelector 2.0在Q机型上传图片失败?
java.io.FileNotFoundException: open failed: EACCES (Permission denied) 无权限访问或文件找不到,这同样也是因为我们在选择公共目录下的一些图片资源上传时你并没有访问权限所以也自然不能使用它进行上传操作,其实我们在上面讲到MediaStore.MediaColumns.DATA```已过时问题时你点开源码就会发现其实已经告诉了我们替代方案了
/**
*
* @deprecated Apps may not have filesystem permissions to
* directly
* access this path. Instead of trying to open this path
* directly, apps should use
* {@link ContentResolver#openFileDescriptor(Uri, String)}
* to gain access.
*/
通过ContentResolver#openFileDescriptor(Uri, String);方法获得访问权限,PictureSelector的解决方法是
通过getContentResolver().openFileDescriptor(uri,"r");获得文件的访问权限然后将其拷贝到自己应用沙盒中然后再进行上传操作,代码如下:
ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri,"r");FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();FileInputStream inputStream = new FileInputStream(fileDescriptor); boolean copyFileSuccess = copyFile(inputStream, outFile); if (copyFileSuccess) {Log.i(TAG,"Copy File Success"); }/*** Copy File** @param fileInputStream* @param outFile* @return* @throws IOException*/public static boolean copyFile(FileInputStream fileInputStream, File outFile) throws IOException {if (fileInputStream == null) {return false;}FileChannel inputChannel = null;FileChannel outputChannel = null;try {inputChannel = fileInputStream.getChannel();outputChannel = new FileOutputStream(outFile).getChannel();inputChannel.transferTo(0, inputChannel.size(), outputChannel);inputChannel.close();return true;} catch (Exception e) {return false;} finally {if (inputChannel != null) inputChannel.close();if (outputChannel != null) outputChannel.close();}}
以上就是PictureSelector2.0关于Android Q的适配一些方案,如果您有更好方案也欢迎提出参考;
另外:
有一些小伙伴在Q机型上使用PictureSelector2.0时总是问我为什么需要一个拷贝文件至应用沙盒内的操
作?说自己通过Uri转Path也能拿到真实路径进行上传,在这里我想说的是Uri转Path其实早在AndroidQ未出
来之前就一直可以的,这也仅仅是路径之间的相互转换而已并没有访问的权限,如果有,那么只能说明你应用
targetSdkVersion没有改成>=29或没有开启沙盒机制而已。
PictureSelector 2.0 Android Q 适配之旅。相关推荐
- Android Q适配攻略(一)(图标适配)
Android Q之提前适配攻略(一)(图标适配) Android Q之提前适配攻略(二)(后台定位适配) Android Q之提前适配攻略(三)(唯一标识符更改) Android Q之提前适配攻略( ...
- android q测试机型,小米9安卓Q系统刷机包开启测试 小米Android Q适配机型一览
7月9日,小米9 Android Q Beta版已开启测试.MIUI官方微博宣布,小米9 Android Q Beta版已开启测试,正在招募尝鲜用户.看名字就可以看出,新系统是基于Android Q打 ...
- android 适配红米,小米MIUI放出Android Q适配计划,11款手机参与,包括红米Note 7
原标题:小米MIUI放出Android Q适配计划,11款手机参与,包括红米Note 7 谷歌在今年五月份发布了最新的Android Q系统,虽然很多国产手机还没有升级到Android P,但是还是有 ...
- Android Q 适配指南
在Android 10开始版本中,官方的改动较大,相应的开发者适配成本还是很高的. 这里按照2019.11.11 google android q workshop流程,大概说明一下Android Q ...
- V小米Android Q适配计划公布:怒赞
6月14日消息,MIUI管理员在论坛发布"MIUI关于Android Q升级适配计划的通知". 目前计划适配机型有小米9.红米K20 Pro.小米8.小米8透明探索版.小米8屏幕指 ...
- 小米android q适配机型,小米公布首批适配Android Q机型,然而“诚意”却不是很足?...
原标题:小米公布首批适配Android Q机型,然而"诚意"却不是很足? 最近谷歌"断供"华为一事可谓是在业界引起了不小的波澜,有些人在得知这一消息之后就此选择 ...
- Android Q 适配,看这篇就妥了
转载自公众号:刘望舒 作者: 吃猫猫的鱼 地址:https://juejin.im/post/5cad5b7ce51d456e5a0728b0 1. 导读 Android Q Beta 1刚出,讲 ...
- Android Q 适配详细操作
去年(2018年)我们陆续收到来自各个应用市场平台的API升级通知,下面以阿里应用分发平台通知为例: 亲爱的开发者:为保障用户合法权益,建立健康的移动应用环境,2018年7月18日,国内主流应用预置与 ...
- Android Q 适配指南 让你少走一堆弯路
导读 文中链接请自行科学上网 Android Q Beta 1刚出,讲道理国内是不到下半年不用理睬Q的,但是上月末的一封华为要求适配Q的邮件要求我们在5月底之前完成相关适配,不然应用会被下架. 一开始 ...
最新文章
- ESP32 One-Wire驱动功能
- bootstrap-table页码ALL显示为NAN
- Google 修补多项 Android 高危漏洞
- 软件开发依据的标准或法律法规_第178篇丨直真科技:官宣!定制软件开发不应该采用完工百分比法确认收入...
- linux查找文件find
- 关于char[]转换成LPCWSTR的有关问题
- 当前工作目录Python
- 180C. Letter
- inline元素的间距问题
- html table的边框线怎么变圆角_实现CSS3中的border-radius(边框圆角)示例代码
- scrapy安装_「从零开始Python爬虫」1.8.3 Scrapy-Splash的安装
- 精讲了33道二叉树经典题目之后,我总结了这些,帮你一举搞定二叉树
- 《我们不一样》β冲刺_3
- visio 2013安装失败正在回滚
- Qt 常用文件对话框及消息对话框使用
- ae合成设置快捷键_AE中常用的快捷键,你了解多少?(五)
- 【HTML、CSS、JS】注册页面实现(带验证码,密码强度验证,表格提交验证等)
- 战地指挥官 服务器维护,战地指挥官有哪些常见问题以及解决方法
- 网页中通过js修改img的src属性刷新图片时,图片缓存问题现象表述及问题解决
- 度量衡计算工具_中国古代度量衡领先世界的计量工具