AndroidQ 沙箱适配多媒体文件(小结)
综述
所有内容的访问变化见下图:
外部媒体文件的扫描,读取和写入
最容易被踩坑的应该是,对外部媒体文件,照片,视频,图片的读取或写入。
扫描
首先是扫描。扫描依然是使用queryMediaStore的方式。一句话介绍MediaStore,MediaStore就是Android系统中的一个多媒体数据库。代码如下图所示,以搜索本地视频为例子:
protectedListdoInBackground(Void...params){ mContentResolver=context.getContentResolver(); String[]mediaColumns={MediaStore.Video.Media._ID,MediaStore.Video.Media.DATA, MediaStore.Video.Media.TITLE,MediaStore.Video.Media.MIME_TYPE, MediaStore.Video.Media.DISPLAY_NAME,MediaStore.Video.Media.SIZE, MediaStore.Video.Media.DATE_ADDED,MediaStore.Video.Media.DURATION, MediaStore.Video.Media.WIDTH,MediaStore.Video.Media.HEIGHT}; CursormCursor=mContentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,mediaColumns, null,null,MediaStore.Video.Media.DATE_ADDED); if(mCursor==null){ returnnull; } //注意,DATA数据在AndroidQ以前代表了文件的路径,但在AndroidQ上该路径无法被访问,因此没有意义。 ixData=mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA); ixMime=mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.MIME_TYPE); //ID是在AndroidQ上读取文件的关键字段 ixId=mCursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID); ixSize=mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE); ixTitle=mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE); allImages=newArrayList (); mTotalVideoCount=0; mCursor.moveToLast(); while(mCursor.moveToPrevious()){ if(addVideo(mCursor)==0){ continue; }elseif(addVideo(mCursor)==1){ break; } } mCursor.close(); returnallImages; }
既然data不可用,就需要知晓id的使用方式,首先是使用id拼装出contenturi,如下所示:
publicgetRealPath(Stringid){
returnMediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build().toString();
}
Image同理换成MediaStore.Images。
读取和写入
其次,是读取contenturi。这里需要注意Filefile=newFile(contentUri);是无法获取到文件的。file.exist()为false。
那么就产生两个问题:1.如何确定ContentUri形式的文件存在2.如何读取或写入文件。
首先,对于ContentUri的读取,必须借助于ContentResolver。
其次,对于1,没有找到Google文档中提供比较容易的API,只能采用打开FileDescriptor是否成功的形式,代码如下所示:
publicbooleanisContentUriExists(Contextcontext,Uriuri){
if(null==context){
returnfalse;
}
ContentResolvercr=context.getContentResolver();
try{
AssetFileDescriptorafd=cr.openAssetFileDescriptor(uri,"r");
if(null==afd){
iterator.remove();
}else{
try{
afd.close();
}catch(IOExceptione){
}
}
}catch(FileNotFoundExceptione){
returnfalse;
}
returntrue;
}
这种方法最大的问题即是,对应于一个同步I/O调用,易造成线程等待。因此,目前对于MediaStore中扫描出来的文件可能不存在的情况,没有直接的好方法可以解决过滤。
对于问题2,如1所示,可以借助ContentUri从ContentResolver里面拿到AssetFileDescriptor,然后就可以拿到InputSteam或OutputStream,那么接下来的读取和写入就非常自然,如下所示:
publicstaticvoidcopy(Filesrc,ParcelFileDescriptorparcelFileDescriptor)throwsIOException{
FileInputStreamistream=newFileInputStream(src);
try{
FileOutputStreamostream=newFileOutputStream(parcelFileDescriptor.getFileDescriptor());
try{
IOUtil.copy(istream,ostream);
}finally{
ostream.close();
}
}finally{
istream.close();
}
}
publicstaticvoidcopy(ParcelFileDescriptorparcelFileDescriptor,Filedst)throwsIOException{
FileInputStreamistream=newFileInputStream(parcelFileDescriptor.getFileDescriptor());
try{
FileOutputStreamostream=newFileOutputStream(dst);
try{
IOUtil.copy(istream,ostream);
}finally{
ostream.close();
}
}finally{
istream.close();
}
}
publicstaticvoidcopy(InputStreamist,OutputStreamost)throwsIOException{
byte[]buffer=newbyte[4096];
intbyteCount=0;
while((byteCount=ist.read(buffer))!=-1){//循环从输入流读取buffer字节
ost.write(buffer,0,byteCount);//将读取的输入流写入到输出流
}
}
保存媒体文件到公共区域
这里仅以Video示例,Image、Downloads基本类似:
publicstaticUriinsertVideoIntoMediaStore(Contextcontext,StringfileName){
ContentValuescontentValues=newContentValues();
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");
Uriuri=context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,contentValues);
returnuri;
}
这里所做的,只是往MediaStore里面插入一条新的记录,MediaStore会返回给我们一个空的ContentUri,接下来问题就转化为往这个ContentUri里面写入,那么应用上一节所述的代码即可实现。
Video的Thumbnail问题
在AndroidQ上已经拿不到Video的Thumbnail路径了,又由于没有暴露Video的Thumbnail的id,导致了Video的Thumbnail只能使用实时获取Bitmap的方法,如下所示:
privateBitmapgetThumbnail(ContentResolvercr,longvideoId)throwsThrowable{
returnMediaStore.Video.Thumbnails.getThumbnail(cr,videoId,MediaStore.Video.Thumbnails.MINI_KIND,
null);
}
可以进去看AndroidSDK的实现,其中最关键的部分是:
Stringcolumn=isVideo?"video_id=":"image_id=";
c=cr.query(baseUri,PROJECTION,column+origId,null,null);
if(c!=null&&c.moveToFirst()){
bitmap=getMiniThumbFromFile(c,baseUri,cr,options);
if(bitmap!=null){
returnbitmap;
}
}
进一步再进去看,可以发现直接就把Video/Image文件打开计算Thumbnail。
privatestaticBitmapgetMiniThumbFromFile(
Cursorc,UribaseUri,ContentResolvercr,BitmapFactory.Optionsoptions){
Bitmapbitmap=null;
UrithumbUri=null;
try{
longthumbId=c.getLong(0);
StringfilePath=c.getString(1);
thumbUri=ContentUris.withAppendedId(baseUri,thumbId);
ParcelFileDescriptorpfdInput=cr.openFileDescriptor(thumbUri,"r");
bitmap=BitmapFactory.decodeFileDescriptor(
pfdInput.getFileDescriptor(),null,options);
pfdInput.close();
}catch(FileNotFoundExceptionex){
Log.e(TAG,"couldn'topenthumbnail"+thumbUri+";"+ex);
}catch(IOExceptionex){
Log.e(TAG,"couldn'topenthumbnail"+thumbUri+";"+ex);
}catch(OutOfMemoryErrorex){
Log.e(TAG,"failedtoallocatememoryforthumbnail"
+thumbUri+";"+ex);
}
returnbitmap;
}
这个API毫无疑问设计的非常不合理,没有暴露Thumbnail的系统缓存给开发者,造成了每次都要重新I/O计算的极大耗时。强烈呼吁AndroidQ的正式版能修正这个API设计缺陷。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。