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.读取和写入
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 中扫描出来的文件可能不存在的情况,没有直接的好方法可以解决过滤。
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); // 将读取的输入流写入到输出流
}
}
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);