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 Q                        if(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 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; //单位kb                        if (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 Q                        synchronized (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);

更多相关文章

  1. android 解决asset下面文件太大报错问题
  2. 【Tech-Android-Other】android操作sdcard中的多媒体文件——音
  3. Android-手机归属地和座机归属地查询
  4. Android学习笔记(一)---一定要看的 Android(安卓)资源目录的相关知
  5. Android借助系统自带图片裁剪集成图片选择以及7.0适配
  6. Android----xml文件中的控件的id设置
  7. android JNI的使用示例详解
  8. android中的app加固
  9. Android(安卓)studio下Cmake配置编译开发jni总结

随机推荐

  1. Android(安卓)读取sdcard指定目录文件
  2. FlycoDialog_Master 酷炫吊炸天的Android
  3. Android手势库
  4. Tabhost
  5. 更新Anadroid SDK Tooks之后,Eclipse提示N
  6. ScrollView 嵌套 RecyclerView 显示不完
  7. Android(安卓)再按一次退出程序三种办法
  8. Android(安卓)-- 过滤器相关
  9. 第一个Android程序--Hello Android
  10. android 界面布局