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

来源:互联网 发布:淘宝申诉流程 编辑:程序博客网 时间:2024/06/04 19:35
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个文件的修改:

      modified: frameworks/base/media/java/android/media/MediaScanner.java
      modified: frameworks/base/media/java/android/media/MiniThumbFile.java

MiniThumbFile.java文件的主要修改如下:

/**
@@ -85,16 +89,34 @@ public class MiniThumbFile {
     /*add 1 on google's version.*/
     /*this version add check code for thumbdata.*/
     private static final int MINI_THUMB_DATA_FILE_VERSION = 3 + 1;
-    public static final int BYTES_PER_MINTHUMB = 17000;
+    public static final int BYTES_PER_MINTHUMB = 17000;
     private static final int HEADER_SIZE = 1 + 8 + 4 + 8;
     private Uri mUri;
-    private RandomAccessFile mMiniThumbFile;
+    Map<Long,RandomAccessFile> mMiniThumbFilesMap = Collections.synchronizedMap(new HashMap<Long,RandomAccessFile>(5));
     private FileChannel mChannel;
     private ByteBuffer mBuffer;
     private static Hashtable<String, MiniThumbFile> sThumbFiles =
         new Hashtable<String, MiniThumbFile>();
     private static java.util.zip.Adler32 sChecker = new Adler32();
     private static final long UNKNOWN_CHECK_CODE = -1;
+    final static char SEPERATE_CHAR='_';
+    public static String[] parseFileName(String fileName)
+    {
+       String str=fileName;
+       if(fileName==null)
+              return null;
+       int index=fileName.lastIndexOf("/");
+       if(index!=-1)
+       {
+              str=fileName.substring(index+1);
+       }
+       String strs[]=str.split(SEPERATE_CHAR+"");
+        if(strs==null||strs.length!=4||(!str.startsWith(".thumbdata")))
+        {
+              return null;
+        }
+        return strs;
+    }
     /**
      * 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
      */
-    public static String getThumbdataPath(Uri 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;
-    }
+    }*/
-    private void removeOldFile() {
-        String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1);
+    private void removeOldMiniThumbDataFile(long miniThumbDataFileId) {
+        String oldPath =getMiniThumbDataFilePath(MINI_THUMB_DATA_FILE_VERSION - 1,miniThumbDataFileId);
         File oldFile = new File(oldPath);
         if (oldFile.exists()) {
             try {
@@ -152,35 +186,43 @@ public class MiniThumbFile {
             }
         }
     }
-
-    private RandomAccessFile miniThumbDataFile() {
-        if (mMiniThumbFile == null) {
-            removeOldFile();
-            String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION);
-            File directory = new File(path).getParentFile();
-            if (!directory.isDirectory()) {
-                if (!directory.mkdirs()) {
-                    Log.e(TAG, "Unable to create .thumbnails directory "
-                            + directory.toString());
-                }
-            }
+    final private String getMiniThumbDataFilePath(int version,long miniThumbDataFileId)
+    {
+       return randomAccessFilePath(version)+SEPERATE_CHAR+miniThumbDataFileId;
+    }
+    private RandomAccessFile miniThumbDataFile(final long miniThumbDataFileId) {
+       return miniThumbDataFile(miniThumbDataFileId,false);
+    }
+    private RandomAccessFile miniThumbDataFile(final long miniThumbDataFileId,boolean onlyRead) {
+              RandomAccessFile miniThumbFile=mMiniThumbFilesMap.get(miniThumbDataFileId);
+            String path = getMiniThumbDataFilePath(MINI_THUMB_DATA_FILE_VERSION,miniThumbDataFileId);
             File f = new File(path);
+            if (miniThumbFile == null||!f.exists()) {
+              if(onlyRead)
+                      return null;
+              removeOldMiniThumbDataFile(miniThumbDataFileId);
+                File directory = new File(path).getParentFile();
+                if (!directory.isDirectory()) {
+                    if (!directory.mkdirs()) {
+                        Log.e(TAG, "Unable to create .thumbnails directory "
+                                + directory.toString());
+                    }
+                }
+           
             try {
-                mMiniThumbFile = new RandomAccessFile(f, "rw");
+                miniThumbFile = new RandomAccessFile(f, "rw");
             } catch (IOException ex) {
                 // Open as read-only so we can at least read the existing
                 // thumbnails.
                 try {
-                    mMiniThumbFile = new RandomAccessFile(f, "r");
+                    miniThumbFile = new RandomAccessFile(f, "r");
                 } catch (IOException ex2) {
                                   }
             }
-            if (mMiniThumbFile != null) {
-                mChannel = mMiniThumbFile.getChannel();
-            }
+            mMiniThumbFilesMap.put(miniThumbDataFileId, miniThumbFile);
         }
-        return mMiniThumbFile;
+        return miniThumbFile;
     }
     public MiniThumbFile(Uri uri) {
@@ -189,10 +231,19 @@ public class MiniThumbFile {
     }
     public synchronized void deactivate() {
-        if (mMiniThumbFile != null) {
+        if (mMiniThumbFilesMap != null) {
+              Set<Long> keySet=mMiniThumbFilesMap.keySet();
             try {
-                mMiniThumbFile.close();
-                mMiniThumbFile = null;
+              for(Long key:keySet)
+              {
+                      RandomAccessFile file=mMiniThumbFilesMap.get(key);
+                      if(file!=null)
+                      {
+                              mMiniThumbFilesMap.put(key, null);
+                              file.close();
+                      }
+              }
+              mMiniThumbFilesMap.clear();
             } catch (IOException ex) {
                 // ignore exception
             }
@@ -205,18 +256,20 @@ public class MiniThumbFile {
0 0
原创粉丝点击