ImageLoader源碼分析

image

介紹

ImageLoader是android使用中出現(xiàn)比較早,使用最多的一個開源圖片加載庫了柏蘑,隨著glide,picasso等圖片加載的庫出現(xiàn)届搁,ImageLoader使用變得越來越少。

特點(diǎn)

  • 支持網(wǎng)絡(luò),本地加載圖片
  • 多線程下載圖片
  • 支持圖片下載進(jìn)度的監(jiān)聽
  • 支持Listview滾動時暫停下載
  • 支持內(nèi)存和磁盤的圖片緩存
  • 支持自定義配置選項(xiàng)(線程執(zhí)行器愕鼓,下載器,編碼器,緩存望门,顯示圖片等)

本文也是采用調(diào)用流程為線的方式來分析源碼,下面我們先來看下ImageLoader整體流程圖和核心類的名稱和作用锰霜。

預(yù)習(xí)

流程圖:


img_01.png

重要的類:

  • ImageLoaderConfiguration

      配置信息類筹误,使用builder設(shè)計(jì)模式
    
  • ImageLoaderEngine

    圖片加載引擎,主要負(fù)責(zé)將LoadAndDisplayImageTaskProcessAndDisplayImageTask任務(wù)分發(fā)給線程池去執(zhí)行
    
    Executor taskExecutor; // 用于從源獲取圖片的線程池
    Executor taskExecutorForCachedImages; // 從緩存池中獲取圖片的線程池
    Executor taskDistributor; // 分發(fā)任務(wù)的線程池癣缅,把任務(wù)分發(fā)到上面兩個線程池中
    
  • ImageAware

    圖片顯示的對象厨剪,一般為ImageView的封裝
    
  • ImageDownloader

    圖片下載器哄酝,主要是從本地,網(wǎng)絡(luò)等獲取圖片流
    
  • MemoryCache

    內(nèi)存緩存祷膳,使用Lru算法實(shí)現(xiàn)對內(nèi)存緩存的管理陶衅,當(dāng)圖片占用內(nèi)存或者圖片數(shù)量大于設(shè)置的閾值,回收最老的直晨,最少使用的万哪。
    
  • DiskCache

    本地緩存,可以自定義配置抡秆,實(shí)現(xiàn)對本地緩存的控制
    
  • ImageDecoder

    圖片解碼器奕巍,將輸入流轉(zhuǎn)化成Bitmap
    
  • BitmapProcessor

    圖片處理器,圖片的預(yù)處理和后期處理都使用這個類儒士,圖片的處理包括圖片寬高等等
    
  • BitmapDisplayer

    bitmap顯示器的止,負(fù)責(zé)將處理過后的bitmap顯示
    
  • LoadAndDisplayImageTask

    加載并顯示圖片任務(wù)
    
  • ProcessAndDisplayImageTask

    處理并顯示圖片任務(wù)
    
  • DisplayBitmapTask

    顯示圖片任務(wù)
    


源碼解析

本文不打算對ImageLoaderConfiguration配置類進(jìn)行講解,其實(shí)就是進(jìn)行配置着撩,還有使用了Builder設(shè)置模式诅福,有興趣的可以去看源碼。

1.ImageLoader.displayImage
    public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
        ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        //檢查配置
        checkConfiguration();
        //如果沒有顯示對象就拋出異常
        if (imageAware == null) {
            throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
        }
        //如果沒有加載監(jiān)聽拖叙,設(shè)置默認(rèn)的
        if (listener == null) {
            listener = defaultListener;
        }
        //設(shè)置顯示圖片的選項(xiàng)(緩存氓润,加載中顯示,圖片處理薯鳍,是否同步咖气,編碼)
        if (options == null) {
            options = configuration.defaultDisplayImageOptions;
        }
        //請求地址為空
        if (TextUtils.isEmpty(uri)) {
            //取消顯示任務(wù)
            engine.cancelDisplayTaskFor(imageAware);
            //加載開始
            listener.onLoadingStarted(uri, imageAware.getWrappedView());
            //空地址情況下,是否有顯示的圖片
            if (options.shouldShowImageForEmptyUri()) {
                imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
            } else {
                imageAware.setImageDrawable(null);
            }
            //加載完成
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
            return;
        }
        //圖片寬高為空挖滤,設(shè)置為imageview寬高
        if (targetSize == null) {
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }
        //通過uri+targetsize生成緩存 key
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
        //將imageAware和緩存key關(guān)聯(lián)
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
        //開始加載
        listener.onLoadingStarted(uri, imageAware.getWrappedView());
        //獲取緩存
        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
        if (bmp != null && !bmp.isRecycled()) {
            L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

            //有緩存且可用
            //是否支持后期處理bitmap
            if (options.shouldPostProcess()) {
                //封裝ImageLoadingInfo崩溪,封裝處理顯示任務(wù)
                //defineHandler()如果同步,返回null
                ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                        options, listener, progressListener, engine.getLockForUri(uri));
                ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                        defineHandler(options));
                //是否同步調(diào)用
                if (options.isSyncLoading()) {
                    //直接執(zhí)行run(當(dāng)前線程)
                    displayTask.run();
                } else {
                    //將任務(wù)交給taskExecutorForCachedImages處理緩存的線程池來執(zhí)行
                    engine.submit(displayTask);
                }
            } else {
                //不支持后期處理bitmap
                //顯示bitmap,加載完畢
                options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
            }
        } else {
            //沒有緩存或者已經(jīng)被回收

            //是否有顯示加載中圖片
            if (options.shouldShowImageOnLoading()) {
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {
                //加載圖片前重置
                imageAware.setImageDrawable(null);
            }
            //封裝ImageLoadingInfo斩松,封裝加載顯示任務(wù)
            //defineHandler()如果同步伶唯,返回null
            ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                    options, listener, progressListener, engine.getLockForUri(uri));
            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                    defineHandler(options));
            //是否同步
            if (options.isSyncLoading()) {
                //同步執(zhí)行run
                displayTask.run();
            } else {
                //任務(wù)交給taskDistributor進(jìn)行派發(fā)(根據(jù)是否有本地緩存)
                engine.submit(displayTask);
            }
        }
    }

displayImage方法中主要就是:

  • 生成 cacheKey,并和當(dāng)前ImageAware保存到map中

    作用是惧盹,在線程中判斷當(dāng)前ImageAware是否被重用了
    
  • 有緩存乳幸,異步,生成ProcessAndDisplayImageTask钧椰,且交給緩存線程池taskExecutorForCachedImages

  • 沒有緩存粹断,異步,生成LoadAndDisplayImageTask演侯,交給分發(fā)線程池taskDistributor來根據(jù)是否有本地緩存來分發(fā)

關(guān)于同步我們就不講解了姿染,我們下面從兩個方面講解:有內(nèi)存緩存異步,沒有內(nèi)存緩存異步。

2.有內(nèi)存緩存異步

2.1 ProcessAndDisplayImageTask.run()

public void run() {     
    //后期處理圖片
    BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
    Bitmap processedBitmap = processor.process(bitmap);
    //創(chuàng)建DisplayBitmapTask(顯示任務(wù))
    DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
            LoadedFrom.MEMORY_CACHE);
    //執(zhí)行顯示任務(wù)
    LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}

對來自內(nèi)存緩存中的圖片進(jìn)行后期處理悬赏,創(chuàng)建DisplayBitmapTask狡汉,交給LoadAndDisplayImageTask的runTask方法去執(zhí)行。

2.2 LoadAndDisplayImageTask.runTask

    static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
        if (sync) {
            //同步執(zhí)行闽颇,當(dāng)前線程顯示
            r.run();
        } else if (handler == null) {
            //異步執(zhí)行盾戴,且在執(zhí)行線程中顯示
            engine.fireCallback(r);
        } else {
            //handler執(zhí)行(主線程顯示)
            handler.post(r);
        }
    }

runTask方法就是判斷顯示任務(wù)在那個線程中執(zhí)行。

2.3 DisplayBitmapTask.run

public void run() {
    //imageAware被回收
    if (imageAware.isCollected()) {
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
    } else if (isViewWasReused()) {
        //imageAware被重用
        listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
    } else {
        //顯示圖片
        displayer.display(bitmap, imageAware, loadedFrom);
        //移除cacheKeysForImageAwares中當(dāng)前iamgeAware元素
        engine.cancelDisplayTaskFor(imageAware);
        //調(diào)用加載完成監(jiān)聽
        listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
    }
}

如果ImageAware被回收或者被重用兵多,那么直接回調(diào)onLoadingCancelled尖啡,負(fù)責(zé)顯示圖片,移除cacheKeysForImageAwares中當(dāng)前ImageAware的數(shù)據(jù)剩膘,回調(diào)onLoadingComplete衅斩。

2.4 isViewWasReused()

    private boolean isViewWasReused() {
        //獲取ImageAware最新的cacheKey
        String currentCacheKey = engine.getLoadingUriForView(imageAware);
        //如果當(dāng)前任務(wù)的cacheKey和最新的cacheKey不一致,說明ImageAware被重用了怠褐。
        return !memoryCacheKey.equals(currentCacheKey);
    }

如果當(dāng)前任務(wù)的cacheKey和最新的cacheKey不一致畏梆,說明ImageAware被重用了。

有內(nèi)存緩存的情況已經(jīng)分析結(jié)束奈懒。


3.沒有內(nèi)存緩存異步

3.1 ImageLoaderEngine.submit()

    void submit(final LoadAndDisplayImageTask task) {
        taskDistributor.execute(new Runnable() {
            @Override
            public void run() {
                //獲取本地緩存
                File image = configuration.diskCache.get(task.getLoadingUri());
                boolean isImageCachedOnDisk = image != null && image.exists();
                initExecutorsIfNeed();
                //是否有本地緩存
                if (isImageCachedOnDisk) {
                    //如果有本地緩存奠涌,交給處理緩存線程池
                    taskExecutorForCachedImages.execute(task);
                } else {
                    //如果沒有本地緩存,交給taskExecutor
                    taskExecutor.execute(task);
                }
            }
        });
    }

分發(fā)線程池taskDistributor根據(jù)是否有本地緩存來進(jìn)行處理:

  • 有本地緩存磷杏,將任務(wù)交給緩存線程池
  • 沒有本地緩存溜畅,將任務(wù)交給taskExecutor

3.2 LoadAndDisplayImageTask.run()

    public void run() {
        //如果線程被打斷,進(jìn)入休眠
        //如果線程沒有被打斷或者被喚醒,且imageAware被GC回收极祸,或者imageAware被重用了慈格,那么返回true
        if (waitIfPaused()) return;
        //延時加載且imageAware被GC回收,或者imageAware被重用了贿肩,那么返回true
        if (delayIfNeed()) return;

        //根據(jù)當(dāng)前uri,獲取鎖
        ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
        
        //加鎖
        loadFromUriLock.lock();
        Bitmap bmp;
        try {
            //如果ImageAware被回收了或者被重用了峦椰,直接拋出任務(wù)取消異常TaskCancelledException
            checkTaskNotActual();
            //獲取內(nèi)存緩存(有可能圖片被其他線程加載過)
            bmp = configuration.memoryCache.get(memoryCacheKey);
            if (bmp == null || bmp.isRecycled()) {
                //沒有內(nèi)存緩存

                //嘗試從本地緩存或者本地或者網(wǎng)絡(luò)等等源加載圖片
                bmp = tryLoadBitmap();
                //失敗的onLoadingFailed監(jiān)聽已經(jīng)被回調(diào)龄寞,這邊直接返回
                if (bmp == null) return; // listener callback already was fired

                //處理圖片前汰规,檢查ImageAware被回收了,或者被重用了物邑,或者線程被打斷了
                checkTaskNotActual();
                checkTaskInterrupted();

                //內(nèi)存緩存前的預(yù)處理
                if (options.shouldPreProcess()) {
                    bmp = options.getPreProcessor().process(bmp);
                }

                //添加到內(nèi)存緩存中
                if (bmp != null && options.isCacheInMemory()) {
                    configuration.memoryCache.put(memoryCacheKey, bmp);
                }
            } else {
                //有內(nèi)存緩存
                loadedFrom = LoadedFrom.MEMORY_CACHE;
            }

            //內(nèi)存緩存后的處理
            if (bmp != null && options.shouldPostProcess()) {
                bmp = options.getPostProcessor().process(bmp);
            }
            ////處理圖片后溜哮,檢查ImageAware被回收了,或者被重用了色解,或者線程被打斷了
            checkTaskNotActual();
            checkTaskInterrupted();
        } catch (TaskCancelledException e) {
            //異步且線程不是被打斷的茂嗓,執(zhí)行onLoadingCancelled
            fireCancelEvent();
            return;
        } finally {
            //釋放鎖
            loadFromUriLock.unlock();
        }
        //創(chuàng)建DisplayBitmapTask任務(wù)
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        //執(zhí)行
        runTask(displayBitmapTask, syncLoading, handler, engine);
    }

代碼注釋很清晰的,來個簡單的流程總結(jié):

  • 1.imageAware被GC回收科阎,或者imageAware被重用了述吸,直接返回true
  • 2.加鎖
  • 3.獲取內(nèi)存緩存

有緩存:

  • 4.后期的處理
  • 5.釋放鎖
  • 6.創(chuàng)建DisplayBitmapTask任務(wù),并執(zhí)行

沒有緩存

  • 4.從本地緩存中獲取,如果沒有從網(wǎng)絡(luò)蝌矛,sd卡等中獲取圖片
  • 5.預(yù)處理
  • 6.添加到內(nèi)存緩存中
  • 7.后期的處理
  • 8.釋放鎖
  • 9.創(chuàng)建DisplayBitmapTask任務(wù)道批,并執(zhí)行

下面分析核心的方法:

3.2.1 waitIfPaused() && delayIfNeed()

3.2.1.1 waitIfPaused()

    /**
     * 如果線程被打斷(比如:滾動listview),線程進(jìn)入wait,并釋放鎖
     * 如果線程沒有被打斷或者被喚醒,你們返回isTaskNotActual()
     * isTaskNotActual --- >如果imageAware被GC回收入撒,或者imageAware被重用了隆豹,那么返回true
     *
     * @return
     */
    private boolean waitIfPaused() {
        //獲取是否暫停狀態(tài)
        AtomicBoolean pause = engine.getPause();
        if (pause.get()) {
            synchronized (engine.getPauseLock()) {
                if (pause.get()) {
                    L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
                    try {
                        //休眠,等待被喚醒
                        engine.getPauseLock().wait();
                    } catch (InterruptedException e) {
                        L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
                        return true;
                    }
                    L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
                }
            }
        }
        return isTaskNotActual();
    }
  • 如果當(dāng)前的狀態(tài)為暫停(pasue = true),那么進(jìn)入休眠茅逮,等待被喚醒
  • 如果沒有暫土模或者被喚醒,那么返回isTaskNotActual()結(jié)果

上面代碼有兩個點(diǎn)需要注意:1.何時暫停献雅,何時被喚醒碉考。 2.isTaskNotActual()執(zhí)行

1.何時暫停,何時被喚醒

我們都知道當(dāng)ListView滑動的時候挺身,ImageLoader會暫停圖片加載豆励,停止滑動時,繼續(xù)加載圖片瞒渠,這就是暫停和喚醒的時機(jī):

PauseOnScrollListener.onScrollStateChanged()

    public void onScrollStateChanged(AbsListView view, int scrollState) {
    switch (scrollState) {
        case OnScrollListener.SCROLL_STATE_IDLE:
            //恢復(fù)加載(喚醒)
            imageLoader.resume();
            break;
        case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
            if (pauseOnScroll) {
                //滑動良蒸,暫停加載(休眠)
                imageLoader.pause();
            }
            break;
        case OnScrollListener.SCROLL_STATE_FLING:
            if (pauseOnFling) {
                //滾動,暫停加載(休眠)
                imageLoader.pause();
            }
            break;
    }
    ...
}

ImageLoaderEngine.java

private final AtomicBoolean paused = new AtomicBoolean(false);
void pause() {
    paused.set(true);
}

void resume() {
    paused.set(false);
    synchronized (pauseLock) {
        //喚醒所有任務(wù)
        pauseLock.notifyAll();
    }
}

2.isTaskNotActual()

    /**
     * 如果imageAware被GC回收伍玖,或者imageAware被重用了嫩痰,那么返回true
     * @return
     */
    private boolean isTaskNotActual() {
        return isViewCollected() || isViewReused();
    }

     /**
     * imageAware是否被GC回收
     *
     * @return 回收 --true
     */
    private boolean isViewCollected() {
        if (imageAware.isCollected()) {
            return true;
        }
        return false;
    }
    
    /**
     * ImageAware是否被重用,來顯示其他圖片
     * 以開始執(zhí)行時窍箍,存放在cacheKeysForImageAwares中的cacheKey為最新的key
     * 如果當(dāng)前的cachekey不相同串纺,則停止舍棄當(dāng)前的加載任務(wù)
     *
     * @return true--重用
     */
    private boolean isViewReused() {
        //獲取最新的cachekey
        String currentCacheKey = engine.getLoadingUriForView(imageAware);
        //如果和當(dāng)前cachekey不一致,說明imageAware被重用了
        boolean imageAwareWasReused = !memoryCacheKey.equals(currentCacheKey);
        if (imageAwareWasReused) {
            return true;
        }
        return false;
    }

waitIfPaused()總結(jié):

  • 如果暫停加載椰棘,進(jìn)入休眠等待被喚醒
  • 如果沒有暫停纺棺,或者被喚醒,則ImageAware被重用或者imageAware被GC回收邪狞,返回true,直接結(jié)束

3.2.1.2 delayIfNeed()

private boolean delayIfNeed() {
    //延時加載
    if (options.shouldDelayBeforeLoading()) {
         try {
            //睡眠
            Thread.sleep(options.getDelayBeforeLoading());
        } catch (InterruptedException e) {
            return true;
        }
        return isTaskNotActual();
    }
    return false;
}

delayIfNeed總結(jié):

  • 如果沒有延時加載祷蝌,返回false
  • 如果延時加載,進(jìn)入睡眠
  • 線程被打斷結(jié)束睡眠 帆卓,返回true
  • 正常結(jié)束睡眠巨朦,則ImageAware被重用或者imageAware被GC回收,返回true,直接結(jié)束


3.2.2 checkTaskNotActual() && checkTaskInterrupted()

//如果ImageAware被GC回收剑令,拋出TaskCancelledException
private void checkViewCollected() throws TaskCancelledException {
    if (isViewCollected()) {
        throw new TaskCancelledException();
    }
}
//如果ImageAware被重用糊啡,拋出TaskCancelledException
private void checkViewReused() throws TaskCancelledException {
    if (isViewReused()) {
        throw new TaskCancelledException();
    }
}

checkTaskNotActual總結(jié):

  • 如果ImageAware被GC回收或者ImageAware被重用,拋出TaskCancelledException異常
  • 拋出異常的處理將在后面進(jìn)行講解


    //如果線程被打斷吁津,拋出TaskCancelledException
    private void checkTaskInterrupted() throws TaskCancelledException {
        if (isTaskInterrupted()) {
            throw new TaskCancelledException();
        }
   }
    //如果線程被打斷棚蓄,返回true
    private boolean isTaskInterrupted() {
        if (Thread.interrupted()) {
            return true;
        }
        return false;
    }

checkTaskInterrupted總結(jié):

  • 如果線程被打斷,拋出TaskCancelledException異常
  • 拋出異常的處理將在后面進(jìn)行講解


3.2.3 TaskCancelledException 異常處理

private void fireCancelEvent() {
    //同步或者線程被打斷,直接返回
    if (syncLoading || isTaskInterrupted()) return;
    Runnable r = new Runnable() {
        @Override
        public void run() {
            listener.onLoadingCancelled(uri, imageAware.getWrappedView());
        }
    };
    //執(zhí)行onLoadingCancelled
    runTask(r, false, handler, engine);
}

如果是同步或者線程被打斷梭依,直接返回挣柬,否則在指定線程中回調(diào)onLoadingCancelled


3.2.4 tryLoadBitmap()

獲取圖片的核心方法:

private Bitmap tryLoadBitmap() throws TaskCancelledException {
    Bitmap bitmap = null;
    try {
        //獲取本地緩存
        File imageFile = configuration.diskCache.get(uri);
        if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
            //有本地緩存
            loadedFrom = LoadedFrom.DISC_CACHE;
            //檢查
            checkTaskNotActual();
            //解碼(file-->inputstream--->bitmap)
            bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
        }
        //沒有本地緩存
        if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
            loadedFrom = LoadedFrom.NETWORK;

            String imageUriForDecoding = uri;
            //如果開啟本地緩存則調(diào)用tryCacheImageOnDisk從network,asset,content,file,drawable中獲取圖片,且緩存到本地緩存中
            if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
                //獲取到下載的文件
                imageFile = configuration.diskCache.get(uri);
                if (imageFile != null) {
                    imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                }
            }

            //檢查
            checkTaskNotActual();
            //如果上面已經(jīng)獲取了圖片,則這里的uri為本地uri
            //如果沒有睛挚,下面將從network,asset,content,file,drawable中獲取圖片
            //內(nèi)存保存的是根據(jù)ImageView大小邪蛔、scaletype、方向處理過得圖片
            bitmap = decodeImage(imageUriForDecoding);

            if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                //如果是異步且線程沒有被打斷扎狱,imageaware沒有被回收和重用
                // 那么獲取圖片失敗侧到,顯示失敗后的圖片,并且調(diào)用onLoadingFailed
                fireFailEvent(FailType.DECODING_ERROR, null);
            }
        }
    } catch (IllegalStateException e) {
        //如果是異步且線程沒有被打斷淤击,imageaware沒有被回收和重用
        // 那么獲取圖片失敗匠抗,顯示失敗后的圖片,并且調(diào)用onLoadingFailed
        fireFailEvent(FailType.NETWORK_DENIED, null);
    } catch (TaskCancelledException e) {
        throw e;
    } catch (IOException e) {
        L.e(e);
        fireFailEvent(FailType.IO_ERROR, e);
    } catch (OutOfMemoryError e) {
        L.e(e);
        fireFailEvent(FailType.OUT_OF_MEMORY, e);
    } catch (Throwable e) {
        L.e(e);
        fireFailEvent(FailType.UNKNOWN, e);
    }
    return bitmap;
}

tryLoadBitmap流程:

  • 1.獲取本地緩存
  • 2.如果有本地緩存污抬,直接解碼取出
  • 3.如果開啟了本地緩存汞贸,則下載圖片,并將圖片保存到本地緩存中
  • 4.解碼圖片印机,返回

分析幾個核心方法:

1.tryCacheImageOnDisk

    /**
     * 從network,asset,content,file,drawable中獲取圖片矢腻,且緩存到本地緩存中
     * @return  獲取圖片,保存本地成功返回true
     * @throws TaskCancelledException
     */
    private boolean tryCacheImageOnDisk() throws TaskCancelledException {
        boolean loaded;
        try {
            //獲取圖片射赛,并保存本地緩存
            loaded = downloadImage();
            if (loaded) {
                //將原圖轉(zhuǎn)化為設(shè)定的最大本地緩存圖片大卸喔獭(默認(rèn)為0,所以不要改邊)
                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) {
            loaded = false;
        }
        return loaded;
    }   
    
private boolean downloadImage() throws IOException {
    //下載圖片
    InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
    if (is == null) {
        return false;
    } else {
        try {
            //保存到磁盤中
            return configuration.diskCache.save(uri, is, this);
        } finally {
            IoUtils.closeSilently(is);
        }
    }
}

BaseImageDownloader.getStream()

    public InputStream getStream(String imageUri, Object extra) throws IOException {
        switch (Scheme.ofUri(imageUri)) {
            case HTTP:
            case HTTPS:
                //網(wǎng)絡(luò)圖片通過HttpURLConnection實(shí)現(xiàn)的
                return getStreamFromNetwork(imageUri, extra);
            case FILE:
                return getStreamFromFile(imageUri, extra);
            case CONTENT:
                return getStreamFromContent(imageUri, extra);
            case ASSETS:
                return getStreamFromAssets(imageUri, extra);
            case DRAWABLE:
                return getStreamFromDrawable(imageUri, extra);
            case UNKNOWN:
            default:
                return getStreamFromOtherSource(imageUri, extra);
        }
    }

getStream總結(jié):

  • 根據(jù)uri協(xié)議的不同楣责,從不同的源加載圖片

tryCacheImageOnDisk總結(jié):

  • 獲取圖片竣灌,保存到本地緩存



2.decodeImage

private Bitmap decodeImage(String imageUri) throws IOException {
    ViewScaleType viewScaleType = imageAware.getScaleType();
    //targetSize -- imageview大小或者內(nèi)存緩存的最大size
    ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
            getDownloader(), options);
    return decoder.decode(decodingInfo);
}

BaseImageDecoder.decode()

public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
    Bitmap decodedBitmap;
    ImageFileInfo imageInfo;
    //獲取圖片流
    InputStream imageStream = getImageStream(decodingInfo);
    if (imageStream == null) {
        return null;
    }
    try {
        //確定圖片尺寸 和 旋轉(zhuǎn)角度 ,生成ImageFileInfo
        imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
        //重置流游標(biāo)
        imageStream = resetStream(imageStream, decodingInfo);
        //BitmapFactory.options
        //準(zhǔn)備decode的opions
        //decodingOptions.inSampleSize = scale; 對bitmap進(jìn)行壓縮
        Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
        decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
    } finally {
        IoUtils.closeSilently(imageStream);
    }

    if (decodedBitmap == null) {
    } else {
        //對Bitmap進(jìn)行縮放秆麸,翻轉(zhuǎn)和旋轉(zhuǎn)等操作
        decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
                imageInfo.exif.flipHorizontal);
    }
    return decodedBitmap;
}

getImageStream():

    //調(diào)用BaseImageDownloader.getStream() 
    protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
        return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
    }

defineImageSizeAndRotation()

    //獲取圖片的size 和 旋轉(zhuǎn)角度(EXIF信息)
    protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo) {
        //BitmapFactory.options
        Options options = new Options();
        //不加載bitmap數(shù)據(jù),只返回bitmap信息
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(imageStream, null, options);

        //EXIF信息初嘹,是可交換圖像文件的縮寫,是專門為數(shù)碼相機(jī)的照片設(shè)定的沮趣,可以記錄數(shù)碼照片的屬性信息和拍攝數(shù)據(jù)屯烦。
        ExifInfo exif;
        String imageUri = decodingInfo.getImageUri();
        //考慮 exif  且 文件能夠定義 exif(JPEG文件)
        //附加:EXIF可以附加于JPEG、TIFF兔毒、RIFF等文件之中
        if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {
            //根據(jù)文件exif信息漫贞,獲取旋轉(zhuǎn)角度,保存到ExifInfo中
            exif = defineExifOrientation(imageUri);
        } else {
            //創(chuàng)建對象(角度為0育叁,即不旋轉(zhuǎn))
            exif = new ExifInfo();
        }
        //將   圖片的寬高,旋轉(zhuǎn)角度 芍殖,exif  構(gòu)建ImageFileInfo對象
        return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);
    }

resetStream()

    //重置游標(biāo)
    protected InputStream resetStream(InputStream imageStream, ImageDecodingInfo decodingInfo) throws IOException {
        if (imageStream.markSupported()) {
            try {
                //重置游標(biāo)
                imageStream.reset();
                return imageStream;
            } catch (IOException ignored) {
            }
        }
        IoUtils.closeSilently(imageStream);
        return getImageStream(decodingInfo);
    }

prepareDecodingOptions():

    準(zhǔn)備decode的opions,根據(jù)ImageScaleType,imagesize計(jì)算decodingOptions.inSampleSize 的值豪嗽。

considerExactScaleAndOrientatiton()

    //對Bitmap進(jìn)行縮放,翻轉(zhuǎn)和旋轉(zhuǎn)等操作
    protected Bitmap considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo,
        int rotation, boolean flipHorizontal) {
        Matrix m = new Matrix();
        // Scale to exact size if need
        //縮放到指定size
        ImageScaleType scaleType = decodingInfo.getImageScaleType();
        if (scaleType == ImageScaleType.EXACTLY || scaleType == ImageScaleType.EXACTLY_STRETCHED) {
            ImageSize srcSize = new ImageSize(subsampledBitmap.getWidth(), subsampledBitmap.getHeight(), rotation);
            float scale = ImageSizeUtils.computeImageScale(srcSize, decodingInfo.getTargetSize(), decodingInfo
                    .getViewScaleType(), scaleType == ImageScaleType.EXACTLY_STRETCHED);
            if (Float.compare(scale, 1f) != 0) {
                m.setScale(scale, scale);

                if (loggingEnabled) {
                    L.d(LOG_SCALE_IMAGE, srcSize, srcSize.scale(scale), scale, decodingInfo.getImageKey());
                }
            }
        }
        // Flip bitmap if need
        //翻轉(zhuǎn)bitmap
        if (flipHorizontal) {
            m.postScale(-1, 1);

            if (loggingEnabled) L.d(LOG_FLIP_IMAGE, decodingInfo.getImageKey());
        }
        // Rotate bitmap if need
        //旋轉(zhuǎn)bitmap
        if (rotation != 0) {
            m.postRotate(rotation);

            if (loggingEnabled) L.d(LOG_ROTATE_IMAGE, rotation, decodingInfo.getImageKey());
        }

        Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap
                .getHeight(), m, true);
        if (finalBitmap != subsampledBitmap) {
            subsampledBitmap.recycle();
        }
        return finalBitmap;
    }

decodeImage總結(jié):

  • 獲取bitmap,根據(jù)ImageView大小、scaletype龟梦、方向隐锭,旋轉(zhuǎn)角度處理圖片,最后返回



3.fireFailEvent

    /**
     * 如果是異步且線程沒有被打斷,imageaware沒有被回收和重用
       那么獲取圖片失敗计贰,顯示失敗后的圖片钦睡,并且調(diào)用onLoadingFailed
     * @param failType
     * @param failCause
     */
    private void fireFailEvent(final FailType failType, final Throwable failCause) {
        if (syncLoading || isTaskInterrupted() || isTaskNotActual()) return;
        Runnable r = new Runnable() {
            @Override
            public void run() {
                //設(shè)置失敗后的圖片
                if (options.shouldShowImageOnFail()) {
                    imageAware.setImageDrawable(options.getImageOnFail(configuration.resources));
                }
                listener.onLoadingFailed(uri, imageAware.getWrappedView(), new FailReason(failType, failCause));
            }
        };
        runTask(r, false, handler, engine);
    }


3.2.5 執(zhí)行DisplayBitmapTask

同上,2.2-2.3

上一個我自己畫的流程圖(請忽略試用版水印幾個大字):

img_02.png


imageloader框架的源碼解析就講到這里躁倒,有興趣的朋友可以去研究下內(nèi)存緩存荞怒,本地緩存的實(shí)現(xiàn)等等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秧秉,一起剝皮案震驚了整個濱河市褐桌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌象迎,老刑警劉巖荧嵌,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異砾淌,居然都是意外死亡啦撮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門汪厨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逻族,“玉大人,你說我怎么就攤上這事骄崩∑噶郏” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵要拂,是天一觀的道長抠璃。 經(jīng)常有香客問我,道長脱惰,這世上最難降的妖魔是什么搏嗡? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮拉一,結(jié)果婚禮上采盒,老公的妹妹穿的比我還像新娘。我一直安慰自己蔚润,他們只是感情好磅氨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫡纠,像睡著了一般烦租。 火紅的嫁衣襯著肌膚如雪延赌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天叉橱,我揣著相機(jī)與錄音挫以,去河邊找鬼。 笑死窃祝,一個胖子當(dāng)著我的面吹牛掐松,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粪小,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼大磺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了糕再?” 一聲冷哼從身側(cè)響起量没,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎突想,沒想到半個月后殴蹄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猾担,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年袭灯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绑嘹。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡稽荧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出工腋,到底是詐尸還是另有隱情姨丈,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布擅腰,位于F島的核電站蟋恬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏趁冈。R本人自食惡果不足惜歼争,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渗勘。 院中可真熱鬧沐绒,春花似錦、人聲如沸旺坠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽价淌。三九已至申眼,卻和暖如春瞒津,著一層夾襖步出監(jiān)牢的瞬間蝉衣,已是汗流浹背括尸。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留病毡,地道東北人濒翻。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像啦膜,于是被迫代替她去往敵國和親有送。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,321評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,089評論 1 32
  • 1僧家、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_x閱讀 15,968評論 3 119
  • 最近看知乎上問題最多的,還是關(guān)于大學(xué)生活迷茫要怎么辦肌稻,大學(xué)每天過得很廢怎么辦等問題清蚀。 之前在知乎已經(jīng)總結(jié)了幾十個,...
    雨chen的晨閱讀 732評論 0 1
  • 我喜歡你爹谭,是雨后彩虹枷邪,如此絢麗。 我喜歡你诺凡,是行云流水东揣,如此愜意。 我喜歡你腹泌,是葉落歸根嘶卧,如此合理。
    顧墨源閱讀 215評論 0 3