ImageLoader深入原始碼學習探究
由於我當前的ImageLoader版本與讀者們的版本可能不同,所以下面講解的地方可能存在一些出入,但大體上的實現基本一致,請讀者自己參照自己的imageloader原始碼來分析
//顯示圖片的配置
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.default)
.showImageOnFail(R.drawable.error)
.cacheInMemory(true)
.cacheOnDisk(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
然後會呼叫displayImage方法來載入圖片
ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);
我們看下一下displayImage的實現
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options, ImageLoadingListener listener) {
this.displayImage(uri, (ImageAware)(new ImageViewAware(imageView)), options, listener);
}
將imageView轉化成ImageViewAware,ImageViewAware實現了ImageAware介面,我們來看一下ImageViewAware 中的方法首先是構造方法
public ImageViewAware(ImageView imageView) {
this(imageView, true);
}
public ImageViewAware(ImageView imageView, boolean checkActualViewSize) {
this.imageViewRef = new WeakReference(imageView);
this.checkActualViewSize = checkActualViewSize;
}
可以看到在第一個構造方法中呼叫了第二個構造方法,在第二個構造方法中使用了WeakReference,即將我們的imageView由強引用轉化為弱引用,這樣當記憶體不足的時候,可以更好的回收ImageView物件
接下來看一下displayImage的具體實現程式碼↓
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener) {
this.checkConfiguration();
if(imageAware == null) {
throw new IllegalArgumentException("Wrong arguments were passed to displayImage() method (ImageView reference must not be null)");
} else {
if(listener == null) {
listener = this.emptyListener;
}
if(options == null) {
options = this.configuration.defaultDisplayImageOptions;
}
if(TextUtils.isEmpty(uri)) {
this.engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if(options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(this.configuration.resources));
} else {
imageAware.setImageDrawable((Drawable)null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), (Bitmap)null);
} else {
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, this.configuration.getMaxImageSize());
String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);
this.engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
Bitmap bmp = (Bitmap)this.configuration.memoryCache.get(memoryCacheKey);
ImageLoadingInfo imageLoadingInfo;
if(bmp != null && !bmp.isRecycled()) {
if(this.configuration.writeLogs) {
L.d("Load image from memory cache [%s]", new Object[]{memoryCacheKey});
}
if(options.shouldPostProcess()) {
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask1 = new ProcessAndDisplayImageTask(this.engine, bmp, imageLoadingInfo, options.getHandler());
if(options.isSyncLoading()) {
displayTask1.run();
} else {
this.engine.submit(displayTask1);
}
} else {
bmp = options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
if(options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(this.configuration.resources));
} else if(options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable((Drawable)null);
}
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, options.getHandler());
if(options.isSyncLoading()) {
displayTask.run();
} else {
this.engine.submit(displayTask);
}
}
}
}
}
在displayImage方法具體的實現中,第一步呼叫了checkConfiguration()方法
private void checkConfiguration() {
if(this.configuration == null) {
throw new IllegalStateException("ImageLoader must be init with configuration before using");
}
}
當我們的配置是空時,則會丟擲異常ImageLoader must be init with configuration before using,這個異常在新手使用時比較容易遇到,這是因為沒有init我們的imageloader,下面是初始化的程式碼(下面這段初始化的程式碼只提供參考,可根據實際情況自己配置自己需要的引數)
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context).threadPriority(Thread.NORM_PRIORITY - 2).denyCacheImageMultipleSizesInMemory().
discCacheFileNameGenerator(new Md5FileNameGenerator()).tasksProcessingOrder(QueueProcessingType.LIFO).build();
ImageLoader.getInstance().init(config);
我們再來分析一下displayImage中的這段程式碼
if(TextUtils.isEmpty(uri)) {
this.engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if(options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(this.configuration.resources));
} else {
imageAware.setImageDrawable((Drawable)null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), (Bitmap)null);
} else {
....
}
在if語句中,處理的就是當我們傳遞進去的url為空的情況,我們看到this.engine.cancelDisplayTaskFor(imageAware);有這麼一句,那麼這一句是什麼意思呢?
engine是一個ImageLoaderEngine物件,ImageLoaderEngine中存在一個HashMap,用來記錄正在載入的任務,載入圖片的時候會將ImageView的id和圖片的url加上尺寸加入到HashMap中,載入完成之後會將其移除,我們可以看cancelDisplayTaskFor的具體試下,他將正在載入中的任務的當前iamgeAware給remove掉了
void cancelDisplayTaskFor(ImageAware imageAware) {
this.cacheKeysForImageAwares.remove(Integer.valueOf(imageAware.getId()));
}
然後將DisplayImageOptions的imageResForEmptyUri的圖片設定給ImageView,最後回撥給ImageLoadingListener介面告訴它這次任務完成了。
接下來我們就來分析一下在url不為空的情況下,這才是我們應該著重關注的部分
if(TextUtils.isEmpty(uri)) {
...
} else {
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, this.configuration.getMaxImageSize());
String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);
this.engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
Bitmap bmp = (Bitmap)this.configuration.memoryCache.get(memoryCacheKey);
ImageLoadingInfo imageLoadingInfo;
if(bmp != null && !bmp.isRecycled()) {
if(this.configuration.writeLogs) {
L.d("Load image from memory cache [%s]", new Object[]{memoryCacheKey});
}
if(options.shouldPostProcess()) {
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask1 = new ProcessAndDisplayImageTask(this.engine, bmp, imageLoadingInfo, options.getHandler());
if(options.isSyncLoading()) {
displayTask1.run();
} else {
this.engine.submit(displayTask1);
}
} else {
bmp = options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
if(options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(this.configuration.resources));
} else if(options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable((Drawable)null);
}
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, options.getHandler());
if(options.isSyncLoading()) {
displayTask.run();
} else {
this.engine.submit(displayTask);
}
}
}
首先它會呼叫ImageSizeUtils類的defineTargetSizeForView方法 將我們的imageAware封裝為一個ImageSize物件 ,defineTargetSizeForView方法實現如下
public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) {
int width = imageAware.getWidth();
if(width <= 0) {
width = maxImageSize.getWidth();
}
int height = imageAware.getHeight();
if(height <= 0) {
height = maxImageSize.getHeight();
}
return new ImageSize(width, height);
}
如果獲取ImageView的寬高小於等於0,就會使用手機螢幕的寬高作為ImageView的寬高。
String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);
這一句的作用是生成一個快取時使用的key,再從快取中取資料的時候通過該key值來獲取generateKey方法如下,非常簡單,大家看看就好哈,這裡就不說了↓
public static String generateKey(String imageUri, ImageSize targetSize) {
return imageUri + "_" + targetSize.getWidth() + "x" + targetSize.getHeight();
}
this.engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
這一句就是將當前任務加入到haspmap中記錄起來,cacheKeysForImageAwares就是一個haspMap 如下↓
void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
this.cacheKeysForImageAwares.put(Integer.valueOf(imageAware.getId()), memoryCacheKey);
}
Bitmap bmp = (Bitmap)this.configuration.memoryCache.get(memoryCacheKey);
這一句程式碼從記憶體快取中獲取Bitmap物件,我們可以再ImageLoaderConfiguration中配置記憶體快取邏輯,預設使用的是LruMemoryCache。
我們再來看接下來的這一段程式碼
if(options.shouldPostProcess()) {
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask1 = new ProcessAndDisplayImageTask(this.engine, bmp, imageLoadingInfo, options.getHandler());
if(options.isSyncLoading()) {
displayTask1.run();
} else {
this.engine.submit(displayTask1);
}
} else {
bmp = options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
這一段程式碼是在if(bmp != null && !bmp.isRecycled())為true的情況下執行的,就是說是在快取不為空且沒有被回收的條件下執行的。我們如果在DisplayImageOptions中設定了postProcessor就進入true邏輯,不過預設postProcessor是為null的,BitmapProcessor介面主要是對Bitmap進行處理,這個框架並沒有給出相對應的實現,如果我們有自己的需求的時候可以自己實現BitmapProcessor介面。
bmp = options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
這兩行主要是將Bitmap設定到ImageView上面,這裡我們可以在DisplayImageOptions中配置顯示需求displayer,預設使用的是SimpleBitmapDisplayer,直接將Bitmap設定到ImageView上面,我們可以配置其他的顯示邏輯, 他這裡提供了FadeInBitmapDisplayer(透明度從0-1)RoundedBitmapDisplayer(4個角是圓弧)等, 然後回撥ImageLoadingListener介面onLoadingComplete 載入完成。
if(options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(this.configuration.resources));
} else if(options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable((Drawable)null);
}
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, options.getHandler());
if(options.isSyncLoading()) {
displayTask.run();
} else {
this.engine.submit(displayTask);
}
這段程式碼主要是Bitmap不在記憶體快取,從檔案中或者網路裡面獲取bitmap物件,例項化一個LoadAndDisplayImageTask物件,LoadAndDisplayImageTask實現了Runnable,如果配置了isSyncLoading為true, 直接執行LoadAndDisplayImageTask的run方法,表示同步,預設是false,將LoadAndDisplayImageTask提交給執行緒池物件
我們來看一下LoadAndDisplayImageTask中的run方法如何實現的
public void run() {
if(!this.waitIfPaused()) {
if(!this.delayIfNeed()) {
...
}
}
}
當waitIfPaused()和delayIfNeed()方法返回true時,會直接結束run方法,我們先來看看這兩個方法的實現
waitIfPaused()方法
private boolean waitIfPaused() {
AtomicBoolean pause = this.engine.getPause();
synchronized(pause) {
if(pause.get()) {
this.log("ImageLoader is paused. Waiting... [%s]");
try {
pause.wait();
} catch (InterruptedException var5) {
L.e("Task was interrupted [%s]", new Object[]{this.memoryCacheKey});
return true;
}
this.log(".. Resume loading [%s]");
}
}
return this.checkTaskIsNotActual();
}
這個方法是幹嘛用呢,主要是我們在使用ListView,GridView去載入圖片的時候,有時候為了滑動更加的流暢,我們會選擇手指在滑動或者猛地一滑動的時候不去載入圖片,所以才提出了這麼一個方法,那麼要怎麼用呢? 這裡用到了PauseOnScrollListener這個類,使用很簡單ListView.setOnScrollListener(new PauseOnScrollListener(pauseOnScroll, pauseOnFling )), pauseOnScroll控制我們緩慢滑動ListView,GridView是否停止載入圖片,pauseOnFling 控制猛的滑動ListView,GridView是否停止載入圖片
除此之外,這個方法的返回值由isTaskNotActual()決定,我們接著看看checkTaskIsNotActual()的原始碼
private boolean checkTaskIsNotActual() {
return this.checkViewCollected() || this.checkViewReused();
}
checkViewCollected()是判斷我們ImageView是否被垃圾回收器回收了,如果回收了,LoadAndDisplayImageTask方法的run()就直接返回了,checkViewReused()判斷該ImageView是否被重用,被重用run()方法也直接返回,為什麼要用checkViewReused()方法呢?主要是ListView,GridView我們會複用item物件,假如我們先去載入ListView,GridView第一頁的圖片的時候,第一頁圖片還沒有全部載入完我們就快速的滾動,checkViewReused()方法就會避免這些不可見的item去載入圖片,而直接載入當前介面的圖片
delayIfNeed()方法與waitIfPaused() 一樣,都是由checkTaskIsNotActual()來控制返回值,就不多說這個方法了。
然後我們來看看當這兩個都返回false時,執行的程式碼
ReentrantLock loadFromUriLock = this.imageLoadingInfo.loadFromUriLock;
this.log("Start display image task [%s]");
if(loadFromUriLock.isLocked()) {
this.log("Image already is loading. Waiting... [%s]");
}
loadFromUriLock.lock();
Bitmap bmp;
try {
if(this.checkTaskIsNotActual()) {
return;
}
bmp = (Bitmap)this.configuration.memoryCache.get(this.memoryCacheKey);
if(bmp == null) {
bmp = this.tryLoadBitmap();
if(this.imageAwareCollected) {
return;
}
if(bmp == null) {
return;
}
if(this.checkTaskIsNotActual() || this.checkTaskIsInterrupted()) {
return;
}
if(this.options.shouldPreProcess()) {
this.log("PreProcess image before caching in memory [%s]");
bmp = this.options.getPreProcessor().process(bmp);
if(bmp == null) {
L.e("Pre-processor returned null [%s]", new Object[0]);
}
}
if(bmp != null && this.options.isCacheInMemory()) {
this.log("Cache image in memory [%s]");
this.configuration.memoryCache.put(this.memoryCacheKey, bmp);
}
} else {
this.loadedFrom = LoadedFrom.MEMORY_CACHE;
this.log("...Get cached bitmap from memory after waiting. [%s]");
}
if(bmp != null && this.options.shouldPostProcess()) {
this.log("PostProcess image before displaying [%s]");
bmp = this.options.getPostProcessor().process(bmp);
if(bmp == null) {
L.e("Pre-processor returned null [%s]", new Object[]{this.memoryCacheKey});
}
}
} finally {
loadFromUriLock.unlock();
}
if(!this.checkTaskIsNotActual() && !this.checkTaskIsInterrupted()) {
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, this.imageLoadingInfo, this.engine, this.loadedFrom);
displayBitmapTask.setLoggingEnabled(this.writeLogs);
if(this.options.isSyncLoading()) {
displayBitmapTask.run();
} else {
this.handler.post(displayBitmapTask);
}
}
我們可以看到在第一行中有一個loadFromUriLock,這個其實就是一個鎖,他可以通過ImageLoaderEngine類的getLockForUri()方法來獲取
ReentrantLock getLockForUri(String uri) {
ReentrantLock lock = uriLocks.get(uri);
if (lock == null) {
lock = new ReentrantLock();
uriLocks.put(uri, lock);
}
return lock;
}
這個鎖物件與圖片的url是相互對應的,為什麼要這麼做?不知道大家有沒有考慮過一個場景,假如在一個ListView中,某個item正在獲取圖片的過程中,而此時我們將這個item滾出介面之後又將其滾進來,滾進來之後如果沒有加鎖,該item又會去載入一次圖片,假設在很短的時間內滾動很頻繁,那麼就會出現多次去網路上面請求圖片,所以這裡根據圖片的Url去對應一個ReentrantLock物件,讓具有相同Url的請求就會在等待,等到這次圖片載入完成之後,ReentrantLock就被釋放,剛剛那些相同Url的請求才會繼續執行下面的程式碼
接下來又會執行bmp = (Bitmap)this.configuration.memoryCache.get(this.memoryCacheKey);這一句程式碼,先從記憶體快取中獲取一遍,如果記憶體快取中沒有在去執行下面的邏輯,所以ReentrantLock的作用就是避免這種情況下重複的去從網路上面請求圖片。
當記憶體中沒有快取該圖片時 會執行一個tryLoadBitmap()方法,
private Bitmap tryLoadBitmap() {
File imageFile = this.getImageFileInDiscCache();
Bitmap bitmap = null;
try {
if(imageFile.exists()) {
this.log("Load image from disc cache [%s]");
this.loadedFrom = LoadedFrom.DISC_CACHE;
bitmap = this.decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
if(this.imageAwareCollected) {
return null;
}
}
if(bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
this.log("Load image from network [%s]");
this.loadedFrom = LoadedFrom.NETWORK;
String e = this.options.isCacheOnDisc()?this.tryCacheImageOnDisc(imageFile):this.uri;
if(!this.checkTaskIsNotActual()) {
bitmap = this.decodeImage(e);
if(this.imageAwareCollected) {
return null;
}
if(bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
this.fireFailEvent(FailType.DECODING_ERROR, (Throwable)null);
}
}
}
} catch (IllegalStateException var4) {
this.fireFailEvent(FailType.NETWORK_DENIED, (Throwable)null);
} catch (IOException var5) {
L.e(var5);
this.fireFailEvent(FailType.IO_ERROR, var5);
if(imageFile.exists()) {
imageFile.delete();
}
} catch (OutOfMemoryError var6) {
L.e(var6);
this.fireFailEvent(FailType.OUT_OF_MEMORY, var6);
} catch (Throwable var7) {
L.e(var7);
this.fireFailEvent(FailType.UNKNOWN, var7);
}
return bitmap;
}
這裡面的邏輯是先從檔案快取中獲取有沒有Bitmap物件,如果沒有在去從網路中獲取,然後將bitmap儲存在檔案系統中,我們來看一下它從網路獲取圖片後是如何進行快取的
String e = this.options.isCacheOnDisc()?this.tryCacheImageOnDisc(imageFile):this.uri;
先檢查是否配置了DisplayImageOptions的isCacheOnDisk,表示是否需要將Bitmap物件儲存在檔案系統中,一般我們需要配置為true,當為true時就會呼叫tryCacheImageOnDisc()這個方法了
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
boolean loaded;
try {
loaded = downloadImage();
if (loaded) {
int width = configuration.maxImageWidthForDiskCache;
int height = configuration.maxImageHeightForDiskCache;
if (width > 0 || height > 0) {
L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
resizeAndSaveImage(width, height); // TODO : process boolean result
}
}
} catch (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}
private boolean downloadImage() throws IOException {
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
return configuration.diskCache.save(uri, is, this);
}
downloadImage()方法是負責下載圖片,並將其保持到檔案快取中,將下載儲存Bitmap的進度回撥到IoUtils.CopyListener介面的onBytesCopied(int current, int total)方法中,所以我們可以設定ImageLoadingProgressListener介面來獲取圖片下載儲存的進度,這裡儲存在檔案系統中的圖片是原圖
int width = configuration.maxImageWidthForDiskCache;
int height = configuration.maxImageHeightForDiskCache;
獲取ImageLoaderConfiguration是否設定儲存在檔案系統中的圖片大小,如果設定了maxImageWidthForDiskCache和maxImageHeightForDiskCache,會呼叫resizeAndSaveImage()方法對圖片進行裁剪然後在替換之前的原圖,儲存裁剪後的圖片到檔案系統的,所以我們只要在Application中例項化ImageLoaderConfiguration的時候設定maxImageWidthForDiskCache和maxImageHeightForDiskCache就可以儲存縮圖了
然後我們再回到run方法中,執行完tryLoadBitmap()後會執行下面這段程式碼,將圖片儲存到記憶體快取中去
if(bmp != null && this.options.isCacheInMemory()) {
this.log("Cache image in memory [%s]");
this.configuration.memoryCache.put(this.memoryCacheKey, bmp);
}
最後這一段程式碼就是一個顯示的過程
if(!this.checkTaskIsNotActual() && !this.checkTaskIsInterrupted()) {
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, this.imageLoadingInfo, this.engine, this.loadedFrom);
displayBitmapTask.setLoggingEnabled(this.writeLogs);
if(this.options.isSyncLoading()) {
displayBitmapTask.run();
} else {
this.handler.post(displayBitmapTask);
}
}
我們直接看一下displayBitmapTask.run();方法
public void run() {
if(this.imageAware.isCollected()) {
if(this.loggingEnabled) {
L.d("ImageAware was collected by GC. Task is cancelled. [%s]", new Object[]{this.memoryCacheKey});
}
this.listener.onLoadingCancelled(this.imageUri, this.imageAware.getWrappedView());
} else if(this.isViewWasReused()) {
if(this.loggingEnabled) {
L.d("ImageAware is reused for another image. Task is cancelled. [%s]", new Object[]{this.memoryCacheKey});
}
this.listener.onLoadingCancelled(this.imageUri, this.imageAware.getWrappedView());
} else {
if(this.loggingEnabled) {
L.d("Display image in ImageAware (loaded from %1$s) [%2$s]", new Object[]{this.loadedFrom, this.memoryCacheKey});
}
Bitmap displayedBitmap = this.displayer.display(this.bitmap, this.imageAware, this.loadedFrom);
this.listener.onLoadingComplete(this.imageUri, this.imageAware.getWrappedView(), displayedBitmap);
this.engine.cancelDisplayTaskFor(this.imageAware);
}
}
假如ImageView被回收了或者被重用了,就回撥ImageLoadingListener介面的onLoadingCancelled方法,否則就呼叫BitmapDisplayer去顯示Bitmap。
到此整個載入和快取的過程就講完了,裡面有很多講得不好的地方 歡迎大家一起討論。
相關文章
- 《Android原始碼設計模式》學習筆記之ImageLoaderAndroid原始碼設計模式筆記
- iOS 從原始碼深入探究weak的實現 | 掘金技術徵文iOS原始碼
- 值得深入學習的控制元件-RecyclerView(原始碼解析篇)控制元件View原始碼
- 深入原始碼學習 android data binding 之:ViewDataBinding原始碼AndroidView
- Golang原始碼學習:使用gdb除錯探究Golang函式呼叫棧結構Golang原始碼除錯函式
- Vue原始碼探究-原始碼檔案組織Vue原始碼
- .NET Core Session原始碼探究Session原始碼
- .NET Core HttpClient原始碼探究HTTPclient原始碼
- Mybatis日誌原始碼探究MyBatis原始碼
- Java集合原始碼探究~ListJava原始碼
- UITableView+FDTemplateLayoutCell 原始碼探究UIView原始碼
- 深入原始碼學習 android data binding 之:原始碼的正確開啟姿勢原始碼Android
- vue原始碼學習Vue原始碼
- MMKV原始碼學習原始碼
- EventBus原始碼學習原始碼
- fishhook原始碼學習Hook原始碼
- 學習HashMap原始碼HashMap原始碼
- koa原始碼學習原始碼
- express原始碼學習Express原始碼
- redis原始碼學習Redis原始碼
- Ember原始碼學習原始碼
- 執行緒池原始碼探究執行緒原始碼
- Vue原始碼探究-事件系統Vue原始碼事件
- Vue原始碼探究-生命週期Vue原始碼
- vue原始碼探究(第四彈)Vue原始碼
- Proxy.newProxyInstance原始碼探究原始碼
- .Netcore HttpClient原始碼探究NetCoreHTTPclient原始碼
- Java容器原始碼學習--ArrayList原始碼分析Java原始碼
- 深入 Nodejs 原始碼探究 CPU 資訊的獲取與利用率計算NodeJS原始碼
- PHP 原始碼加密學習PHP原始碼加密
- Okio 框架原始碼學習框架原始碼
- Vue 原始碼學習(一)Vue原始碼
- Masonry 原始碼學習整理原始碼
- 精讀《原始碼學習》原始碼
- java原始碼學習-SpliteratorJava原始碼
- jQuery原始碼學習之$()jQuery原始碼
- Mybatis 原始碼學習(二)MyBatis原始碼
- java原始碼學習-AbstractSequentialListJava原始碼