我们这里主要讲本地sd卡的数据,pisaca看情况后续再作分析。
数据操作设计的类包括:CacheService,MediaFeed,LocalDataSource,DiskCache,MediaItem, MediaSet,MediaClustering。
数据操作包括几个方面:寻找媒体源(图片、视频),cache媒体源,将cache数据导出,将cache的媒体渲染到屏幕。
首先是找图片或者视频:
1. 如果当前cache文件不存在,或者当前cache文件记录的语言、国家和变量代码与系统的不一致的场景时,首先将所有cache文件(index和chunk文件)删除,接着将语言、国家和变量代码写入local-album-cachechunk0的文件中。
2. 启动一个线程,寻找图片或者视频,并将相册信息cache。
3. 再启动一个线程,将图片缩略图化,并cache。
4. 如果上一次还有一部分数据cache,执行步骤2和3。
5. 如果这一次有新的媒体文件需要cache,启动一个线程,将新的媒体文件cache。再执行步骤3。
关键的代码如下:
@Override protected void onHandleIntent(final Intent intent) { Log.i(TAG, "CacheService, onHandleIntent, Starting CacheService"); if (Environment.getExternalStorageState() == Environment.MEDIA_BAD_REMOVAL) { sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); } Locale locale = getLocaleForAlbumCache(); if (locale != null && locale.equals(Locale.getDefault())) { // The cache is in the same locale as the system locale. if (!isCacheReady(false)) { // The albums and their items have not yet been cached, we need // to run the service. Log.i(TAG, "CacheService, onHandleIntent, start new cache thread"); startNewCacheThread(); } else { Log.i(TAG, "CacheService, onHandleIntent, start new cache thread for dirty sets"); startNewCacheThreadForDirtySets(); } } else { // The locale has changed, we need to regenerate the strings. Log.i(TAG, "CacheService, onHandleIntent, delete all"); sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); startNewCacheThread(); } if (intent.getBooleanExtra("checkthumbnails", false)) { Log.i(TAG, "CacheService, onHandleIntent, start new thumbnail thread"); startNewThumbnailThread(this); } else { final Thread existingThread = THUMBNAIL_THREAD.getAndSet(null); if (existingThread != null) { existingThread.interrupt(); } } }
先看这个函数:
private static final Locale getLocaleForAlbumCache() { Log.i(TAG, "CacheService, getLocaleForAlbumCache"); final byte[] data = sAlbumCache.get(ALBUM_CACHE_LOCALE_INDEX, 0); if (data != null && data.length > 0) { ByteArrayInputStream bis = new ByteArrayInputStream(data); DataInputStream dis = new DataInputStream(bis); try { String country = Utils.readUTF(dis); if (country == null) country = ""; String language = Utils.readUTF(dis); if (language == null) language = ""; String variant = Utils.readUTF(dis); if (variant == null) variant = ""; final Locale locale = new Locale(language, country, variant); dis.close(); bis.close(); return locale; } catch (IOException e) { // Could not read locale in cache. Log.i(TAG, "Error reading locale from cache."); return null; } } return null; }
我们看到 sAlbumCache这个变量,这个变量的定义是这样的:
public static final DiskCache sAlbumCache = new DiskCache("local-album-cache");
我们来看看DiskCache的构造函数做了什么?
public DiskCache(String cacheDirectoryName) { String cacheDirectoryPath = CacheService.getCachePath(cacheDirectoryName); // Create the cache directory if needed. File cacheDirectory = new File(cacheDirectoryPath); if (!cacheDirectory.isDirectory() && !cacheDirectory.mkdirs()) { Log.e(TAG, "Unable to create cache directory " + cacheDirectoryPath); } mCacheDirectoryPath = cacheDirectoryPath; loadIndex(); }
public static final String getCachePath(final String subFolderName) { return Environment.getExternalStorageDirectory() + "/Android/data/com.luowei.media/cache/" + subFolderName; }
原来如此,在sd卡建立了一个目录/Android/data/com.luowei.media/cache/local-album-cache。
建立目录后呢?
private void loadIndex() { final String indexFilePath = getIndexFilePath(); try { // Open the input stream. final FileInputStream fileInput = new FileInputStream(indexFilePath); final BufferedInputStream bufferedInput = new BufferedInputStream(fileInput, 1024); final DataInputStream dataInput = new DataInputStream(bufferedInput); // Read the header. final int magic = dataInput.readInt();//4 final int version = dataInput.readInt();//4 boolean valid = true; if (magic != INDEX_HEADER_MAGIC) { Log.e(TAG, "Index file appears to be corrupt (" + magic + " != " + INDEX_HEADER_MAGIC + "), " + indexFilePath); valid = false; } if (valid && version != INDEX_HEADER_VERSION) { // Future versions can implement upgrade in this case. Log.e(TAG, "Index file version " + version + " not supported"); valid = false; } if (valid) { mTailChunk = dataInput.readShort();//2 } // Read the entries. if (valid) { // Parse the index file body into the in-memory map. final int numEntries = dataInput.readInt();//4 mIndexMap = new LongSparseArray<Record>(numEntries); synchronized (mIndexMap) { for (int i = 0; i < numEntries; ++i) { final long key = dataInput.readLong();//8 final int chunk = dataInput.readShort();//2 final int offset = dataInput.readInt();//4 final int size = dataInput.readInt();//4 final int sizeOnDisk = dataInput.readInt();//4 final long timestamp = dataInput.readLong();//8 mIndexMap.append(key, new Record(chunk, offset, size, sizeOnDisk, timestamp)); } } } dataInput.close(); if (!valid) { deleteAll(); } } catch (FileNotFoundException e) { // If the file does not exist the cache is empty, so just continue. } catch (IOException e) { Log.e(TAG, "Unable to read the index file " + indexFilePath); } finally { if (mIndexMap == null) { mIndexMap = new LongSparseArray<Record>(); } } }
首先找索引文件,索引文件路径是什么?
private String getIndexFilePath() { return mCacheDirectoryPath + INDEX_FILE_NAME; }
具体路径是 sd卡下/Android/data/com.luowei.media/cache/local-album-cacheindex。如果文件不存在,就只做一件事,将mIndexMap初始化;如果文件存在,读取文件头,如果文件头是00 00 CA FE 00 00 00 02这个格式,说明是标准的索引文件,继续分析此文件,将数据写入mIndexMap映射表。
接着继续分析getLocaleForAlbumCache函数,sAlbumCache.get做了什么?
public byte[] get(long key, long timestamp) { // Look up the record for the given key. Record record = null; synchronized (mIndexMap) { record = mIndexMap.get(key); } if (record != null) { // Read the chunk from the file. if (record.timestamp != timestamp) { return null; } try { RandomAccessFile chunkFile = getChunkFile(record.chunk); if (chunkFile != null) { byte[] data = new byte[record.size]; chunkFile.seek(record.offset); chunkFile.readFully(data); return data; } } catch (Exception e) { Log.e(TAG, "Unable to read from chunk file"); } } return null; }
如果mIndexMap没有ALBUM_CACHE_LOCALE_INDEX这个key的数据,返回null;如果有,接着比较。如果记录的时间戳也不一致,返回null。那getChunkFile又有什么呢?
private RandomAccessFile getChunkFile(int chunk) { RandomAccessFile chunkFile = null; synchronized (mChunkFiles) { chunkFile = mChunkFiles.get(chunk); } if (chunkFile == null) { final String chunkFilePath = mCacheDirectoryPath + CHUNK_FILE_PREFIX + chunk; Log.i(TAG, "DiskCache, chunkFilePath:"+chunkFilePath); try { chunkFile = new RandomAccessFile(chunkFilePath, "rw"); } catch (FileNotFoundException e) { Log.e(TAG, "Unable to create or open the chunk file " + chunkFilePath); } synchronized (mChunkFiles) { mChunkFiles.put(chunk, chunkFile); } } return chunkFile; }
mChunkFiles也是一个chunk映射表,local-album-cache对应的chunk文件路径对应为/Android/data/com.luowei.media/cache/local-album-cachechunk0。如果chunk文件不存在,需要建立这个文件,同时将这个文件加入mChunkFiles映射表。
回到getLocaleForAlbumCache这个函数,发现这个chunk文件的头记录语言、地区和变量代码。再返回onHandleIntent函数,如果getLocaleForAlbumCache取回的代码是空的或者跟本机的代码不一致,就需要做三件事情:
1. 将/Android/data/com.luowei.media/cache/local-album-cache目录下所有文件,其实这里面有个bug,请读者自行发现;
2. 创建chunk和index文件,并将语言、地区和变量代码写入chunk文件,这就是putLocaleForAlbumCache函数所作的事情;
3. 开启新的cache线程:startNewCacheThread。