原生Android缩略图填满SD卡的问题本人博客原文google原生Android中,MiniThumbFile.java里存储图片/视频的
原生Android缩略图填满SD卡的问题
本人博客原文
google原生Android中,MiniThumbFile.java里存储图片/视频的缩略图的算法有问题。该算法的漏洞造成微缩略图文件(DCIM\.thumbnails\.thumbdata4--1967290299)非常庞大和臃肿,多达1G,理论上可以无限大,直到填满SD卡重现步骤:第一步:插入一张拥有10万张图片的外部SD卡,第二步:等待手机扫描完整个SD卡,这个过程大概30分钟。至于扫描是否完成,你可以看TAG为MediaProvider的日志。第三步:拔出外部SD卡,再次等手机进行扫描大概10分钟。第四步:按下手机的电源key +音量down key来截屏。第五步:通过电脑查看内部SD卡的DCIM\.thumbnails\.thumbdata4--1967290299文件。这时你会发现,该文件多达1G.对于该问题,大概的逻辑是这样的:MediaProvider会对SD卡上的文件的进行扫描,得到一些基本的信息放入数据库的files这个表中。对于一些图片(比如手机截屏图片),手机会以这些图片文件在files表中的_id的值,作为一个定位标志,把其缩略文件存放在.thumbdata4--1967290299中。比如:_id为1,那么该文件的缩略图就存放在.thumbdata4--1967290299文件的_id * BYTES_PER_MINTHUMB开始的位置,其中BYTES_PER_MINTHUMB = 17000,对此可以参照MiniThumbFile.java文件中的saveMiniThumbToFile(byte[] data, long id, long magic)等函数对于Android的Sqlite3数据库,_id是自增的。比如,最开始我插入了10001记录,这时最后一条记录的_id为10001,这时如果删除1到10000条记录,再插入一条记录,那么这条的记录的_id值为10002.因此,随着用户日常中不停的向SD卡反复地添加删除文件,那么files表中的_id的值会不停的增大,当该值为10万时,即使我们截屏时,系统只把该截屏图片这样一张图的缩略图保存在.thumbdata4--1967290299文件,10万*BYTES_PER_MINTHUMB>1G,也就是说.thumbdata4--1967290299文件就会占用了1G多的SD空间。随着时间的推移,该值会继续变大,直到填满SD卡。我们的方案就是把.thumbdata4--1967290299文件分成多个文件来存储缩略图。比如.thumbdata4--1967290299-0存_id 为1到1024的文件的缩略图,.thumbdata4--1967290299-1存_id 为1025到2048的文件的缩略图同时对缩略图文件做一些维护,即如果一个缩略图文件本身对应原文件不在时,清理掉该微缩略图文件。另外在极端情况下(剩余容量小于100M下),清理掉所有的微缩略图文件。在MTK 6575平台上对该问题的修改包含2个文件的修改::/base/////MediaScanner.:/base/////MiniThumbFile./**@@ -85,16 +89,34 @@ public class MiniThumbFile { /*add 1 on google's version.*//*this version add check code for thumbdata.*/privatestaticfinalint=3+1;-publicstaticfinalint=17000;+publicstaticfinalint=17000;privatestaticfinalint=1+8+4+8;privateUri;-privateRandomAccessFile;+Map<Long,RandomAccessFile>=Collections.(newHashMap<Long,RandomAccessFile>(5));privateFileChannel;privateByteBuffer;privatestaticHashtable<String,MiniThumbFile>=newHashtable<String,MiniThumbFile>();privatestatic...Adler32=newAdler32();privatestaticfinallong=-1;+finalstaticchar='_';+publicstaticString[](String)+{+String=;+if(==null)+returnnull;+int=.("/");+if(!=-1)+{+=.(+1);+}+String[]=.(+"");+if(==null||.!=4||(!.(".thumbdata")))+{+returnnull;+}+return;+}/** * We store different types of thumbnails in different files. To remain backward compatibility, * we should hashcode of content://media/external/images/media remains the same.@@ -105,7 +127,18 @@ public class MiniThumbFile { } sThumbFiles.clear(); }-+ private final static long getMiniThumbFileId(long id)+ {+ return (id>>8);+ }+ public final static int getMiniThumbDataFileBlockNum()+ {+ return (1<<8);+ }+ private final static long getPositionInMiniThumbFile(long id)+ {+ return (id&0xff)*BYTES_PER_MINTHUMB;+ } public static synchronized MiniThumbFile instance(Uri uri) { String type = uri.getPathSegments().get(1); MiniThumbFile file = sThumbFiles.get(type);@@ -120,10 +153,11 @@ public class MiniThumbFile { } private String randomAccessFilePath(int version) {+ String type = mUri.getPathSegments().get(1); String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails";- return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode();+ return directoryName + "/.thumbdata" + version + SEPERATE_CHAR + mUri.hashCode()+SEPERATE_CHAR+type; } /**@@ -131,18 +165,18 @@ public class MiniThumbFile { * @param uri the Uri same as instance(Uri uri). * @return */-publicstaticString(Uri){+/*public static String getThumbdataPath(Uri uri) { String type = uri.getPathSegments().get(1); Uri thumbFileUri = Uri.parse("content://media/external/" + type + "/media"); String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails";- String path = directoryName + "/.thumbdata" + MINI_THUMB_DATA_FILE_VERSION + "-" + thumbFileUri.hashCode();+ String path = directoryName + "/.thumbdata" + MINI_THUMB_DATA_FILE_VERSION +SEPERATE_CHAR + thumbFileUri.hashCode()+SEPERATE_CHAR+type; if (LOG) Log.i(TAG, "getThumbdataPath(" + uri + ") return " + path); return path;- }+ }*/-privatevoid(){-String=(-1);+privatevoid(long){+String=(-1,);File=newFile();if(.()){try{@@-152,35+186,43@@publicclassMiniThumbFile{}}}--privateRandomAccessFile(){-if(==null){-();-String=();-File=newFile().();-if(!.()){-if(!.()){-Log.(,"Unable to create .thumbnails directory "-+.());-}-}+finalprivateString(int,long)+{+return()++;+}+privateRandomAccessFile(finallong){+return(,false);+}+privateRandomAccessFile(finallong,boolean){+RandomAccessFile=.get();+String=(,);File=newFile();+if(==null||!.()){+if()+returnnull;+();+File=newFile().();+if(!.()){+if(!.()){+Log.(,"Unable to create .thumbnails directory "++.());+}+}+try{-=newRandomAccessFile(,"rw");+=newRandomAccessFile(,"rw");}catch(IOException){// Open as read-only so we can at least read the existing // thumbnails.try{-=newRandomAccessFile(,"r");+=newRandomAccessFile(,"r");}catch(IOException){}}-if(!=null){-=.();-}+.(,);}-return;+return;}publicMiniThumbFile(Uri){@@-189,10+231,19@@publicclassMiniThumbFile{}publicsynchronizedvoid(){-if(!=null){+if(!=null){+Set<Long>=.();try{-.();-=null;+for(Long:)+{+RandomAccessFile=.get();+if(!=null)+{+.(,null);+.();+}+}+.();}catch(IOException){// ignore exception }@@ -205,18 +256,20 @@ public class MiniThumbFile { // check the mini thumb file for the right data. Right is // defined as having the right magic number at the offset // reserved for this "id".-RandomAccessFile=();+finallong=();+RandomAccessFile=(,true);if(!=null){-long=*;+FileChannel=.();+long=();FileLocklock=null;try{.();.(1+8);-lock=.lock(,1+8,true);+lock=.lock(,1+8,true);// check that we can read the following 9 bytes // (1 for the "status" and 8 for the long)-if(.(,)==9){+if(.(,)==9){.(0);if(.get()==1){return.();@@-242,10+295,12@@publicclassMiniThumbFile{publicsynchronizedvoid(byte[],long,long)throwsIOException{-RandomAccessFile=();+finallong=();+RandomAccessFile=();if(==null)return;-long=*;+finallong=();+FileChannel=.();FileLocklock=null;try{if(!=null){@@-266,14+321,13@@publicclassMiniThumbFile{=.();}.();-if()Log.(,"saveMiniThumbToFile("++") flag=1, magic="-++", length="+.+", check="+);-.();.();-lock=.lock(,,false);-.(,);+lock=.lock(,,false);+.(,);+if()Log.(,"saveMiniThumbToFile("++") flag=1, magic="+++", length="+.+", check="+);}}catch(IOException){Log.(,"couldn't save mini thumbnail data for "@@-319,27+373,29@@publicclassMiniThumbFile{*@return*/ public synchronized byte[] getMiniThumbFromFile(long id, byte [] data, ThumbResult result) {- RandomAccessFile r = miniThumbDataFile();+ final long miniThumbDataFileId=getMiniThumbFileId(id);+ RandomAccessFile r = miniThumbDataFile(miniThumbDataFileId,true); if (r == null) return null;- long pos = id * BYTES_PER_MINTHUMB;+ long pos = getPositionInMiniThumbFile(id);+ FileChannel channel=r.getChannel(); FileLock lock = null; try { mBuffer.clear();- lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true);- int size = mChannel.read(mBuffer, pos);+ lock = channel.lock(pos, BYTES_PER_MINTHUMB, true);+ int size = channel.read(mBuffer, pos); if (size > 1 + 8 + 4 + 8) { //,,,.(0);byte=.get();long=.();int=.();long=.();-if()Log.(,"getMiniThumbFromFile("++") flag="+-+", magic="++", length="++", check="+);-long=;if(>=1+8+4+8+&&.>=){.get(,0,);+Log.(," success to getMiniThumbFromFile("++") flag="+++", magic="++", length="++", check="+);synchronized(){.();.(,0,);@@-1224,14+1224,14@@publicclassMediaScanner.();}-if(!=0){+/*if (videoCount != 0) { String fullPathString = MiniThumbFile.getThumbdataPath(mVideoThumbsUri); existingFiles.remove(fullPathString); } if (imageCount != 0) { String fullPathString = MiniThumbFile.getThumbdataPath(mThumbsUri); existingFiles.remove(fullPathString);- }+ }*/for(String:){if()Log.(,"fileToDelete is "+);@@-1247,7+1247,163@@publicclassMediaScanner.();}}+/*[robin_20120511*/+privatevoid(){+HashSet<String>=newHashSet<String>();+String=Environment.().()+"/DCIM/.thumbnails";+File=newFile();+String[]=().();+if(==null){+=newString[0];+}+for(int=0;<.;++){+String=+"/"+[];+.();+}+try{+Cursor=.(+,+newString[]{"_data"},+null,+null,+null);+if(null!=){+if(.()){+do{+String=.(0);+.();+}while(.());+}+.();+}+=.(+,+newString[]{"_data"},+null,+null,+null);+if(null!=){+if(.()){+do{+String=.(0);+.();+}while(.());+}+.();+}+String[];+long;+long;+long;+int=MiniThumbFile.();+finalString=" "+Images.Thumbnails.+">=? AND "+Images.Thumbnails.+"<?";+finalString=Images.Thumbnails.;+finalString=" "+Video.Thumbnails.+">=? AND "+Video.Thumbnails.+"<?";+finalString=Video.Thumbnails.;+HashSet<String>=newHashSet<String>();+for(String:){+=MiniThumbFile.();+if(==null||.!=4)+{+.();+continue;+}+if("images".([2])||"video".([2]))+{+=Long.([3]);+=*;+=*(+1);+finalString[]=newString[]{+"",+""};+if("images".([2]))+{++=.(+,+newString[]{Images.Thumbnails.},+,+,+);+if(null!=){+if(.()>0){++.();+}+.();+}+}+elseif("video".([2]))+{+=.(+,+newString[]{Video.Thumbnails.},+,+,+);+if(null!=){+if(.()>0){+.();+}+.();+}+}+}+else+{+.();+}+}+for(String:){+if()+Log.(,"fileToDelete is "+);+try{+(newFile()).delete();+}catch(SecurityException){+.();+}+}+=newFile();+long=(.()>>20);+/*+ * when the free Disk is very low(<100M),delete all MiniThumbFile+ */+if(<100)+{+=().();+if(==null){+=newString[0];+}+for(int=0;<.;++){+String=+"/"+[];+.();+}+=newHashSet<String>();+for(String:){+=MiniThumbFile.();+if(==null||.!=4)+{+.();+continue;+}+}+for(String:){+if()+Log.(,"Memeroy is very Low.delete MiniThumbFile:"+);+try{+(newFile()).delete();+}catch(SecurityException){+.();+}+}++}+Log.(,"pruneDeadMiniThumbnailFiles... "+);+}catch(RemoteException){+/* We will soon be killed...*/+.();+}+}+/*robin_20120511]*/privatevoid(String[])throwsRemoteException{Iterator<FileCacheEntry>=.().();@@-1306,8+1462,12@@publicclassMediaScannerif((==0||==0)&&.(Images.Media.("external"))){();+}/*[robin_20120511*/+elseif(.(Images.Media.("external")))+{+();}-+/*robin_20120511]*//* allow GC to clean up*/=null;=null;@@-1399,7+1559,12@@publicclassMediaScannerlong=System.();();if()Log.(,"mtkPostscan: pruneDeadThumbnailFiles takes "+(System.()-)+"ms.");+}/*[robin_20120511*/+elseif(.(Images.Media.("external")))+{+();}+/*robin_20120511]*/// allow GC to clean up mPlayLists = null;结束!
