原生Android缩略图填满SD卡的有关问题

原生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;


 结束!