花开两朵,各表一枝。上回书讲到了数据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());
}
}