安卓的thumbdata文件是什麼怎麼整
問題描述:
安卓系統的照片縮略圖目錄 DCIM/.thumbnails/ 下面有一個或兩個形如.thumbdataN--xxxxxxx的文件,手機使用的時間越久,這個文件會越大,佔用很多空間。很多朋友想知道:1. 為什麼它會越來越大
2. 為什麼這個縮略圖文件有時比手機里所有的照片原圖加一起還大
3. 為什麼刪掉之後還會出現,系統如何憑空還原出這麼大的文件
網上常見的幾種解決方法都是間接解決或者逃避問題,並沒有把根本原因分析清楚,例如:
1. 構造一個同名的只讀空文件佔位——此方法可能造成系統不斷重試寫文件,耗電量增加,也可能間接影響系統功能
2. 用.nomedia屏蔽掃描——此方法不一定管用,也會影響系統功能
3. 禁用系統相機和圖庫,用第三方軟體——此方法不一定管用,也會影響系統功能
問題分析:
為了找到根本原因,我找了個安卓4.0的源碼包,從18萬個文件裡面定位到了構造和使用這個文件的一個java腳本,/frameworks/base/media/java/android/media/MiniThumbFile.java,這裡面的代碼就是問題的根本所在。
解開問題的關鍵其實只需要看其中一句代碼「long pos = id * BYTES_PER_MINTHUMB」,寫過程序的人可能很容易猜出來,這句代碼的意思是「縮略圖起始位置=圖片id*每個縮略圖的長度」。這句代碼就揭示了上面幾個問題的根本原因:
- 這裡的id是安卓後台資料庫里的文件編號,是資料庫的自增主鍵,隨著使用時間的增加,系統中不斷有新文件產生,那麼新文件的id會越來越大,無論是否刪除舊文件。
- BYTES_PER_MINTHUMB是每個id在thumbdata裡面佔用的固定空間,無論這個id對應的文件是否是圖片(是圖片則存儲縮略圖相關信息,剩餘的空間用0填充,不是圖片則全用0填充)。
- pos是縮略圖在thumbdata裡面的偏移量,而thumbdata文件的大小就等於圖片文件的最大id(或者全局最大id,沒嚴格驗算過)乘以BYTES_PER_MINTHUMB。按附件程序中每個id佔10000Bytes來計算,如果系統最大文件id是100000,那麼thumbdata大約有950MB。
因此,隨著新增圖片id的不斷變大,這個文件會越來越大,並且其中非圖片id對應的位置是空值(即使是圖片id,末尾通常也留有大量空白空間),大量的空值使其體積比手機里所有圖片加一起還大,系統也很容易通過現存的圖片來重構這個文件。
這麼做的目的應該是為了快速定位縮略圖信息,但是可能股溝的工程師沒想到現在的手機里會有那麼多的文件。
解決方案:
這個問題如果不改源代碼的話應該不能根除的,我下面提供的方法實操起來也不容易,但是更接近問題的根本。你需要root許可權和re瀏覽器,然後定期做下面幾個事情:
1. 清除系統臨時文件和無用文件——因為目的是減少文件數量,進而減小「最大文件id」,所以可考慮從文件最多的地方開始,最多的一般是微信的緩存(可能有幾萬的文件數),淘寶京東等app的緩存(可能有1-2萬的文件數),具體怎麼刪這裡就不討論了。
2. 用re瀏覽器訪問安卓的系統目錄「/data/data/com.android.providers.media/databases」,刪掉包含「external.db」的三個文件——這幾個是外置存儲卡的媒體文件資料庫(可以用re瀏覽器打開這個資料庫,文件編號在files表裡,最大文件id在sqlite_sequence表裡),上面說的自增id就在這裡產生和更新,把它刪掉後可以重建文件id,否則「最大文件id」只能越來越大。
3. 刪掉thumbdata文件。
3. 重啟手機——過一段時間後,系統會根據文件系統當前的狀態重建那個資料庫,這時「最大文件id」就是清理之後剩下的文件數量,再過一段時間,新的thumbdata文件也會被重構出來,大小與新的最大id正相關。
附MiniThumbFile.java:
/** * This class handles the mini-thumb file. A mini-thumb file consists * of blocks, indexed by id. Each block has BYTES_PER_MINTHUMB bytes in the * following format: * * 1 byte status (0 = empty, 1 = mini-thumb available) * 8 bytes magic (a magic number to match what"s in the database) * 4 bytes data length (LEN) * LEN bytes jpeg data * (the remaining bytes are unused) * * @hide This file is shared between MediaStore and MediaProvider and should remained internal use * only. */public class MiniThumbFile { private static final String TAG = "MiniThumbFile"; private static final int MINI_THUMB_DATA_FILE_VERSION = 3; public static final int BYTES_PER_MINTHUMB = 10000; private static final int HEADER_SIZE = 1 + 8 + 4; private Uri mUri; private RandomAccessFile mMiniThumbFile; private FileChannel mChannel; private ByteBuffer mBuffer; private static Hashtable<String, MiniThumbFile> sThumbFiles = new Hashtable<String, MiniThumbFile>(); /** * 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. */ public static synchronized void reset() { for (MiniThumbFile file : sThumbFiles.values()) { file.deactivate(); } sThumbFiles.clear(); } public static synchronized MiniThumbFile instance(Uri uri) { String type = uri.getPathSegments().get(1); MiniThumbFile file = sThumbFiles.get(type); // Log.v(TAG, "get minithumbfile for type: "+type); if (file == null) { file = new MiniThumbFile( Uri.parse("content://media/external/" + type + "/media")); sThumbFiles.put(type, file); } return file; } private String randomAccessFilePath(int version) { String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails"; return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode(); } private void removeOldFile() { String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1); File oldFile = new File(oldPath); if (oldFile.exists()) { try { oldFile.delete(); } catch (SecurityException ex) { // ignore } } } 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()); } } File f = new File(path); try { mMiniThumbFile = 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"); } catch (IOException ex2) { // ignore exception } } if (mMiniThumbFile != null) { mChannel = mMiniThumbFile.getChannel(); } } return mMiniThumbFile; } public MiniThumbFile(Uri uri) { mUri = uri; mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); } public synchronized void deactivate() { if (mMiniThumbFile != null) { try { mMiniThumbFile.close(); mMiniThumbFile = null; } catch (IOException ex) { // ignore exception } } } // Get the magic number for the specified id in the mini-thumb file. // Returns 0 if the magic is not available. public synchronized long getMagic(long id) { // 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(); if (r != null) { long pos = id * BYTES_PER_MINTHUMB; FileLock lock = null; try { mBuffer.clear(); mBuffer.limit(1 + 8); lock = mChannel.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) { mBuffer.position(0); if (mBuffer.get() == 1) { return mBuffer.getLong(); } } } catch (IOException ex) { Log.v(TAG, "Got exception checking file magic: ", ex); } catch (RuntimeException ex) { // Other NIO related exception like disk full, read only channel..etc Log.e(TAG, "Got exception when reading magic, id = " + id + ", disk full or mount read-only? " + ex.getClass()); } finally { try { if (lock != null) lock.release(); } catch (IOException ex) { // ignore it. } } } return 0; } public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { RandomAccessFile r = miniThumbDataFile(); if (r == null) return; long pos = id * BYTES_PER_MINTHUMB; FileLock lock = null; try { if (data != null) { if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) { // not enough space to store it. return; } mBuffer.clear(); mBuffer.put((byte) 1); mBuffer.putLong(magic); mBuffer.putInt(data.length); mBuffer.put(data); mBuffer.flip(); lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false); mChannel.write(mBuffer, pos); } } catch (IOException ex) { Log.e(TAG, "couldn"t save mini thumbnail data for " + id + "; ", ex); throw ex; } catch (RuntimeException ex) { // Other NIO related exception like disk full, read only channel..etc Log.e(TAG, "couldn"t save mini thumbnail data for " + id + "; disk full or mount read-only? " + ex.getClass()); } finally { try { if (lock != null) lock.release(); } catch (IOException ex) { // ignore it. } } } /** * Gallery app can use this method to retrieve mini-thumbnail. Full size * images share the same IDs with their corresponding thumbnails. * * @param id the ID of the image (same of full size image). * @param data the buffer to store mini-thumbnail. */ public synchronized byte [] getMiniThumbFromFile(long id, byte [] data) { RandomAccessFile r = miniThumbDataFile(); if (r == null) return null; long pos = id * BYTES_PER_MINTHUMB; FileLock lock = null; try { mBuffer.clear(); lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true); int size = mChannel.read(mBuffer, pos); if (size > 1 + 8 + 4) { // flag, magic, length mBuffer.position(0); byte flag = mBuffer.get(); long magic = mBuffer.getLong(); int length = mBuffer.getInt(); if (size >= 1 + 8 + 4 + length && data.length >= length) { mBuffer.get(data, 0, length); return data; } } } catch (IOException ex) { Log.w(TAG, "got exception when reading thumbnail id=" + id + ", exception: " + ex); } catch (RuntimeException ex) { // Other NIO related exception like disk full, read only channel..etc Log.e(TAG, "Got exception when reading thumbnail, id = " + id + ", disk full or mount read-only? " + ex.getClass()); } finally { try { if (lock != null) lock.release(); } catch (IOException ex) { // ignore it. } } return null; }}
推薦閱讀: