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

来源:互联网 发布:Windows 系统 编辑:程序博客网 时间:2024/05/16 08:28
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 {         // 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 r = miniThumbDataFile();        final long miniThumbDataFileId=getMiniThumbFileId(id);        RandomAccessFile r = miniThumbDataFile(miniThumbDataFileId,true);        if (r != null) {            long pos = id * BYTES_PER_MINTHUMB;            FileChannel channel=r.getChannel();            long pos = getPositionInMiniThumbFile(id);            FileLock lock = null;            try {                mBuffer.clear();                mBuffer.limit(1 + 8);                lock = mChannel.lock(pos, 1 + 8, true);                lock = channel.lock(pos, 1 + 8, true);                // check that we can read the following 9 bytes                // (1 for the "status" and 8 for the long)                if (mChannel.read(mBuffer, pos) == 9) {                if (channel.read(mBuffer, pos) == 9) {                    mBuffer.position(0);                    if (mBuffer.get() == 1) {                        return mBuffer.getLong();@@ -242,10 +295,12 @@ public class MiniThumbFile {     public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic)            throws IOException {        RandomAccessFile r = miniThumbDataFile();       final long miniThumbDataFileId=getMiniThumbFileId(id);        RandomAccessFile r = miniThumbDataFile(miniThumbDataFileId);        if (r == null) return;        long pos = id * BYTES_PER_MINTHUMB;        final long pos = getPositionInMiniThumbFile(id);        FileChannel channel=r.getChannel();        FileLock lock = null;        try {            if (data != null) {@@ -266,14 +321,13 @@ public class MiniThumbFile {check = sChecker.getValue();                }                mBuffer.putLong(check);                if (LOG) Log.i(TAG, "saveMiniThumbToFile(" + id + ") flag=1, magic="                        + magic + ", length=" + data.length + ", check=" + check);                                mBuffer.put(data);                mBuffer.flip();                lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false);                mChannel.write(mBuffer, pos);                lock = channel.lock(pos, BYTES_PER_MINTHUMB, false);                channel.write(mBuffer, pos);                if (LOG) Log.i(TAG, "saveMiniThumbToFile(" + id + ") flag=1, magic="                        + magic + ", length=" + data.length + ", check=" + check);            }        } catch (IOException ex) {            Log.e(TAG, "couldn't save mini thumbnail data for "@@ -319,27 +373,29 @@ public class MiniThumbFile {      * @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) { // flag, magic, length, check code                mBuffer.position(0);                byte flag = mBuffer.get();                long magic = mBuffer.getLong();                int length = mBuffer.getInt();                long check = mBuffer.getLong();                if (LOG) Log.i(TAG, "getMiniThumbFromFile(" + id + ") flag=" + flag                        + ", magic=" + magic + ", length=" + length + ", check=" + check);                               long newCheck = UNKNOWN_CHECK_CODE;                if (size >= 1 + 8 + 4 + 8 + length && data.length >= length) {                    mBuffer.get(data, 0, length);                    Log.i(TAG, " success to getMiniThumbFromFile(" + id + ") flag=" + flag                            + ", magic=" + magic + ", length=" + length + ", check=" + check);                    synchronized (sChecker) {                        sChecker.reset();                        sChecker.update(data, 0, length);

MediaScanner.java文件的修改如下:
@@ -1224,14 +1224,14 @@ public class MediaScanner                c.close();             }            if (videoCount != 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 fileToDelete : existingFiles) {                 if (LOG)                     Log.v(TAG, "fileToDelete is " + fileToDelete);@@ -1247,7 +1247,163 @@ public class MediaScanner             e.printStackTrace();         }     }    /*[robin_20120511*/    private void pruneDeadMiniThumbnailFiles() {        HashSet<String> existingFiles = new HashSet<String>();        String directory = Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails";        File file=new File(directory);        String [] files = (file).list();        if (files == null) {            files = new String[0];        }        for (int i=0; i < files.length; i++) {            String fullPathString = directory + "/" + files[i];            existingFiles.add(fullPathString);        }        try {            Cursor c = mMediaProvider.query(                    mThumbsUri,                    new String [] { "_data" },                    null,                    null,                    null);             if (null != c) {                if (c.moveToFirst()) {                    do {                        String fullPathString = c.getString(0);                        existingFiles.remove(fullPathString);                    } while (c.moveToNext());                }                c.close();            }            c = mMediaProvider.query(                    mVideoThumbsUri,                    new String [] { "_data" },                    null,                    null,                    null);            if (null != c) {                if (c.moveToFirst()) {                    do {                        String fullPathString = c.getString(0);                        existingFiles.remove(fullPathString);                    } while (c.moveToNext());                }                c.close();            }            String strs[];            long fileSN;            long id0;            long id1;            int blockNum=MiniThumbFile.getMiniThumbDataFileBlockNum();            final String selection_IMAGE=" "+Images.Thumbnails.IMAGE_ID+">=? AND "+Images.Thumbnails.IMAGE_ID+"<?";            final String sortOrder_IMAGE=Images.Thumbnails.IMAGE_ID;            final String selection_VIDEO=" "+Video.Thumbnails.VIDEO_ID+">=? AND "+Video.Thumbnails.VIDEO_ID+"<?";            final String sortOrder_VIDEO=Video.Thumbnails.VIDEO_ID;            HashSet<String> fileSet=new HashSet<String>(existingFiles);            for (String fileToDelete : fileSet) {              strs=MiniThumbFile.parseFileName(fileToDelete);                if(strs==null||strs.length!=4)                {                      existingFiles.remove(fileToDelete);                      continue;                }                if("images".equals(strs[2])||"video".equals(strs[2]))                {                    fileSN=Long.parseLong(strs[3]);                    id0=blockNum*fileSN;                    id1=blockNum*(fileSN+1);                    final String[] selectionArgs= new String[]{id0+"",id1+""};                    if("images".equals(strs[2]))                    {                                               c = mMediaProvider.query(                                mThumbsUri,                               new String [] { Images.Thumbnails.IMAGE_ID },                               selection_IMAGE,                                selectionArgs,                                sortOrder_IMAGE);                        if (null != c) {                            if (c.getCount()>0) {                                                           existingFiles.remove(fileToDelete);                                   }                            c.close();                        }                    }                    else if ("video".equals(strs[2]))                    {                        c = mMediaProvider.query(                                mVideoThumbsUri,                                new String [] { Video.Thumbnails.VIDEO_ID },                                selection_VIDEO,                                selectionArgs,                                sortOrder_VIDEO);                        if (null != c) {                            if (c.getCount()>0) {                              existingFiles.remove(fileToDelete);                            }                            c.close();                        }                                       }                }                else                {                      existingFiles.remove(fileToDelete);                }            }            for (String fileToDelete : existingFiles) {                if (LOG)                    Log.v(TAG, "fileToDelete is " + fileToDelete);                try {                    (new File(fileToDelete)).delete();                } catch (SecurityException ex) {                    ex.printStackTrace();                }            }            file=new File(directory);            long freeDiskSpape=(file.getUsableSpace()>>20);            /*             * when the free Disk is very low(<100M),delete all MiniThumbFile             */            if(freeDiskSpape<100)            {files = (file).list();                if (files == null) {                    files = new String[0];                }                for (int i=0; i < files.length; i++) {                    String fullPathString = directory + "/" + files[i];                    existingFiles.add(fullPathString);                }                fileSet=new HashSet<String>(existingFiles);                for (String fileToDelete : fileSet) {                      strs=MiniThumbFile.parseFileName(fileToDelete);                    if(strs==null||strs.length!=4)                    {existingFiles.remove(fileToDelete);continue;                    }                }                for (String fileToDelete : existingFiles) {                    if (LOG)                        Log.v(TAG, "Memeroy is very Low.delete MiniThumbFile:" + fileToDelete);                    try {                        (new File(fileToDelete)).delete();                    } catch (SecurityException ex) {                        ex.printStackTrace();                    }                }                           }            Log.v(TAG, "pruneDeadMiniThumbnailFiles... " + c);        } catch (RemoteException e) {            /* We will soon be killed...*/            e.printStackTrace();        }    }    /*robin_20120511]*/     private void postscan(String[] directories) throws RemoteException {         Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();@@ -1306,8 +1462,12 @@ public class MediaScanner         if ((mOriginalCount == 0 || mOriginalVideoCount == 0)                 && mImagesUri.equals(Images.Media.getContentUri("external"))) {            pruneDeadThumbnailFiles();        }/*[robin_20120511*/        else if (mImagesUri.equals(Images.Media.getContentUri("external")))        {            pruneDeadMiniThumbnailFiles();        }        /*robin_20120511]*/        /* allow GC to clean up*/        mPlayLists = null;        mFileCache = null;@@ -1399,7 +1559,12 @@ public class MediaScanner             long prune = System.currentTimeMillis();             pruneDeadThumbnailFiles();             if (LOG) Log.d(TAG, "mtkPostscan: pruneDeadThumbnailFiles takes " + (System.currentTimeMillis() - prune) + "ms.");        }/*[robin_20120511*/        else if (mImagesUri.equals(Images.Media.getContentUri("external")))        {             pruneDeadMiniThumbnailFiles();        }        /*robin_20120511]*/               // allow GC to clean up         mPlayLists = null;



 结束!
原创粉丝点击