花开两朵,各表一枝。上回书讲到了数据cache的一些流程。这回书要说的就是gallery3d刚开始在手机上运行cache数据的流程。
如果你切换手机的语言,跟此流程一致。这个在上回书也提到了。
首先来看看startNewCacheThread函数:
private void startNewCacheThread() { restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() //开启线程,如果之前有同样的线程已经存在,关闭之前线程 { public void run() { refresh(CacheService.this);//cache媒体数据 } }); }
restartThread函数的源代码如下,不细说。
private static final void restartThread(final AtomicReference<Thread> threadRef, final String name, final Runnable action) { // Create a new thread. final Thread newThread = new Thread() { public void run() { try { action.run(); } finally { threadRef.compareAndSet(this, null); } } }; newThread.setName(name); newThread.start();
// Interrupt any existing thread. final Thread existingThread = threadRef.getAndSet(newThread); if (existingThread != null) { existingThread.interrupt(); } }
接下来分析关键的refresh函数:
private final static void refresh(final Context context) { // First we build the album cache. // This is the meta-data about the albums / buckets on the SD card. Log.i(TAG, "Refreshing cache."); sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); final ArrayList<MediaSet> sets = new ArrayList<MediaSet>(); LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>(); Log.i(TAG, "Building albums."); final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build(); final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build(); final ContentResolver cr = context.getContentResolver(); try { final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, null, null, DEFAULT_BUCKET_SORT_ORDER);//搜索图像 final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, null, null, DEFAULT_BUCKET_SORT_ORDER);//搜索视频 Cursor[] cursors = new Cursor[2]; cursors[0] = cursorImages; cursors[1] = cursorVideos; final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.BUCKET_DISPLAY_NAME, SortCursor.TYPE_STRING, true); try { if (sortCursor != null && sortCursor.moveToFirst()) { sets.ensureCapacity(sortCursor.getCount()); acceleratedSets = new LongSparseArray<MediaSet>(sortCursor.getCount()); MediaSet cameraSet = new MediaSet(); cameraSet.mId = LocalDataSource.CAMERA_BUCKET_ID;//相机的bucket id cameraSet.mName = context.getResources().getString(R.string.camera); sets.add(cameraSet); acceleratedSets.put(cameraSet.mId, cameraSet);//将相机的照片集加入acceleratedSets映射表 do { if (Thread.interrupted()) { return; } long setId = sortCursor.getLong(BUCKET_ID_INDEX); MediaSet mediaSet = findSet(setId, acceleratedSets); if (mediaSet == null) {//如果在acceleratedSets映射表找不到setId, 那么加入acceleratedSets映射表 mediaSet = new MediaSet(); mediaSet.mId = setId; mediaSet.mName = sortCursor.getString(BUCKET_NAME_INDEX); sets.add(mediaSet); acceleratedSets.put(setId, mediaSet); } mediaSet.mHasImages |= (sortCursor.getCurrentCursorIndex() == 0); mediaSet.mHasVideos |= (sortCursor.getCurrentCursorIndex() == 1); } while (sortCursor.moveToNext()); sortCursor.close(); } } finally { if (sortCursor != null) sortCursor.close(); } sAlbumCache.put(ALBUM_CACHE_INCOMPLETE_INDEX, sDummyData, 0);//写入dummy data到ALBUM_CACHE_INCOMPLETE_INDEX块,说明目前cache开始 writeSetsToCache(sets);//cache媒体集 Log.i(TAG, "Done building albums."); // Now we must cache the items contained in every album / bucket. populateMediaItemsForSets(context, sets, acceleratedSets, false);//将media item归类到sets,并将item信息cache } catch (Exception e) { // If the database operation failed for any reason. ; } sAlbumCache.delete(ALBUM_CACHE_INCOMPLETE_INDEX);//删除ALBUM_CACHE_INCOMPLETE_INDEX块,说明目前cache技术 }
CAMERA_BUCKET_ID的定义是这样的,其实就是相机目录的hash码。
public static final int CAMERA_BUCKET_ID = getBucketId(CAMERA_BUCKET_NAME);
public static final String CAMERA_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/DCIM/" + CAMERA_STRING;//相机目录
public static int getBucketId(String path) { return (path.toLowerCase().hashCode()); }
下面是writeSetsToCache函数的功能,就是cache相册信息:
private static final void writeSetsToCache(final ArrayList<MediaSet> sets) { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final int numSets = sets.size(); final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256)); try { dos.writeInt(numSets); for (int i = 0; i < numSets; ++i) { if (Thread.interrupted()) { return; } final MediaSet set = sets.get(i); dos.writeLong(set.mId);//相册id Utils.writeUTF(dos, set.mName);//相册名称 dos.writeBoolean(set.mHasImages);//是否是图片集 dos.writeBoolean(set.mHasVideos);//是否是视频集 } dos.flush(); sAlbumCache.put(ALBUM_CACHE_METADATA_INDEX, bos.toByteArray(), 0);//写chunk文件 dos.close(); if (numSets == 0) { sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); } sAlbumCache.flush();//写index文件 } catch (IOException e) { Log.e(TAG, "Error writing albums to diskcache."); sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); } }
关键函数sAlbumCache.put(ALBUM_CACHE_METADATA_INDEX, bos.toByteArray(), 0),我们来看看put函数做了些什么?
public void put(long key, byte[] data, long timestamp) { // Check to see if the record already exists. Record record = null; Log.i(TAG, "DiskCache, put key:"+key); synchronized (mIndexMap) { record = mIndexMap.get(key); } if (record != null && data.length <= record.sizeOnDisk) {//如果记录在mIndexMap已经存在,替换当前记录 // We just replace the chunk. int currentChunk = record.chunk; try { Log.i(TAG, "DiskCache, get chunk file"); RandomAccessFile chunkFile = getChunkFile(record.chunk);//获取chunk文件 if (chunkFile != null) { chunkFile.seek(record.offset); chunkFile.write(data); synchronized (mIndexMap) { mIndexMap.put(key, new Record(currentChunk, record.offset, data.length, record.sizeOnDisk, timestamp));//更改mIndexMap的key的记录数据 } return; } } catch (Exception e) { Log.e(TAG, "Unable to read from chunk file"); } } //如果记录不存在,将新的chunk添加到当前chunk尾部 // Append a new chunk to the current chunk. final int chunk = mTailChunk; final RandomAccessFile chunkFile = getChunkFile(chunk); if (chunkFile != null) { try { //将数据写入chunk文件 final int offset = (int) chunkFile.length(); chunkFile.seek(offset); chunkFile.write(data); synchronized (mIndexMap) { mIndexMap.put(key, new Record(chunk, offset, data.length, data.length, timestamp));//将新纪录添加到mIndexMap } if (offset + data.length > CHUNK_SIZE) { ++mTailChunk; } if (++mNumInsertions == 64) { // CR: 64 => constant // Flush the index file at a regular interval. To avoid // writing the entire // index each time the format could be changed to an // append-only journal with // a snapshot generated on exit. flush();//写64次后,写索引文件 } } catch (IOException e) { Log.e(TAG, "Unable to write new entry to chunk file"); } } else { Log.e(TAG, "getChunkFile() returned null"); } }
put函数就是把相册集的信息写入chunk文件。
那继续看sAlbumCache.flush()函数:
public void flush() { if (mNumInsertions != 0) { mNumInsertions = 0; writeIndex(); } }
private void writeIndex() { final String indexFilePath = getIndexFilePath(); try { // Create a temporary file to write the index into. File tempFile = File.createTempFile("DiskCacheIndex", null);//创建临时索引文件 final FileOutputStream fileOutput = new FileOutputStream(tempFile); final BufferedOutputStream bufferedOutput = new BufferedOutputStream(fileOutput, 1024); final DataOutputStream dataOutput = new DataOutputStream(bufferedOutput); // Write the index header. final int numRecords = mIndexMap.size(); dataOutput.writeInt(INDEX_HEADER_MAGIC);//索引文件头部的关键字 dataOutput.writeInt(INDEX_HEADER_VERSION);//索引文件头部的关键字 dataOutput.writeShort(mTailChunk);//尾部chunk索引数 dataOutput.writeInt(numRecords);//记录条数 // Write the records. //将chunk文件信息写入索引文件 for (int i = 0; i < numRecords; ++i) { final long key = mIndexMap.keyAt(i); final Record record = mIndexMap.valueAt(i); dataOutput.writeLong(key); dataOutput.writeShort(record.chunk); dataOutput.writeInt(record.offset); dataOutput.writeInt(record.size); dataOutput.writeInt(record.sizeOnDisk); dataOutput.writeLong(record.timestamp); } // Close the file. dataOutput.close(); Log.d(TAG, "Wrote index with " + numRecords + " records."); // Atomically overwrite the old index file. tempFile.renameTo(new File(indexFilePath));//将临时索引文件重命名 } catch (IOException e) { Log.e(TAG, "Unable to write the index file " + indexFilePath); } }
再回到refresh函数,populateMediaItemsForSets做了哪些事情?
private final static void populateMediaItemsForSets(final Context context, final ArrayList<MediaSet> sets, final LongSparseArray<MediaSet> acceleratedSets, boolean useWhere) { if (sets == null || sets.size() == 0 || Thread.interrupted()) { return; } Log.i(TAG, "Building items."); final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI; final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI; final ContentResolver cr = context.getContentResolver(); String whereClause = null; if (useWhere) { int numSets = sets.size(); StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + " in ("); for (int i = 0; i < numSets; ++i) { whereString.append(sets.get(i).mId); if (i != numSets - 1) { whereString.append(","); } } whereString.append(")"); whereClause = whereString.toString(); Log.i(TAG, "Updating dirty albums where " + whereClause); } try { final Cursor cursorImages = cr.query(uriImages, PROJECTION_IMAGES, whereClause, null, DEFAULT_IMAGE_SORT_ORDER); final Cursor cursorVideos = cr.query(uriVideos, PROJECTION_VIDEOS, whereClause, null, DEFAULT_VIDEO_SORT_ORDER); final Cursor[] cursors = new Cursor[2]; cursors[0] = cursorImages; cursors[1] = cursorVideos; final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.DATE_TAKEN, SortCursor.TYPE_NUMERIC, true); if (Thread.interrupted()) { return; } try { if (sortCursor != null && sortCursor.moveToFirst()) { final int count = sortCursor.getCount(); final int numSets = sets.size(); final int approximateCountPerSet = count / numSets; for (int i = 0; i < numSets; ++i) { final MediaSet set = sets.get(i); set.setNumExpectedItems(approximateCountPerSet); } do { if (Thread.interrupted()) { return; } final MediaItem item = new MediaItem(); final boolean isVideo = (sortCursor.getCurrentCursorIndex() == 1); if (isVideo) { populateVideoItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_VIDEOS);//获取视频item的信息 } else { populateMediaItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_IMAGES);//获取图片item的信息 } final long setId = sortCursor.getLong(MEDIA_BUCKET_ID_INDEX); final MediaSet set = findSet(setId, acceleratedSets); if (set != null) { set.addItem(item);//将item加入相册集 } } while (sortCursor.moveToNext()); } } finally { if (sortCursor != null) sortCursor.close(); } } catch (Exception e) { // If the database operation failed for any reason ; } if (sets.size() > 0) { writeItemsToCache(sets);//缓存sets的item Log.i(TAG, "Done building items."); } }
关键函数writeItemsToCache就是缓存每个set的items。
private static final void writeItemsToCache(final ArrayList<MediaSet> sets) { final int numSets = sets.size(); for (int i = 0; i < numSets; ++i) { if (Thread.interrupted()) { return; } writeItemsForASet(sets.get(i)); } sAlbumCache.flush(); }
writeItemsForASet就是将item的信息写入chunk文件,sAlbumCache.flush就是将chunk文件信息写入index文件。
private static final void writeItemsForASet(final MediaSet set) { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256)); try { final ArrayList<MediaItem> items = set.getItems(); final int numItems = items.size(); dos.writeInt(numItems); dos.writeLong(set.mMinTimestamp); dos.writeLong(set.mMaxTimestamp); for (int i = 0; i < numItems; ++i) { MediaItem item = items.get(i); if (set.mId == LocalDataSource.CAMERA_BUCKET_ID || set.mId == LocalDataSource.DOWNLOAD_BUCKET_ID) { // Reverse the display order for the camera bucket - want // the latest first. item = items.get(numItems - i - 1); } dos.writeLong(item.mId); Utils.writeUTF(dos, item.mCaption); Utils.writeUTF(dos, item.mMimeType); dos.writeInt(item.getMediaType()); dos.writeDouble(item.mLatitude); dos.writeDouble(item.mLongitude); dos.writeLong(item.mDateTakenInMs); dos.writeBoolean(item.mTriedRetrievingExifDateTaken); dos.writeLong(item.mDateAddedInSec); dos.writeLong(item.mDateModifiedInSec); dos.writeInt(item.mDurationInSec); dos.writeInt((int) item.mRotation); Utils.writeUTF(dos, item.mFilePath); } dos.flush(); sAlbumCache.put(set.mId, bos.toByteArray(), 0); dos.close(); } catch (IOException e) { Log.e(TAG, "Error writing to diskcache for set " + set.mName); sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); } }