Android Q版本出来也有一段时间了,但是大部分我们都没有去适配过它,首选说一下Android Q版,最大的亮点集中在隐私安全和智能交互两方面,其中在隐私安全方面Android Q增加了外部存储策略变更、位置权限的后台访问限制、后台应用(不限于摄像头、麦克风等)的启动限制、设备识别码限制权限收敛举措,其将限制应用在安卓设备上接受通话日志和短信权限的能力,并不再通过安卓通讯录API提供联系人互动数据,这些举措都是针对社交属性的权限进行的。这里说再多的解释还不如大家去官方文档看的更加详细,我这里只记录下自已适配Q版有关于读取系统多媒体文件的心得(参考)。

  • 官方文档

  • 内部存储访问变化:

  • 多媒体文件(图片,视频等)
    1.使用MediaStore来获取图片和视频,然后通过Cursor来得到多媒体文件的相关信息

         ``MediaStore.Images.Media``表示媒体文件为图片``MediaStore.Images.Media``表示媒体文件为视频
    

图片:

 Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;String[] projection = {MediaStore.Images.Media._ID,MediaStore.Images.Media.DATA,MediaStore.Images.Media.DATE_ADDED,MediaStore.Images.Thumbnails.DATA};//全部图片String where = MediaStore.Images.Media.MIME_TYPE + "=? or "+ MediaStore.Images.Media.MIME_TYPE + "=? or "+ MediaStore.Images.Media.MIME_TYPE + "=?";//指定格式String[] whereArgs = {"image/jpeg", "image/png", "image/jpg"};//查询Cursor mCursor = activity.getContentResolver().query(mImageUri, projection, null, null,null);if (mCursor != null) {while (mCursor.moveToNext()) {// 获取图片的路径int thumbPathIndex = mCursor.getColumnIndex(MediaStore.Images.Thumbnails.DATA);int timeIndex = mCursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED);int pathIndex = mCursor.getColumnIndex(MediaStore.Images.Media.DATA);int id = mCursor.getColumnIndex(MediaStore.Images.Media._ID);Long date = mCursor.getLong(timeIndex)*1000;String filepath,thumbPath;//适配Android Qif(Build.VERSION.SDK_INT == Build.VERSION_CODES.P){thumbPath  =MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon().appendPath(String.valueOf(mCursor.getInt(id))).build().toString();filepath = thumbPath;if(FileManagerUtils.isContentUriExists(MSApplication.getmContext(), Uri.parse(filepath))){MediaData fi = new MediaData(id,MediaConstant.IMAGE,filepath,"",getPhotoUri(mCursor),date,"",false);mediaBeen.add(fi);}}else{thumbPath = mCursor.getString(thumbPathIndex);filepath = mCursor.getString(pathIndex);//判断文件是否存在,存在才去加入boolean b = FileManagerUtils.fileIsExists(filepath);if (b) {File f = new File(filepath);MediaData fi = new MediaData(id,MediaConstant.IMAGE,filepath,thumbPath,null,date,f.getName(),false);mediaBeen.add(fi);}}}mCursor.close();}

视频:

final List<MediaData> videoList = new ArrayList<>();Uri mVideoUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;String[] projection = {MediaStore.Video.Thumbnails._ID, MediaStore.Video.Thumbnails.DATA, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE, MediaStore.Video.Media.DATE_ADDED, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATE_MODIFIED};//全部视频String where = MediaStore.Images.Media.MIME_TYPE + "=? or "+ MediaStore.Video.Media.MIME_TYPE + "=? or "+ MediaStore.Video.Media.MIME_TYPE + "=? or "+ MediaStore.Video.Media.MIME_TYPE + "=? or "+ MediaStore.Video.Media.MIME_TYPE + "=? or "+ MediaStore.Video.Media.MIME_TYPE + "=? or "+ MediaStore.Video.Media.MIME_TYPE + "=? or "+ MediaStore.Video.Media.MIME_TYPE + "=? or "+ MediaStore.Video.Media.MIME_TYPE + "=?";String[] whereArgs = {"video/mp4", "video/3gp", "video/aiv", "video/rmvb", "video/vob", "video/flv","video/mkv", "video/mov", "video/mpg"};Cursor mCursor = activity.getContentResolver().query(mVideoUri,projection, null, null, MediaStore.Video.Media.DATE_ADDED + " DESC ");if (mCursor != null) {while (mCursor.moveToNext()) {// 获取视频的路径int videoId = mCursor.getInt(mCursor.getColumnIndex(MediaStore.Video.Media._ID));String path;if(Build.VERSION.SDK_INT == Build.VERSION_CODES.P){path =MediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendPath(String.valueOf(videoId)).build().toString();}else{path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Video.Media.DATA));}long duration = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Video.Media.DURATION));long size = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Video.Media.SIZE)) / 1024; //单位kbif (size < 0) {//某些设备获取size<0,直接计算Log.e("dml", "this video size < 0 " + path);size = new File(path).length() / 1024;}String displayName = mCursor.getString(mCursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME));//用于展示相册初始化界面int timeIndex = mCursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED);Long date = mCursor.getLong(timeIndex) *1000;//需要判断当前文件是否存在  一定要加,不然有些文件已经不存在图片显示不出来。这里适配Android Qsynchronized (activity) {boolean fileIsExists;if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {fileIsExists = FileManagerUtils.isContentUriExists(MSApplication.getmContext(), Uri.parse(path));if (fileIsExists) {videoList.add(new MediaData(videoId, MediaConstant.VIDEO, path, "", getVideoUri(mCursor), duration, date, displayName, false));}} else {fileIsExists = FileManagerUtils.fileIsExists(path);if (fileIsExists) {videoList.add(new MediaData(videoId, MediaConstant.VIDEO, path, path, null, duration, date, displayName, false));}}}}mCursor.close();}

2.使用Content Uri
在以上代码中我们可以看到都适配了Andorid Q,这是为什么呢?因为DATA 数据在 Android Q 以前代表了文件的路径,但在 Android Q上该路径无法被访问,因此没有意义。所以既然DATA不可用,那么我们可以用ID 在 Android Q 上读取文件,即使用ID拼装出Content Uri,如1中的代码:

 图片:int id = mCursor.getColumnIndex(MediaStore.Images.Media._ID);String thumbPath  = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon().appendPath(String.valueOf(mCursor.getInt(id))).build().toString();
 视频:int videoId = mCursor.getInt(mCursor.getColumnIndex(MediaStore.Video.Media._ID));String path = MediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendPath(String.valueOf(videoId)).build().toString();

3.读取和写入

  • 在Andorid Q 中,当我们通过Content Uri拿到路径之后,是无法通过File来判断文件是否存在,即file.exist()会总是为False。所以我们借助于ContentResolver来判断
public static boolean isContentUriExists(Context context, Uri uri){if (null == context) {return false;}ContentResolver cr = context.getContentResolver();try {AssetFileDescriptor afd = cr.openAssetFileDescriptor(uri, "r");if (null == afd) {return false;} else {try {afd.close();} catch (IOException e) {}}} catch (FileNotFoundException e) {return false;}return true;}

这种方法最大的问题即是,对应于一个同步 I/O 调用,易造成线程等待。因此,目前对于 MediaStore 中扫描出来的文件可能不存在的情况,没有直接的好方法可以解决过滤。

  • 在读取和写入时,我们可以借助 Content Uri 从 ContentResolver 里面拿到 AssetFileDescriptor,然后就可以拿到 InputSteam 或 OutputStream,那么接下来的读取和写入就非常自然,如下所示:
public static void copy(File src, ParcelFileDescriptor parcelFileDescriptor) throws IOException {FileInputStream istream = new FileInputStream(src);try {FileOutputStream ostream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());try {IOUtil.copy(istream, ostream);} finally {ostream.close();}} finally {istream.close();}
}public static void copy(ParcelFileDescriptor parcelFileDescriptor, File dst) throws IOException {FileInputStream istream = new FileInputStream(parcelFileDescriptor.getFileDescriptor());try {FileOutputStream ostream = new FileOutputStream(dst);try {IOUtil.copy(istream, ostream);} finally {ostream.close();}} finally {istream.close();}
}public static void copy(InputStream ist, OutputStream ost) throws IOException {byte[] buffer = new byte[4096];int byteCount = 0;while ((byteCount = ist.read(buffer)) != -1) {  // 循环从输入流读取 buffer字节ost.write(buffer, 0, byteCount);        // 将读取的输入流写入到输出流}
}
  • 保存媒体文件到公共区域,这里仅以 Video 示例,Image、Downloads 基本类似:
public static Uri insertVideoIntoMediaStore(Context context, String fileName) {ContentValues contentValues = new ContentValues();contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);contentValues.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());contentValues.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");Uri uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues);return uri;
}

4.关于视频Video的缩略图问题,在 Android Q 上已经拿不到 Video 的 Thumbnail 路径了,又由于没有暴露 Video 的 Thumbnail 的 id ,导致了 Video 的 Thumbnail 只能使用实时获取 Bitmap 的方法,即

private Bitmap getThumbnail(ContentResolver cr, long videoId) throws Throwable {return MediaStore.Video.Thumbnails.getThumbnail(cr, videoId, MediaStore.Video.Thumbnails.MINI_KIND,null);
}

5.在3中我们需要通过FileInputStream 来出来文件,比如加载到ImageView,我感觉有点麻烦,所以这里我是用了Glide来加载Uri,这样也就没有和流相关的操作了。

public static Uri getPhotoUri(Cursor cursor) {return getMediaUri(cursor, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);}public static Uri getVideoUri(Cursor cursor) {return getMediaUri(cursor, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);}public static Uri getMediaUri(Cursor cursor, Uri uri) {String id = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns._ID));return Uri.withAppendedPath(uri, id);}

加载:

Glide.with(XXApplication.getmContext()).load(thumbPathUri).apply(options).into(iv_item_image);

适配Android Q上读取多媒体文件相关推荐

  1. 适配Android Q拍照和读取相册图片

    Google发行Android Q版本也有很长一段时间了,华为应用市场已经要求要适配Android Q版本了,所以,我们也要去对Android Q进行适配. 先讲一下咱们这节用到的新特性 Androi ...

  2. 适配Android Q指南

    一 .行为变更:所有应用 Android Q 平台包含一些行为变更,这些变更可能会影响您的应用.以下行为变更将影响在 Android Q 上运行的所有应用,无论其采用哪种 targetSdkVersi ...

  3. 小米android q适配机型,小米公布首批适配Android Q机型,然而“诚意”却不是很足?...

    原标题:小米公布首批适配Android Q机型,然而"诚意"却不是很足? 最近谷歌"断供"华为一事可谓是在业界引起了不小的波澜,有些人在得知这一消息之后就此选择 ...

  4. 华为android o适配名单,华为给出首批升级名单,这8款华为手机率先适配Android Q 10系统...

    原标题:华为给出首批升级名单,这8款华为手机率先适配Android Q 10系统 昨天,在谷歌的I/O大会上,谷歌正式向消费者介绍了Android Q 10系统中的新功能,除了自家的Pixel设备率先 ...

  5. 国内首家!网易易盾加固第一时间适配Android Q Beta

    北京时间3月14日消息,谷歌在今天正式发布Android Q首个开发者预览版本,并对Pixel用户提供更新.而在发布后的第一时间,网易易盾加固已经完美适配. 由于兼容性以及对未来趋势的把握上做的非常到 ...

  6. Android 腾讯tbs适配Android Q啦~~

    前言: 首先说声抱歉,去年升级App目标版本到Android Q,使用腾讯tbs时在一加7plus,华为mate20上报错了,还吐槽了他们,不过最后还是通过降低版本到28解决了,最近发现腾讯tbs已经 ...

  7. android 切换字体崩溃,androidx - 在Android 10 / Android Q上使用捆绑的ttf字体时崩溃 - 堆栈内存溢出...

    将我的Android应用的目标级别从28更新为29(Android 10)后,该应用在Pixel 3(使用Android 10)上崩溃了. 使用的版本 Android Gradle插件3.5.0 摇篮 ...

  8. 红米k20适配android q,红米K20 Pro现已推送Android Q内测版 增强隐私保护

    8 月 8 日晚间,红米官方微博宣布,红米K20 Pro现已推送Android Q开发者内测版.Android Q是谷歌最新的系统版本,目前还处在测试阶段,而最后一个测试版系统也于昨天正式推送,意味着 ...

  9. Android Q 上的Biometric生物识别之Face人脸识别流程

    第一部分,人脸识别身份验证HIDL 借助人脸识别身份验证功能,用户只需要将自己的面孔对准设备即可将其解锁.Android 10 增加了对一种新的人脸识别身份验证堆栈的支持,这种堆栈可安全处理摄像头帧, ...

最新文章

  1. load python txt文件_详解Python中numpy.loadtxt()读取txt文件
  2. 《算法竞赛进阶指南》打卡-基本算法-AcWing 90. 64位整数乘法:位运算
  3. Apache常见配置错误
  4. 【Qt】数据库实战(一)
  5. 系统调用软中断处理程序system_call分析
  6. 细说ASP.NET Core与OWIN的关系
  7. codeforces438 D. The Child and Sequence
  8. CLR via C# 之管中窥豹(一)
  9. 7-5 考试座位号 (15 分)
  10. java实例分析宠物商店_java实例分析:宠物商店.ppt
  11. Tensorflow:tensor数据类型转换、计算和变换
  12. 线性代数知识荟萃(4)——矩阵相抵
  13. 时间管理PPT课件该怎么做?
  14. 软体机器人与类脑智能机器人
  15. word删除空白页删不了怎么办?Word怎么删除空白页?
  16. php汉字转拼音百家姓版,百家姓详(带拼音).ppt
  17. [渝粤教育] 山东职业学院 话说铁道 参考 资料
  18. 申请https证书相关说明
  19. FX110网:鳄鱼线(Alligator)指标的操作应用
  20. Linux SWAP 交换分区配置说明(转)

热门文章

  1. 二叉树之前序遍历、中序遍历、后续遍历
  2. java改成字体_更改JRE字体配置
  3. 健康开怀一辈子(转)
  4. 学习就是这样一条时而郁郁寡欢,时而开怀大笑的路
  5. 那些年,美团里的年轻人
  6. QQ空间JS代码原理
  7. 笔记随笔(ing):计算机视觉部分
  8. JavaScript前台判空
  9. swift对接整合ceph
  10. iOS播放/渲染/解析MIDI