一 XUtils圖檔緩存子產品源碼分析
1.BitmapUtils對象建立
bitmapUtils = new BitmapUtils(appContext);
public BitmapUtils(Context context) {
this(context, null);
}
public BitmapUtils(Context context, String diskCachePath) {
if (context == null) {
throw new IllegalArgumentException("context may not be null");
}
this.context = context.getApplicationContext();
globalConfig = BitmapGlobalConfig.getInstance(this.context, diskCachePath);
defaultDisplayConfig = new BitmapDisplayConfig();
}
public BitmapUtils(Context context, String diskCachePath, int memoryCacheSize) {
this(context, diskCachePath);
globalConfig.setMemoryCacheSize(memoryCacheSize);//記憶體緩存大小
}
public BitmapUtils(Context context, String diskCachePath, int memoryCacheSize, int
diskCacheSize) {
this(context, diskCachePath);
globalConfig.setMemoryCacheSize(memoryCacheSize);
globalConfig.setDiskCacheSize(diskCacheSize);//磁盤緩存大小
}
public BitmapUtils(Context context, String diskCachePath, float memoryCachePercent) {
this(context, diskCachePath);
globalConfig.setMemCacheSizePercent(memoryCachePercent);
}
public BitmapUtils(Context context, String diskCachePath, float memoryCachePercent, int
diskCacheSize) {
this(context, diskCachePath);
globalConfig.setMemCacheSizePercent(memoryCachePercent);
globalConfig.setDiskCacheSize(diskCacheSize);
}
1.1 擷取BitmapGlobalConfig對象BitmapGlobalConfig.getInstance(this.context, diskCachePath);
public synchronized static BitmapGlobalConfig getInstance(Context context,
String diskCachePath) {
if (TextUtils.isEmpty(diskCachePath)) {
diskCachePath = OtherUtils.getDiskCacheDir(context, "xBitmapCache");//緩存路徑
}
if (configMap.containsKey(diskCachePath)) {//有緩存的對象
return configMap.get(diskCachePath);
} else {
BitmapGlobalConfig config = new BitmapGlobalConfig(context, diskCachePath);
configMap.put(diskCachePath, config);
return config;
}
}
/**
* @param context
* @param dirName Only the folder name, not full path.
* @return app_cache_path/dirName
* 1.檢查外部存儲如果挂載去擷取外部緩存路徑
* 2.如果擷取外部緩存路徑為空,擷取内部緩存路徑
*/
public static String getDiskCacheDir(Context context, String dirName) {
String cachePath = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {//外部存儲挂載
File externalCacheDir = context.getExternalCacheDir();
if (externalCacheDir != null) {
cachePath = externalCacheDir.getPath();
}
}
if (cachePath == null) {
File cacheDir = context.getCacheDir();
if (cacheDir != null && cacheDir.exists()) {
cachePath = cacheDir.getPath();
}
}
return cachePath + File.separator + dirName;
}
/**
* @param context
* @param diskCachePath If null, use default appCacheDir+"/xBitmapCache"
* BitmapGlobalConfig構造函數
*/
private BitmapGlobalConfig(Context context, String diskCachePath) {
if (context == null) throw new IllegalArgumentException("context may not be null");
this.mContext = context;
this.diskCachePath = diskCachePath;
initBitmapCache();
}
1.1.1 初始化磁盤與記憶體緩存initBitmapCache();
/**
* 初始化記憶體緩存與磁盤緩存
*/
private void initBitmapCache() {
new BitmapCacheManagementTask().execute(BitmapCacheManagementTask
.MESSAGE_INIT_MEMORY_CACHE);
new BitmapCacheManagementTask().execute(BitmapCacheManagementTask.MESSAGE_INIT_DISK_CACHE);
}
@Override
protected Object[] doInBackground(Object... params) {
if (params == null || params.length == 0) return params;
BitmapCache cache = getBitmapCache();
if (cache == null) return params;
try {
switch ((Integer) params[0]) {
case MESSAGE_INIT_MEMORY_CACHE:
cache.initMemoryCache();//初始化記憶體緩存
break;
case MESSAGE_INIT_DISK_CACHE:
cache.initDiskCache();//初始化磁盤緩存
break;
case MESSAGE_FLUSH:
cache.flush();
break;
case MESSAGE_CLOSE:
cache.clearMemoryCache();
cache.close();
break;
case MESSAGE_CLEAR:
cache.clearCache();
break;
case MESSAGE_CLEAR_MEMORY:
cache.clearMemoryCache();
break;
case MESSAGE_CLEAR_DISK:
cache.clearDiskCache();
break;
case MESSAGE_CLEAR_BY_KEY:
if (params.length != 2) return params;
cache.clearCache(String.valueOf(params[1]));
break;
case MESSAGE_CLEAR_MEMORY_BY_KEY:
if (params.length != 2) return params;
cache.clearMemoryCache(String.valueOf(params[1]));
break;
case MESSAGE_CLEAR_DISK_BY_KEY:
if (params.length != 2) return params;
cache.clearDiskCache(String.valueOf(params[1]));
break;
default:
break;
}
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
}
return params;
}
1.1.1.1 初始化記憶體緩存 initMemoryCache();
/**
* Initialize the memory cache
* 1.判斷是否打開記憶體緩存,未打開就傳回
* 2.如果LruMemoryCache對象不為空就清空緩存
* 3.LruMemoryCache對象為空就去建立對象覆寫方法傳回每個緩存單元的大小
*/
public void initMemoryCache() {
if (!globalConfig.isMemoryCacheEnabled()) return;
// Set up memory cache
if (mMemoryCache != null) {
try {
clearMemoryCache();
} catch (Throwable e) {
}
}
mMemoryCache = new LruMemoryCache<MemoryCacheKey, Bitmap>(globalConfig.getMemoryCacheSize
()) {
/**
* Measure item size in bytes rather than units which is more practical
* for a bitmap cache
*/
@Override
protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) {
if (bitmap == null) return 0;
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
1.1.1.2 初始化磁盤緩存 initDiskCache() ;
/**
* Initializes the disk cache. Note that this includes disk access so this should not be
* executed on the main/UI thread. By default an ImageCache does not initialize the disk
* cache when it is created, instead you should call initDiskCache() to initialize it on a
* background thread.
* 初始化磁盤緩存
* 1.擷取緩存路徑生成檔案目錄,确定可緩存磁盤占用空間大小
* 2.根據條件open相應LruDiskCache對象
* 3.設定檔案名設定器
*/
public void initDiskCache() {
// Set up disk cache
synchronized (mDiskCacheLock) {
if (globalConfig.isDiskCacheEnabled() && (mDiskLruCache == null || mDiskLruCache
.isClosed())) {
File diskCacheDir = new File(globalConfig.getDiskCachePath());
if (diskCacheDir.exists() || diskCacheDir.mkdirs()) {
long availableSpace = OtherUtils.getAvailableSpace(diskCacheDir);//目錄總可用空間
long diskCacheSize = globalConfig.getDiskCacheSize();//磁盤緩存大小
diskCacheSize = availableSpace > diskCacheSize ? diskCacheSize : availableSpace;
try {
//第一個參數指定的是資料的緩存位址,第二個參數指定目前應用程式的版本号,第三個參數指定同一個key可以對應多少個緩存檔案,基本都是傳1
// ,第四個參數指定最多可以緩存多少位元組的資料。
mDiskLruCache = LruDiskCache.open(diskCacheDir, 1, 1, diskCacheSize);
mDiskLruCache.setFileNameGenerator(globalConfig.getFileNameGenerator());
LogUtils.d("create disk cache success");
} catch (Throwable e) {
mDiskLruCache = null;
LogUtils.e("create disk cache error", e);
}
}
}
}
}
/**
* OthreUtils.java
* 傳回檔案對應目錄下可用空間
* */
public static long getAvailableSpace(File dir) {
try {
final StatFs stats = new StatFs(dir.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
return -1;
}
}
1.1.1.2.1 建立LruDiskCache對象LruDiskCache.open(diskCacheDir, 1, 1, diskCacheSize);
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @throws IOException if reading or writing the cache directory fails
* <p/>
* LruDiskCache.java
* 1.判斷傳入參數是否合理,不合理抛異常。
* 2.如果日志檔案存在,讀取日志内容生成緩存實體并設定,計算緩存檔案總大小。生成日志檔案輸入流
* 3.如果日志檔案不存在,建立檔案
* 4.傳回LruDiskCache對象
*/
public static LruDiskCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {//日志備份檔案存在
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {//源檔案也存在,删除備份
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// Prefer to pick up where we left off.
LruDiskCache cache = new LruDiskCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {//日志檔案存在
try {
cache.readJournal();//讀取日志
cache.processJournal();//計算總大小
cache.journalWriter = new BufferedWriter(new OutputStreamWriter(new
FileOutputStream(cache.journalFile, true), HTTP.US_ASCII));//寫檔案流
return cache;
} catch (Throwable journalIsCorrupt) {
LogUtils.e("DiskLruCache " + directory + " is corrupt: " + journalIsCorrupt
.getMessage() + ", removing", journalIsCorrupt);
cache.delete();
}
}
// Create a new empty cache.
//重建一個日志檔案。
if (directory.exists() || directory.mkdirs()) {
cache = new LruDiskCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
}
return cache;
}
1.1.1.2.1.1 讀取日志檔案cache.readJournal();
/**
* 讀取日志檔案,設定緩存實體必要資訊。
* 執行步驟:
* 1.讀取日志頭部,判斷是否合理。
* 2.循環讀取日志内容。
*/
private void readJournal() throws IOException {
StrictLineReader reader = null;
try {
reader = new StrictLineReader(new FileInputStream(journalFile));
String magic = reader.readLine();//第一行是個固定的字元串“libcore.io
// .DiskLruCache”,标志着我們使用的是DiskLruCache技術。
String version = reader.readLine();//第二行是DiskLruCache的版本号,這個值是恒為1的.
String appVersionString = reader.readLine();//第三行是應用程式的版本号,我們在open()
// 方法裡傳入的版本号是什麼這裡就會顯示什麼。
String valueCountString = reader.readLine();//第四行是valueCount,這個值也是在open()
// 方法中傳入的,通常情況下都為1。
String blank = reader.readLine();//第五行是一個空行。
if (!MAGIC.equals(magic) || !VERSION.equals(version) || !Integer.toString(appVersion)
.equals(appVersionString) || !Integer.toString(valueCount).equals
(valueCountString) || !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic + ", " +
"" + version + ", " + valueCountString + ", " + blank + "]");
}
int lineCount = 0;
while (true) {//死循環讀取日志檔案
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
} finally {
IOUtils.closeQuietly(reader);
}
}
/**
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
* this end of line marker is not included in the result.
*
* @return the next line from the input.
* @throws IOException for underlying {@code InputStream} errors.
* @throws EOFException for the end of source stream.
* StrictLineReader.java
* 讀取日志檔案
* 1.讀取檔案輸入到位元組數組,初始化位置變量
* 2.對位元組數組逐個周遊,遇到換行符構造字元串傳回并且更新位置變量
* 3.如果字元數組不存在換行符,建立ByteArrayOutputStream假設已讀出80位元組,覆寫toString
* 4.死循環内循環判斷遇到換行符ByteArrayOutputStream寫出到位元組緩存,更新位置變量,傳回toString
*/
public String readLine() throws IOException {
synchronized (in) {
if (buf == null) {
throw new IOException("LineReader is closed");
}
// Read more data if we are at the end of the buffered data.
// Though it's an error to read after an exception, we will let {@code fillBuf()}
// throw again if that happens; thus we need to handle end == -1 as well as end
// == pos.
//初始化pos與end變量
if (pos >= end) {
fillBuf();
}
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {//新行
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;//lineEnd之後為換行
String res = new String(buf, pos, lineEnd - pos, charset.name());
pos = i + 1;//重置起始位
return res;
}
}
// Let's anticipate up to 80 characters on top of those already read.
//假定80個字元已經讀出
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
@Override
public String toString() {
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
try {
return new String(buf, 0, length, charset.name());
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // Since we control the charset this
// will never happen.
}
}
};
while (true) {
out.write(buf, pos, end - pos);//将資料寫入位元組數組緩存
// Mark unterminated line in case fillBuf throws EOFException or IOException.
end = -1;
fillBuf();
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {//新行
if (i != pos) {
out.write(buf, pos, i - pos);//寫出這一行
}
out.flush();
pos = i + 1;
return out.toString();
}
}
}
}
}
/**
* Reads new input data into the buffer. Call only with pos == end or end == -1,
* depending on the desired outcome if the function throws.
*/
private void fillBuf() throws IOException {
int result = in.read(buf, 0, buf.length);
if (result == -1) {
throw new EOFException();
}
pos = 0;
end = result;
}
1.1.1.2.1.1.1 readJournalLine(reader.readLine());
/**
* 讀取日志檔案内容。
* 注意日志格式例如:D xxxxxxxx
* 第六行是以一個DIRTY字首開始的,後面緊跟着緩存圖檔的key。
* 通常我們看到DIRTY這個字樣都不代表着什麼好事情,意味着這是一條髒資料。
* 沒錯,每當我們調用一次DiskLruCache的edit()方法時,都會向journal檔案中寫入一條DIRTY記錄,表示我們正準備寫入一條緩存資料,但不知結果如何。
* 然後調用commit()方法表示寫入緩存成功,這時會向journal中寫入一條CLEAN記錄,意味着這條“髒”資料被“洗幹淨了”,調用abort()
* 方法表示寫入緩存失敗,這時會向journal中寫入一條REMOVE記錄。
* 也就是說,每一行DIRTY的key,後面都應該有一行對應的CLEAN或者REMOVE的記錄,否則這條資料就是“髒”的,會被自動删除掉。
* 除了CLEAN字首和key之外,後面還有一個152313,這是什麼意思呢?
* 其實,DiskLruCache會在每一行CLEAN記錄的最後加上該條緩存資料的大小,以位元組為機關。
* <p/>
* 執行步驟:
* 1.根據第一個空格解析字元串擷取指令辨別:CRUD
* 2.擷取第二個空格索引,如果不存在第二個空格擷取緩存的key,并且指令辨別為D,就删除緩存實體函數傳回。
* 如果存在第二個空格,得到緩存檔案的key
* 3.根據key擷取緩存的實體,如果不存在就建立。
* 4.根據指令辨別符執行操作。
* 對于CLEAN格式:C XXXXXX txxxx 123000,首先判斷是否有過期字首,如果存在儲存過期時間不存在設定為最大值。接着儲存緩存檔案大小。
* 對于UDPATE:擷取Editor對象。
*/
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
char lineTag = 0;
if (firstSpace == 1) {//指令辨別
lineTag = line.charAt(0);
} else {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);//第二個空格索引
final String diskKey;
if (secondSpace == -1) {//不存在第二個空格
diskKey = line.substring(keyBegin);//擷取緩存檔案的key
if (lineTag == DELETE) {//删除指令
lruEntries.remove(diskKey);//移除這個key
return;
}
} else {//存在第二個空格
diskKey = line.substring(keyBegin, secondSpace);//擷取緩存檔案的key
}
Entry entry = lruEntries.get(diskKey);//緩存實體
if (entry == null) {
entry = new Entry(diskKey);
lruEntries.put(diskKey, entry);
}
switch (lineTag) {
case CLEAN: {
entry.readable = true;
entry.currentEditor = null;
String[] parts = line.substring(secondSpace + 1).split(" ");
if (parts.length > 0) {
try {
if (parts[0].charAt(0) == EXPIRY_PREFIX) {//過期字首
entry.expiryTimestamp = Long.valueOf(parts[0].substring(1));
entry.setLengths(parts, 1);//設定緩存檔案的大小
} else {//不存在過期字首
entry.expiryTimestamp = Long.MAX_VALUE;
entry.setLengths(parts, 0);//設定緩存檔案的大小
}
} catch (Throwable e) {
throw new IOException("unexpected journal line: " + line);
}
}
break;
}
case UPDATE: {
entry.currentEditor = new Editor(entry);
break;
}
case READ: {
// This work was already done by calling lruEntries.get().
break;
}
default: {
throw new IOException("unexpected journal line: " + line);
}
}
}
1.1.1.2.1.2 計算總大小 cache.processJournal();
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
* 執行步驟:
* 1.删除日志臨時檔案
* 2.疊代實體,如果不是update直接累加計算緩存檔案總大小,是update删除實體對應的dirty及clean檔案。
*/
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);//删除日志臨時檔案
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];//累計計算緩存檔案總大小
}
} else {//update狀态删除實體的Dirty和Clean檔案 ?
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
2.顯示圖檔, mBitmapUtils.display(mRecyclerViewHolder.mArticleImage, article.getPhotoURL());
public <T extends View> void display(T container, String uri) {
display(container, uri, null, null);
}
public <T extends View> void display(T container, String uri, BitmapDisplayConfig
displayConfig) {
display(container, uri, displayConfig, null);
}
public <T extends View> void display(T container, String uri, BitmapLoadCallBack<T> callBack) {
display(container, uri, null, callBack);
}
/**
* 1.設定加載回調,設定展示配置BitmapDisplayConfig,設定Bitmap大小參數
* 2.先從記憶體中擷取Bitmap,如果存在設定回調狀态
* 3.記憶體中不存在,檢查如果加載Bitmap任務不存在,建立BitmapLoadTask對象獲得PriorityExecutor線程池加載器
* 4.從磁盤擷取緩存檔案,緩存存在并且線程池加載器正在忙,切換磁盤緩存加載器
* 5.獲得配置正在加載Drawable對象封裝添加對應Task的AsyncDrawable對象
* 6.回調接口設定該AsyncDrawable對象
* 7.傳入線程池加載器執行BitmapLoadTask對象
*/
public <T extends View> void display(T container, String uri, BitmapDisplayConfig
displayConfig, BitmapLoadCallBack<T> callBack) {
if (container == null) {
return;
}
if (callBack == null) {
callBack = new DefaultBitmapLoadCallBack<T>();//預設圖檔加載回調
}
if (displayConfig == null || displayConfig == defaultDisplayConfig) {
displayConfig = defaultDisplayConfig.cloneNew();
}
// Optimize Max Size
BitmapSize size = displayConfig.getBitmapMaxSize();
displayConfig.setBitmapMaxSize(BitmapCommonUtils.optimizeMaxSizeByView(container, size
.getWidth(), size.getHeight()));//設定Bitmap顯示的最大值
container.clearAnimation();//清除動畫
if (TextUtils.isEmpty(uri)) {//下載下傳位址為空,回調失敗傳回
callBack.onLoadFailed(container, uri, displayConfig.getLoadFailedDrawable());
return;
}
// start loading
callBack.onPreLoad(container, uri, displayConfig);
// find bitmap from mem cache.先從記憶體中擷取Bitmap
Bitmap bitmap = globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);
if (bitmap != null) {//記憶體中存在Bitmap,設定回調狀态結果
callBack.onLoadStarted(container, uri, displayConfig);
callBack.onLoadCompleted(container, uri, bitmap, displayConfig, BitmapLoadFrom
.MEMORY_CACHE);
} else if (!bitmapLoadTaskExist(container, uri, callBack)) {//bitmap加載任務不存在
final BitmapLoadTask<T> loadTask = new BitmapLoadTask<T>(container, uri,
displayConfig, callBack);//建立下載下傳任務
// get executor 獲得加載執行器
PriorityExecutor executor = globalConfig.getBitmapLoadExecutor();
File diskCacheFile = this.getBitmapFileFromDiskCache(uri);//從磁盤中擷取緩存
boolean diskCacheExist = diskCacheFile != null && diskCacheFile.exists();
if (diskCacheExist && executor.isBusy()) {//檔案存在,并且Bitmap加載線程池忙
executor = globalConfig.getDiskCacheExecutor();//擷取磁盤緩存處理線程池執行器
}
// set loading image
Drawable loadingDrawable = displayConfig.getLoadingDrawable();//設定正在加載顯示的圖檔
callBack.setDrawable(container, new AsyncDrawable<T>(loadingDrawable, loadTask));
loadTask.setPriority(displayConfig.getPriority());
loadTask.executeOnExecutor(executor);
}
}
2.1 設定Bitmap顯示的最大值displayConfig.setBitmapMaxSize(BitmapCommonUtils.optimizeMaxSizeByView(container, size.getWidth(), size.getHeight()));
/**
* BitmapCommonUtils.java
* 設定Bitmap大小傳回BitmapSize。
* 1.如果使用者設定了最大最小值,直接構造傳回BitmapSize對象。
* 2.擷取View的Layout參數設定大小
* 3.如果上述得到值小于0,反射擷取ImageView的“mMaxWidth”“mMaxHeight”值
* 4.如果上述得到值小于0,擷取視窗大小,并進行設定。
* */
public static BitmapSize optimizeMaxSizeByView(View view, int maxImageWidth,
int maxImageHeight) {
int width = maxImageWidth;
int height = maxImageHeight;
if (width > 0 && height > 0) {
return new BitmapSize(width, height);
}
final ViewGroup.LayoutParams params = view.getLayoutParams();
if (params != null) {//根據父容器參數設定View顯示大小
if (params.width > 0) {
width = params.width;
} else if (params.width != ViewGroup.LayoutParams.WRAP_CONTENT) {
width = view.getWidth();
}
if (params.height > 0) {
height = params.height;
} else if (params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
height = view.getHeight();
}
}
if (width <= 0) width = getImageViewFieldValue(view, "mMaxWidth");//根據ImageView聲明字段擷取大小
if (height <= 0) height = getImageViewFieldValue(view, "mMaxHeight");
BitmapSize screenSize = getScreenSize(view.getContext());
if (width <= 0) width = screenSize.getWidth();//根據視窗大小設定大小
if (height <= 0) height = screenSize.getHeight();
return new BitmapSize(width, height);
}
/**
* 獲得ImageView聲明的mMaxWidth,mMaxHeight字段數值
*/
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
if (object instanceof ImageView) {
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = (Integer) field.get(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {//不是預設值
value = fieldValue;
}
} catch (Throwable e) {
}
}
return value;
}
2.2 從記憶體緩存中擷取Bitmap,globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);
public BitmapCache getBitmapCache() {
if (bitmapCache == null) {
bitmapCache = new BitmapCache(this);
}
return bitmapCache;
}
/**
* Get the bitmap from memory cache.
*
* @param uri Unique identifier for which item to get
* @param config
* @return The bitmap if found in cache, null otherwise
* 從記憶體緩存中查找是否存在Bitmap
*/
public Bitmap getBitmapFromMemCache(String uri, BitmapDisplayConfig config) {
if (mMemoryCache != null && globalConfig.isMemoryCacheEnabled()) {
MemoryCacheKey key = new MemoryCacheKey(uri, config);
return mMemoryCache.get(key);
}
return null;
}
2.3 判斷Bitmap加載任務是否存在,bitmapLoadTaskExist(container, uri, callBack)
/**
* 判斷目前bitmap加載任務是否已經存在
* 1.傳入container,獲得Drawable對象強制轉換判斷時候存在下載下傳任務
* 2.如果存在下載下傳任務,在判斷該任務與最新任務差異,存在就取消已存在任務
* 3.不存在就傳回false
*/
private static <T extends View> boolean bitmapLoadTaskExist(T container, String uri,
BitmapLoadCallBack<T> callBack) {
final BitmapLoadTask<T> oldLoadTask = getBitmapTaskFromContainer(container, callBack);
if (oldLoadTask != null) {
final String oldUrl = oldLoadTask.uri;
if (TextUtils.isEmpty(oldUrl) || !oldUrl.equals(uri)) {
oldLoadTask.cancel(true);//取消舊的下載下傳任務
} else {
return true;
}
}
return false;
}
private static <T extends View> BitmapLoadTask<T> getBitmapTaskFromContainer(T container,
BitmapLoadCallBack<T> callBack) {
if (container != null) {
final Drawable drawable = callBack.getDrawable(container);
if (drawable instanceof AsyncDrawable) {//擷取BitmapTask對象
final AsyncDrawable<T> asyncDrawable = (AsyncDrawable<T>) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
2.4 bitmap加載任務不存在,從磁盤中擷取緩存 this.getBitmapFileFromDiskCache(uri);
public File getBitmapFileFromDiskCache(String uri) {
return globalConfig.getBitmapCache().getBitmapFileFromDiskCache(uri);
}
/**
* Get the bitmap file from disk cache.
*
* @param uri Unique identifier for which item to get
* @return The file if found in cache.
*/
public File getBitmapFileFromDiskCache(String uri) {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
return mDiskLruCache.getCacheFile(uri, DISK_CACHE_INDEX);
} else {
return null;
}
}
}
/**LruDiskCache.java
* 擷取緩存檔案
*/
public File getCacheFile(String key, int index) {
String diskKey = fileNameGenerator.generate(key);//生成key
File result = new File(this.directory, diskKey + "." + index);
if (result.exists()) {
return result;
} else {
try {
this.remove(key);
} catch (IOException ignore) {
}
return null;
}
}
2.5執行Btimap加載人任務loadTask.executeOnExecutor(executor);
/**
* BitmapLoadTask.java
* 背景線程執行
* 1.死循環判斷暫定狀态,并阻塞線程
* 2.釋出狀态,從磁盤緩存中擷取Birmap
* 3.磁盤緩存中不存在去下載下傳
*/
@Override
protected Bitmap doInBackground(Object... params) {
synchronized (pauseTaskLock) {
while (pauseTask && !this.isCancelled()) {
try {
pauseTaskLock.wait();//線程阻塞
if (cancelAllTask) {
return null;
}
} catch (Throwable e) {
}
}
}
Bitmap bitmap = null;
// get cache from disk cache
if (!this.isCancelled() && this.getTargetContainer() != null) {
this.publishProgress(PROGRESS_LOAD_STARTED);
bitmap = globalConfig.getBitmapCache().getBitmapFromDiskCache(uri, displayConfig);
}
// download image
if (bitmap == null && !this.isCancelled() && this.getTargetContainer() != null)
{//磁盤緩存不存在,去下載下傳
bitmap = globalConfig.getBitmapCache().downloadBitmap(uri, displayConfig, this);
from = BitmapLoadFrom.URI;
}
return bitmap;
}
2.5.1 擷取磁盤緩存的Bitmap,globalConfig.getBitmapCache().getBitmapFromDiskCache(uri, displayConfig);
/**
* Get the bitmap from disk cache.
* @param uri
* @param config
* @return
* BitmapCache.java
* 從磁盤緩存中擷取Bitmap
* 1.檢查LruDiskCache是否存在,初始化LruDiskCache對象
* 2.根據傳入url擷取磁盤緩存處理Snapshot對象,如果為null即不存在緩存,直接傳回null
* 3.判斷配置是否為空或者顯示原圖,如果是,直接根據輸入流構造Bitmap。否則根據傳入配置壓縮Bitmap
* 4.根據需要處理Bitmap旋轉
* 5.将Bitmap添加到記憶體緩存中,傳回Bitmap
*/
public Bitmap getBitmapFromDiskCache(String uri, BitmapDisplayConfig config) {
if (uri == null || !globalConfig.isDiskCacheEnabled()) return null;
if (mDiskLruCache == null) {
initDiskCache();
}
if (mDiskLruCache != null) {
LruDiskCache.Snapshot snapshot = null;
try {
snapshot = mDiskLruCache.get(uri);
if (snapshot != null) {
Bitmap bitmap = null;
if (config == null || config.isShowOriginal()) {//顯示原圖
bitmap = BitmapDecoder.decodeFileDescriptor(snapshot.getInputStream
(DISK_CACHE_INDEX).getFD());
} else {//圖檔壓縮
bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(snapshot
.getInputStream(DISK_CACHE_INDEX).getFD(), config
.getBitmapMaxSize(), config.getBitmapConfig());
}
bitmap = rotateBitmapIfNeeded(uri, config, bitmap);//圖檔旋轉
bitmap = addBitmapToMemoryCache(uri, config, bitmap, mDiskLruCache
.getExpiryTimestamp(uri));
return bitmap;
}
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(snapshot);
}
}
return null;
}
2.5.1.1 擷取snapshot = mDiskLruCache.get(uri);
public Snapshot get(String key) throws IOException {
String diskKey = fileNameGenerator.generate(key);
return getByDiskKey(diskKey);
}
/**
* Returns a snapshot of the entry named {@code diskKey}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
* 1.根據key擷取對應Entry對象,檢查是否時間過期,如果過期就删除該Entry對象内的所有緩存檔案,删除過期對象檢查是否需要重構日志檔案,最後傳回null
* 2.列舉該Entry對象對應所有緩存檔案的輸入流,如果遇到不存在的檔案異常關閉輸入流傳回null
* 3.再次檢查是否重構日志,同時建立Snapshot對象傳回
*/
private synchronized Snapshot getByDiskKey(String diskKey) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(diskKey);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
// If expired, delete the entry.
if (entry.expiryTimestamp < System.currentTimeMillis()) {//删除過期
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(DELETE + " " + diskKey + '\n');//添加删除記錄
lruEntries.remove(diskKey);
if (journalRebuildRequired()) {//重構日志檔案
executorService.submit(cleanupCallable);
}
return null;
}
// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
//列舉該key對應的所有輸入流,對不存在
FileInputStream[] ins = new FileInputStream[valueCount];
try {
for (int i = 0; i < valueCount; i++) {
ins[i] = new FileInputStream(entry.getCleanFile(i));
}
} catch (FileNotFoundException e) {
// A file must have been deleted manually!
for (int i = 0; i < valueCount; i++) {
if (ins[i] != null) {
IOUtils.closeQuietly(ins[i]);
} else {
break;
}
}
return null;
}
journalWriter.append(READ + " " + diskKey + '\n');//添加讀取記錄
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
2.5.1.2 構造原圖bitmap = BitmapDecoder.decodeFileDescriptor(snapshot.getInputStream(DISK_CACHE_INDEX).getFD());
/***
* BitmapFactory.Options.inPurgeable;
* BitmapDecoder.java
* <p/>
* 如果 inPurgeable 設為True的話表示使用BitmapFactory建立的Bitmap
* 用于存儲Pixel的記憶體空間在系統記憶體不足時可以被回收,
* 在應用需要再次通路Bitmap的Pixel時(如繪制Bitmap或是調用getPixel),
* 系統會再次調用BitmapFactory decoder重新生成Bitmap的Pixel數組。
* 為了能夠重新解碼圖像,bitmap要能夠通路存儲Bitmap的原始資料。
* <p/>
* 在inPurgeable為false時表示建立的Bitmap的Pixel記憶體空間不能被回收,
* 這樣BitmapFactory在不停decodeByteArray建立新的Bitmap對象,
* 不同裝置的記憶體不同,是以能夠同時建立的Bitmap個數可能有所不同,
* 200個bitmap足以使大部分的裝置重新OutOfMemory錯誤。
* 當isPurgable設為true時,系統中記憶體不足時,
* 可以回收部分Bitmap占據的記憶體空間,這時一般不會出現OutOfMemory 錯誤。
*/
public static Bitmap decodeFileDescriptor(FileDescriptor fileDescriptor) {
synchronized (lock) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inInputShareable = true; // 與inPurgeable 一起使用
try {
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
return null;
}
}
}
2.5.1.3 構造縮放bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(snapshot.getInputStream(DISK_CACHE_INDEX).getFD(), config.getBitmapMaxSize(), config.getBitmapConfig());
public static Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fileDescriptor,
BitmapSize maxSize, Bitmap.Config
config) {
synchronized (lock) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//隻是擷取Bitmap參數
options.inPurgeable = true;
options.inInputShareable = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
options.inSampleSize = calculateInSampleSize(options, maxSize.getWidth(), maxSize
.getHeight());
options.inJustDecodeBounds = false;
if (config != null) {
options.inPreferredConfig = config;
}
try {
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
return null;
}
}
}
/**
* BitmapDecoder.java
* 計算實際Bitmap尺寸
*/
public static int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int
maxHeight) {
final int height = options.outHeight;//測量獲得Bitmap尺寸
final int width = options.outWidth;
int inSampleSize = 1;
if (width > maxWidth || height > maxHeight) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) maxHeight);//四舍五入取整
} else {
inSampleSize = Math.round((float) width / (float) maxWidth);
}
final float totalPixels = width * height;
final float maxTotalPixels = maxWidth * maxHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > maxTotalPixels) {//?
inSampleSize++;
}
}
return inSampleSize;
}
2.5.1.4 處理圖檔旋轉,bitmap = rotateBitmapIfNeeded(uri, config, bitmap);
/**
* 1.擷取配置判斷是否自動旋轉
* 2.如果是,擷取Bitmap檔案構造ExifInterface對象,擷取圖檔方向參數
* 3.如果方向不是0,矯正Bitmap方法傳回
*/
private synchronized Bitmap rotateBitmapIfNeeded(String uri, BitmapDisplayConfig config,
Bitmap bitmap) {
Bitmap result = bitmap;
if (config != null && config.isAutoRotation()) {
File bitmapFile = this.getBitmapFileFromDiskCache(uri);
if (bitmapFile != null && bitmapFile.exists()) {//擷取緩存Bitmap檔案對象
ExifInterface exif = null;//這個接口提供了圖檔檔案的旋轉,gps,時間等資訊。
try {
exif = new ExifInterface(bitmapFile.getPath());
} catch (Throwable e) {
return result;
}
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);//擷取圖檔方向參數
int angle = 0;
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
angle = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
angle = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
angle = 270;
break;
default:
angle = 0;
break;
}
if (angle != 0) {
Matrix m = new Matrix();
m.postRotate(angle);
result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap
.getHeight(), m, true);//重新構造Bitmap
bitmap.recycle();
bitmap = null;
}
}
}
return result;
}
2.5.1.5 将Bitmap添加到記憶體緩存中 bitmap = addBitmapToMemoryCache(uri, config, bitmap, mDiskLruCache.getExpiryTimestamp(uri));
/**
* 将Bitmap添加到記憶體中
*/
private Bitmap addBitmapToMemoryCache(String uri, BitmapDisplayConfig config, Bitmap bitmap,
long expiryTimestamp) throws IOException {
if (config != null) {
BitmapFactory bitmapFactory = config.getBitmapFactory();
if (bitmapFactory != null) {//?
bitmap = bitmapFactory.cloneNew().createBitmap(bitmap);
}
}
if (uri != null && bitmap != null && globalConfig.isMemoryCacheEnabled() && mMemoryCache
!= null) {
MemoryCacheKey key = new MemoryCacheKey(uri, config);
mMemoryCache.put(key, bitmap, expiryTimestamp);//添加到記憶體緩存
}
return bitmap;
}
2.5.2 磁盤緩存Bitmap不存在,去下載下傳bitmap = globalConfig.getBitmapCache().downloadBitmap(uri, displayConfig, this);
/**
* BitmapCache.java
* 下載下傳Bitmap
* 1.如果配置開啟磁盤緩存,下載下傳Bitmap到dirtyFile,根據結果clean或者delete,構造Bitmap
* 2.如果上述下載下傳Bitmap為null,将Bitmap下載下傳到記憶體輸出流中,構造Bitmap
* 3.處理Bitmap旋轉以及添加到記憶體緩存中
*/
public Bitmap downloadBitmap(String uri, BitmapDisplayConfig config, final BitmapUtils
.BitmapLoadTask<?> task) {
BitmapMeta bitmapMeta = new BitmapMeta();
OutputStream outputStream = null;
LruDiskCache.Snapshot snapshot = null;
try {
Bitmap bitmap = null;
// try download to disk,下載下傳到磁盤緩存
if (globalConfig.isDiskCacheEnabled()) {
if (mDiskLruCache == null) {
initDiskCache();
}
if (mDiskLruCache != null) {
try {
snapshot = mDiskLruCache.get(uri);
if (snapshot == null) {//緩存不存在
LruDiskCache.Editor editor = mDiskLruCache.edit(uri);
if (editor != null) {
outputStream = editor.newOutputStream(DISK_CACHE_INDEX);//dirtyFile
bitmapMeta.expiryTimestamp = globalConfig.getDownloader()
.downloadToStream(uri, outputStream, task);
//下載下傳Bitmap存儲到dirtyFile
if (bitmapMeta.expiryTimestamp < 0) {//下載下傳出錯
editor.abort();
return null;
} else {//下載下傳成功
editor.setEntryExpiryTimestamp(bitmapMeta.expiryTimestamp);
editor.commit();
}
snapshot = mDiskLruCache.get(uri);
}
}
if (snapshot != null) {
bitmapMeta.inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
bitmap = decodeBitmapMeta(bitmapMeta, config);//構造Bitmap
if (bitmap == null) {//擷取的Bitmap為null,删除對應clean檔案
bitmapMeta.inputStream = null;
mDiskLruCache.remove(uri);
}
}
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
}
}
}
// try download to memory stream,
if (bitmap == null) {
outputStream = new ByteArrayOutputStream();
bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri,
outputStream, task);
if (bitmapMeta.expiryTimestamp < 0) {//下載下傳失敗了
return null;
} else {//成功,去生成Bitmap
bitmapMeta.data = ((ByteArrayOutputStream) outputStream).toByteArray();
bitmap = decodeBitmapMeta(bitmapMeta, config);
}
}
if (bitmap != null) {//處理旋轉,添加到記憶體緩存
bitmap = rotateBitmapIfNeeded(uri, config, bitmap);
bitmap = addBitmapToMemoryCache(uri, config, bitmap, bitmapMeta.expiryTimestamp);
}
return bitmap;
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(outputStream);
IOUtils.closeQuietly(snapshot);
}
return null;
}
2.5.2.1 擷取Editor對象,mDiskLruCache.edit(uri);
/**
* Returns an editor for the entry named {@code Key}, or null if another
* edit is in progress.
*/
public Editor edit(String key) throws IOException {
String diskKey = fileNameGenerator.generate(key);
return editByDiskKey(diskKey, ANY_SEQUENCE_NUMBER);
}
/**
* 擷取Editor對象
* 1.檢查是否存在Entry對象,不存在建立添加到緩存
* 2.建立Editor對象,添加到Entry對象添加日志。
*/
private synchronized Editor editByDiskKey(String diskKey, long expectedSequenceNumber) throws
IOException {
checkNotClosed();
Entry entry = lruEntries.get(diskKey);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null || entry
.sequenceNumber != expectedSequenceNumber)) {
return null; // Snapshot is stale.?
}
if (entry == null) {//不存在緩存記錄
entry = new Entry(diskKey);
lruEntries.put(diskKey, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);//建立
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.write(UPDATE + " " + diskKey + '\n');
journalWriter.flush();
return editor;
}
2.5.2.2 擷取dirtyFile輸出流outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
* LruDiskCache.java
* 1.判斷Entry對象目前的Editor應用是否與此相同,不同異常
* 2.取出Entry對象對應的dirtyFile,建立輸出流并傳回
*/
public OutputStream newOutputStream(int index) throws IOException {
synchronized (LruDiskCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.getDirtyFile(index);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e) {
// Attempt to recreate the cache directory.
directory.mkdirs();
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e2) {
// We are unable to recover. Silently eat the writes.
return NULL_OUTPUT_STREAM;
}
}
return new FaultHidingOutputStream(outputStream);
}
}
2.5.2.3 去下載下傳BitmapbitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri, outputStream, task);
/**
* Download bitmap to outputStream by uri.
*
* @param uri file path, assets path(assets/xxx) or http url.
* @param outputStream
* @param task
* @return The expiry time stamp or -1 if failed to download.
* 下載下傳檔案到dirtyFile
* 1.url是檔案路徑,擷取響應輸入流和檔案長度,構造過期時間戳
* 2.url是assets路徑擷取響應輸入流和檔案長度,構造過期時間戳為最大值
* 3.url是網址,擷取URLConnection,擷取輸入流,構造時間戳
* 4.将擷取的輸入流讀入資源,寫出到提供的輸出流
* 5.傳回過期時間戳
*/
@Override
public long downloadToStream(String uri, OutputStream outputStream, final BitmapUtils
.BitmapLoadTask<?> task) {
if (task == null || task.isCancelled() || task.getTargetContainer() == null) return -1;
URLConnection urlConnection = null;
BufferedInputStream bis = null;
OtherUtils.trustAllHttpsURLConnection();
long result = -1;
long fileLen = 0;
long currCount = 0;
try {
if (uri.startsWith("/")) {//url是一個檔案路徑
FileInputStream fileInputStream = new FileInputStream(uri);
fileLen = fileInputStream.available();//該方法傳回可估算從這個輸入流中可無阻塞讀取剩餘的位元組數。
bis = new BufferedInputStream(fileInputStream);
result = System.currentTimeMillis() + this.getDefaultExpiry();
} else if (uri.startsWith("assets/")) {//assets檔案夾中的資源
InputStream inputStream = this.getContext().getAssets().open(uri.substring(7, uri
.length()));
fileLen = inputStream.available();
bis = new BufferedInputStream(inputStream);
result = Long.MAX_VALUE;
} else {//設定一個資源網址
final URL url = new URL(uri);
urlConnection = url.openConnection();
urlConnection.setConnectTimeout(this.getDefaultConnectTimeout());
urlConnection.setReadTimeout(this.getDefaultReadTimeout());
bis = new BufferedInputStream(urlConnection.getInputStream());
result = urlConnection.getExpiration();//響應過期時間戳
result = result < System.currentTimeMillis() ? System.currentTimeMillis() + this
.getDefaultExpiry() : result;
fileLen = urlConnection.getContentLength();
}
if (task.isCancelled() || task.getTargetContainer() == null) return -1;
byte[] buffer = new byte[4096];
int len = 0;
BufferedOutputStream out = new BufferedOutputStream(outputStream);
while ((len = bis.read(buffer)) != -1) {
out.write(buffer, 0, len);//向提供的輸出流寫出,這個輸出流一般是dirtyFile
currCount += len;
if (task.isCancelled() || task.getTargetContainer() == null) return -1;
task.updateProgress(fileLen, currCount);
}
out.flush();
} catch (Throwable e) {
result = -1;
LogUtils.e(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(bis);
}
return result;
}
2.5.2.4 下載下傳檔案出錯editor.abort();
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public void abort() throws IOException {
completeEdit(this, false);
}
/**
* 1.檢查初次生成entry的index時候有值
* 2.周遊所有緩存檔案,如果狀态為success,将存在的dirty檔案清洗,緩存大小資訊
* 3.如果狀态為失敗,删除所有dirty檔案
* 4.更新日志檔案
*/
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}
// If this edit is creating the entry for the first time, every index must have a value.
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i++) {
if (!editor.written[i]) {
editor.abort();
throw new IllegalStateException("Newly created entry didn't create value for " +
"index " + i);
}
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
return;
}
}
}
for (int i = 0; i < valueCount; i++) {
File dirty = entry.getDirtyFile(i);
if (success) {//如果成功,清洗舊檔案
if (dirty.exists()) {
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);//重命名檔案
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;//更新檔案長度
size = size - oldLength + newLength;
}
} else {
deleteIfExists(dirty);//失敗就删除dirtyFile
}
}
//更新日志檔案
redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
entry.readable = true;
journalWriter.write(CLEAN + " " + entry.diskKey + " " + EXPIRY_PREFIX + entry
.expiryTimestamp + entry.getLengths() + '\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.diskKey);
journalWriter.write(DELETE + " " + entry.diskKey + '\n');
}
journalWriter.flush();
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
2.5.2.5下載下傳成功送出editor.commit();
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
removeByDiskKey(entry.diskKey); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
/**
* Drops the entry for {@code diskKey} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
* 删除clean檔案
*/
private synchronized boolean removeByDiskKey(String diskKey) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(diskKey);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {//删除clean檔案
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(DELETE + " " + diskKey + '\n');
lruEntries.remove(diskKey);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
2.5.2.6再次擷取snapshot = mDiskLruCache.get(uri);見2.5.1.1,此次snapshot對象已經存在
2.5.2.7根據下載下傳的資源構造bitmap = decodeBitmapMeta(bitmapMeta, config);
/**
* 通過BitmapMeta對象構造Bitmap
* 1.如果BitmapMeta對象輸入流不為空,以輸入流擷取Bitmap
* 2.如果BitmapMeta對象byte資料不為空,以此資料擷取Bitmap
*/
private Bitmap decodeBitmapMeta(BitmapMeta bitmapMeta, BitmapDisplayConfig config) throws
IOException {
if (bitmapMeta == null) return null;
Bitmap bitmap = null;
if (bitmapMeta.inputStream != null) {
if (config == null || config.isShowOriginal()) {
bitmap = BitmapDecoder.decodeFileDescriptor(bitmapMeta.inputStream.getFD());
} else {
bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(bitmapMeta.inputStream
.getFD(), config.getBitmapMaxSize(), config.getBitmapConfig());
}
} else if (bitmapMeta.data != null) {
if (config == null || config.isShowOriginal()) {
bitmap = BitmapDecoder.decodeByteArray(bitmapMeta.data);
} else {
bitmap = BitmapDecoder.decodeSampledBitmapFromByteArray(bitmapMeta.data, config
.getBitmapMaxSize(), config.getBitmapConfig());
}
}
return bitmap;
}
2.5.2.8 如果下載下傳到磁盤緩存為null再去下載下傳到記憶體緩存中bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri,outputStream, task);見2.5.2.3
2.5.2.9 處理下載下傳的資源構造Bitmap,bitmap = decodeBitmapMeta(bitmapMeta, config);見2.5.2.7
2.5.2.10 如果下載下傳成功,先去處理圖檔旋轉問題;見2.5.1.4。同時将Bitmap添加到記憶體緩存中;見2.5.1.5
2.5.3 背景任務執行結束,處理回調onPostExecute
//Bitmap加載成功回調
@Override
protected void onPostExecute(Bitmap bitmap) {
final T container = this.getTargetContainer();
if (container != null) {
if (bitmap != null) {
callBack.onLoadCompleted(container, this.uri, bitmap, displayConfig, from);
} else {
callBack.onLoadFailed(container, this.uri, displayConfig
.getLoadFailedDrawable());
}
}
}
至此BitmapLoadTask完成。
二 緻敬原作者,謝謝作者辛苦付出,代碼受益匪淺。
傳送門
三 其他講解:
架構講解:傳送門
四 源碼注釋
源碼詳細注釋:傳送門