ImageLoader深入原始碼學習探究

飛天大豬發表於2016-08-23

由於我當前的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。


到此整個載入和快取的過程就講完了,裡面有很多講得不好的地方 歡迎大家一起討論。

相關文章