Universal-Image-Loader源碼分析(二)——載入圖片的過程分析

来源:http://www.cnblogs.com/qifengshi/archive/2017/07/17/7194094.html
-Advertisement-
Play Games

之前的文章,在上面建立完config之後,UIl通過 來初始化ImageLoader對象,之後就可以用ImageLoader來載入圖片。 這裡,採用到單例模式來獲取ImageLoader對象,保證他全局初始化一次。再上面的分析中,我們可以看出單例模式的好處,創建ImageLoader對象的時候需要創 ...


之前的文章,在上面建立完config之後,UIl通過ImageLoader.getInstance().init(config.build());來初始化ImageLoader對象,之後就可以用ImageLoader來載入圖片。

這裡,採用到單例模式來獲取ImageLoader對象,保證他全局初始化一次。再上面的分析中,我們可以看出單例模式的好處,創建ImageLoader對象的時候需要創建Config,而Config裡面同樣初始化了一堆對象。如果每次用到都現初始化ImageLoader,消耗太大。我們看一下ImageLoader的init的源碼

public synchronized void init(ImageLoaderConfiguration configuration) {
        if (configuration == null) {
            throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
        }
        if (this.configuration == null) {
            L.d(LOG_INIT_CONFIG);
            //創建圖片載入引擎
            engine = new ImageLoaderEngine(configuration);
            this.configuration = configuration;
        } else {
            L.w(WARNING_RE_INIT_CONFIG);
        }
    }
    

如果該對象還沒有config,則將傳入的config賦值給this.config。並且初始化圖片載入引擎。

我們繼續看圖片載入引擎主要執行的工作。

class ImageLoaderEngine {
    /*ImageLoader載入配置*/
    final ImageLoaderConfiguration configuration;
    /*任務執行者*/
    private Executor taskExecutor;
    /*圖片緩存任務執行則*/
    private Executor taskExecutorForCachedImages;
    /*任務分配者*/
    private Executor taskDistributor;

    private final Map<Integer, String> cacheKeysForImageAwares = Collections
            .synchronizedMap(new HashMap<Integer, String>());
    private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();
    /*暫停*/
    private final AtomicBoolean paused = new AtomicBoolean(false);
    /*網路拒絕訪問*/
    private final AtomicBoolean networkDenied = new AtomicBoolean(false);
    /*網路慢*/
    private final AtomicBoolean slowNetwork = new AtomicBoolean(false);

    private final Object pauseLock = new Object();

    /**
     * ImageLoader引擎構造器
     * @param configuration
     */
    ImageLoaderEngine(ImageLoaderConfiguration configuration) {
        //初始化ImageLoader配置參數
        this.configuration = configuration;
        //初始化三個不同任務的執行者
        taskExecutor = configuration.taskExecutor;
        taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;
        taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
    }

    /** Submits task to execution pool */
    /**
     * 提交圖片載入和顯示任務到執行線程池中,進行運行
     * @param task   具體需要執行的任務
     */
    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來進行執行任務
                    taskExecutorForCachedImages.execute(task);
                } else {
                    //當天圖片在本地文件系統中沒有緩存,直接採用taskExecutor來進行執行任務
                    taskExecutor.execute(task);
                }
            }
        });
    }

    /**
     * Submits task to execution pool
     * 提交圖片顯示任務並且執行 (該圖片從記憶體緩存中取得)
     */
    void submit(ProcessAndDisplayImageTask task) {
        initExecutorsIfNeed();
        taskExecutorForCachedImages.execute(task);
    }

    /**
     * 根據需要進行初始化執行者
     */
    private void initExecutorsIfNeed() {
        if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) {
            taskExecutor = createTaskExecutor();
        }
        if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages)
                .isShutdown()) {
            taskExecutorForCachedImages = createTaskExecutor();
        }
    }

    /**
     * 進行創建任務執行者
     * @return
     */
    private Executor createTaskExecutor() {
        return DefaultConfigurationFactory
                .createExecutor(configuration.threadPoolSize, configuration.threadPriority,
                configuration.tasksProcessingType);
    }

    /**
     * 獲取當前被載入ImageAware到圖片的地址
     * Returns URI of image which is loading at this moment into passed {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}
     */
    String getLoadingUriForView(ImageAware imageAware) {
        return cacheKeysForImageAwares.get(imageAware.getId());
    }

    /**
     *
     * Associates <b>memoryCacheKey</b> with <b>imageAware</b>. Then it helps to define image URI is loaded into View at
     * exact moment.
     */
    void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
        cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
    }

    /**
     * Cancels the task of loading and displaying image for incoming <b>imageAware</b>.
     *
     * @param imageAware {@link com.nostra13.universalimageloader.core.imageaware.ImageAware} for which display task
     *                   will be cancelled
     */
    void cancelDisplayTaskFor(ImageAware imageAware) {
        cacheKeysForImageAwares.remove(imageAware.getId());
    }

    /**
     * Denies or allows engine to download images from the network.<br /> <br /> If downloads are denied and if image
     * isn't cached then {@link ImageLoadingListener#onLoadingFailed(String, View, FailReason)} callback will be fired
     * with {@link FailReason.FailType#NETWORK_DENIED}
     *
     * @param denyNetworkDownloads pass <b>true</b> - to deny engine to download images from the network; <b>false</b> -
     *                             to allow engine to download images from network.
     */
    void denyNetworkDownloads(boolean denyNetworkDownloads) {
        networkDenied.set(denyNetworkDownloads);
    }

    /**
     * Sets option whether ImageLoader will use {@link FlushedInputStream} for network downloads to handle <a
     * href="http://code.google.com/p/android/issues/detail?id=6066">this known problem</a> or not.
     *
     * @param handleSlowNetwork pass <b>true</b> - to use {@link FlushedInputStream} for network downloads; <b>false</b>
     *                          - otherwise.
     */
    void handleSlowNetwork(boolean handleSlowNetwork) {
        slowNetwork.set(handleSlowNetwork);
    }

    /**
     * Pauses engine. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.<br
     * /> Already running tasks are not paused.
     * 暫停任務運行
     */
    void pause() {
        paused.set(true);
    }

    /**
     * Resumes engine work. Paused "load&display" tasks will continue its work.
     * 任務恢復運行
     */
    void resume() {
        paused.set(false);
        synchronized (pauseLock) {
            pauseLock.notifyAll();
        }
    }

    /**
     * 停止ImageLoader引擎,取消所有正在運行或者掛起的圖片顯示任務,並且清除內部的數據
     * Stops engine, cancels all running and scheduled display image tasks. Clears internal data.
     * <br />
     * <b>NOTE:</b> This method doesn't shutdown
     * {@linkplain com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder#taskExecutor(java.util.concurrent.Executor)
     * custom task executors} if you set them.
     */
    void stop() {
        if (!configuration.customExecutor) {
            ((ExecutorService) taskExecutor).shutdownNow();
        }
        if (!configuration.customExecutorForCachedImages) {
            ((ExecutorService) taskExecutorForCachedImages).shutdownNow();
        }
        cacheKeysForImageAwares.clear();
        uriLocks.clear();
    }

    void fireCallback(Runnable r) {
        taskDistributor.execute(r);
    }

    ReentrantLock getLockForUri(String uri) {
        ReentrantLock lock = uriLocks.get(uri);
        if (lock == null) {
            lock = new ReentrantLock();
            uriLocks.put(uri, lock);
        }
        return lock;
    }

    AtomicBoolean getPause() {
        return paused;
    }

    Object getPauseLock() {
        return pauseLock;
    }

    boolean isNetworkDenied() {
        return networkDenied.get();
    }

    boolean isSlowNetwork() {
        return slowNetwork.get();
    }
}

上面的代碼中,核心的部分就是創建了任務執行器和圖片緩存執行器,並且在submit方法中針對提交的任務,選擇不同的執行器執行。

ImageLoader關於載入顯示圖片,有如下幾種用法,我們依次分析一下。

displayImage(), loadImage()

先看loadImage()

下麵的loadImage所有的重載方法。

    public void loadImage(String uri, ImageLoadingListener listener) {
        loadImage(uri, null, null, listener, null);
    }
    
    public void loadImage(String uri, ImageSize targetImageSize, ImageLoadingListener listener) {
        loadImage(uri, targetImageSize, null, listener, null);
    }
    
    public void loadImage(String uri, DisplayImageOptions options, ImageLoadingListener listener) {
        loadImage(uri, null, options, listener, null);
    }
    
    public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
            ImageLoadingListener listener) {
        loadImage(uri, targetImageSize, options, listener, null);
    }
    public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
    }

不管是幾個參數的loadImage,最後都會重載下麵的方法。

public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        checkConfiguration();
        if (targetImageSize == null) {
            targetImageSize = configuration.getMaxImageSize();
        }
        if (options == null) {
            options = configuration.defaultDisplayImageOptions;
        }

        NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
        displayImage(uri, imageAware, options, listener, progressListener);
    }

如果沒有指定targetImageSize以及options就會採用config預設的提供,然後根據targetImageSize以及uri生成一個NonViewAware,最終通過displayImage來載入。

targetImageSize是一個ImageSize對象,該類是image尺寸的封裝類,config的預設值是屏幕的寬高。
options是採用config預設創建,config的預設值是faultDisplayImageOptions = DisplayImageOptions.createSimple();
這個初始化都是創建的DisplayImageOptions都是預設值,基本什麼屬性都是false或者null或者0

接下來,我們看一下NonViewAware類,NonViewAware是實現了ImageAware介面的一個類,ImageAware介面主要定義了圖片處理和顯示所需要的方法和屬性。所以NonViewAware也只是對傳入的參數進行封裝,來提供一個外部訪問的介面。

真正顯示圖片的方法都是displayImage,displayImage方法和loadImage一樣,提供了多種參數,displayImage的重載方法要多一些,因為displayImage方法有一類是接受ImageView而另一類是接受ImageAware。
下麵是無論如何都是最終調用的方法:
代碼如下:

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        //進行檢查ImageLoader全局相關配置
        checkConfiguration();
        if (imageAware == null) {
            throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
        }
        if (listener == null) {
            listener = defaultListener;
        }
        //檢查圖片顯示配置
        if (options == null) {
            options = configuration.defaultDisplayImageOptions;
        }
        //==============圖片地址為空=================
        if (TextUtils.isEmpty(uri)) {
            engine.cancelDisplayTaskFor(imageAware);
            //介面方法回調,當前圖片載入任務開始
            listener.onLoadingStarted(uri, imageAware.getWrappedView());
            //進行判斷是否給imageview添加一個空地址的資源圖片
            if (options.shouldShowImageForEmptyUri()) {
                imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
            } else {
                imageAware.setImageDrawable(null);
            }
            //直接載入回調載入成功
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
            return;
        }
        //=============圖片地址存在=====================
        if (targetSize == null) {
            //如果圖片顯示的目標大小沒有設置的,那麼就使用預設大小尺寸即可
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }
        //根據地址和圖片目標尺寸信息,生成緩存key
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
        //開始進行載入圖片
        listener.onLoadingStarted(uri, imageAware.getWrappedView());

        //首先根據key去緩存中獲取是否還存在該圖片
        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
        if (bmp != null && !bmp.isRecycled()) {
            //緩存中該圖片存在
            L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
            if (options.shouldPostProcess()) {
                ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                        options, listener, progressListener, engine.getLockForUri(uri));
                ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                        defineHandler(options));
                //是否允許同步載入
                if (options.isSyncLoading()) {
                    displayTask.run();
                } else {
                    //提交進行顯示
                    engine.submit(displayTask);
                }
            } else {
                options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
            }
        } else {
            //緩存中不存在該圖片 通過網路載入
            if (options.shouldShowImageOnLoading()) {
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {
                imageAware.setImageDrawable(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()) {
                displayTask.run();
            } else {
                engine.submit(displayTask);
            }
        }
    }

具體的執行過程已經通過代碼註釋了,下麵梳理下流程。
首先,針對為null的屬性,初始化這些屬性,然後判斷傳入的uri是不是為空,如果為空,則根據options.shouldShowImageForEmptyUri來判斷是否顯示先設置好的圖片。
如果傳入的uri是存在的,則根據地址和圖片目標尺寸的信息來生成緩存key,然後執行
prepareDisplayTaskFor方法。
再根據key來判斷緩存中是否存在該圖片,如果存在,就判斷options的shouldPostProcess,如果為true就創建一個ProcessAndDisplayImageTask對象,通過圖片載入引擎來載入。如果返回的值false,則調用options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);

如果緩存不存在,則直接通過網路載入該圖片,載入的方式是構建LoadAndDisplayImageTask對象,通過圖片載入引擎來判斷。

在這裡,關於圖片的緩存相關的內容,先不分析。接下來主要分析的是圖片如何執行這兩種不同的任務的。

第一種,在有緩存的情況下,通過判斷shouldPostProcess為true來讓圖片引擎處理任務,這裡的shouldPostProcess是指在拿到bitmap之後是否進行後續的操作,判斷標準就是postProcess是否為null.
如果不為null,也就是shouldPostProcess為true,則執行下麵的代碼:
下麵,我們來看一下,圖片引擎是如何執行ProcessAndDisplayImageTask。

void submit(ProcessAndDisplayImageTask task) {
        initExecutorsIfNeed();
        taskExecutorForCachedImages.execute(task);
    }

內部直接調用taskExecutorForCachedImages去執行task,所以主要看ProcessAndDisplayImageTask的task構造。

final class ProcessAndDisplayImageTask implements Runnable {

    private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
    /*ImageLoader引擎*/
    private final ImageLoaderEngine engine;
    private final Bitmap bitmap;
    /*ImageLoader信息封裝對象*/
    private final ImageLoadingInfo imageLoadingInfo;
    private final Handler handler;

    /**
     * 圖片處理顯示任務構造器
     * @param engine
     * @param bitmap
     * @param imageLoadingInfo
     * @param handler
     */
    public ProcessAndDisplayImageTask(ImageLoaderEngine engine, Bitmap bitmap, ImageLoadingInfo imageLoadingInfo,
            Handler handler) {
        this.engine = engine;
        this.bitmap = bitmap;
        this.imageLoadingInfo = imageLoadingInfo;
        this.handler = handler;
    }

    @Override
    public void run() {
        L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);
        //獲取圖片處理器 然後取得載入的圖片
        BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
        Bitmap processedBitmap = processor.process(bitmap);
        //封裝圖片顯示任務   其中圖片來源設置成-來自記憶體緩存
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
                LoadedFrom.MEMORY_CACHE);
        //執行任務
        LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
    }
}

這邊可以看出,task內部run方法裡面,首先得到一個BitmapProcessor,然後通過該processor去處理bitmap,然後將處理後的bitmap以及其他信息封裝成了DisplayBitmapTask,然後最終還是執行了LoadAndDisplayImageTask的runTask方法

下麵將看LoadAndDisplayImageTask.runTask方法

static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
        if (sync) {
            //如果同步 任務直接運行
            r.run();
        } else if (handler == null) {
            engine.fireCallback(r);
        } else {
            //任務通過Handler分發到主線程執行
            handler.post(r);
        }
    }

這邊,直接在UI線程displayBitmapTask,後面再看displayBitmapTask的內部實現。
回到shouldPostProcess的判斷那裡,如果為false,則直接調用BitmapDisplay顯示圖片,這裡傳入的是SimpleBitmapDisplayer
再回到緩存判斷那裡,上面的代碼都是在有記憶體緩存的情況下,執行的。看一下在無記憶體緩存時,執行的細節。

//緩存中不存在該圖片 通過網路載入
            if (options.shouldShowImageOnLoading()) {
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {
                imageAware.setImageDrawable(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()) {
                displayTask.run();
            } else {
                engine.submit(displayTask);
            }

通過源碼可以看出,首先判斷要不要進行顯示載入中的View,然後構建圖片載入信息,通過圖片載入信息構建LoadAndDisplayImageTask對象,執行去run方法。
上面,我們已經分析了其runTask方法,該方法比較簡單,這次我們看一下run方法。

public void run() {
        //如果當前狀態是暫停 當前任務直接返回
        if (waitIfPaused()) return;
        //如果當前狀態需要等待  當前任務直接返回
        if (delayIfNeed()) return;

        ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
        L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
        if (loadFromUriLock.isLocked()) {
            L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
        }
        //任務加鎖
        loadFromUriLock.lock();
        Bitmap bmp;
        try {
            //進行檢查任務  判斷當前要顯示的引用對象是否已經被回收了
            checkTaskNotActual();
            //先從緩存中獲取圖片
            bmp = configuration.memoryCache.get(memoryCacheKey);
            if (bmp == null || bmp.isRecycled()) {
                //進行嘗試獲取載入圖片(去文件中,文件中不存在去網路下載,然後緩存到文件)
                bmp = tryLoadBitmap();
                if (bmp == null) return; // listener callback already was fired

                checkTaskNotActual();
                checkTaskInterrupted();

                if (options.shouldPreProcess()) {
                    L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
                    bmp = options.getPreProcessor().process(bmp);
                    if (bmp == null) {
                        L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
                    }
                }

                if (bmp != null && options.isCacheInMemory()) {
                    L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
                    configuration.memoryCache.put(memoryCacheKey, bmp);
                }
            } else {
                //從緩存中獲取到圖片信息
                //設置圖片來源信息 --Memory Cache
                loadedFrom = LoadedFrom.MEMORY_CACHE;
                L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
            }

            if (bmp != null && options.shouldPostProcess()) {
                L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
                bmp = options.getPostProcessor().process(bmp);
                if (bmp == null) {
                    L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
                }
            }
            checkTaskNotActual();
            checkTaskInterrupted();
        } catch (TaskCancelledException e) {
            fireCancelEvent();
            return;
        } finally {
            //任務取消鎖
            loadFromUriLock.unlock();
        }
        //封裝圖片顯示任務對象
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        //進行任務運行
        runTask(displayBitmapTask, syncLoading, handler, engine);
    }

其流程圖如下:

可以看到這邊,利用的圖片三級緩存,第一級是記憶體緩存,如果記憶體緩存沒有則利用二級緩存,從文件中去讀取,如果文件中有,則從文件中取出bitmap,如果沒有則從網路下載。
這邊從文件中bitmap的方法是tryLoadBitmap,下麵主要看一下這個方法

private Bitmap tryLoadBitmap() throws TaskCancelledException {
        Bitmap bitmap = null;
        try {
            //從本地文件緩存中獲取圖片
            File imageFile = configuration.diskCache.get(uri);
            if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
                L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
                //文件存在設置圖片來源
                loadedFrom = LoadedFrom.DISC_CACHE;
                //檢查引用是否已經被回收了
                checkTaskNotActual();
                //圖片解碼,文件轉換成bitmap對象
                bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
            }
            if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
                //本地文件系統中圖片解碼失敗,嘗試通過網路獲取
                loadedFrom = LoadedFrom.NETWORK;
                String imageUriForDecoding = uri;
                //判斷圖片可以本地文件系統緩存以及嘗試本地文本系統緩存(網路下載圖片,下載成功圖片緩存本地文件系統)
                if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
                    //從本地文件系統緩存中獲取圖片
                    imageFile = configuration.diskCache.get(uri);
                    if (imageFile != null) {
                        imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                    }
                }

                checkTaskNotActual();
                //圖片解碼
                bitmap = decodeImage(imageUriForDecoding);

                if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                    //回調圖片解碼失敗
                    fireFailEvent(FailType.DECODING_ERROR, null);
                }
            }
        } catch (IllegalStateException e) {
            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;
    }

首先是從diskcache中,取出File,然後對file進行轉換。如果轉換後的bitmap為null,則從網路獲取圖片,獲取圖片的方法調用是在options.isCacheOnDisk() && tryCacheImageOnDisk() 該判斷首先判斷是否存儲在文件中,如果不存在文件中,就不執行後面的網路獲取。

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;
    }

downloadImage是執行網路請求的方法,其內部通過BaseImageDownloader進行下載,其內部的網路庫是HttpURLConnection,下載後的圖片,根據設定的文件存儲的最大寬高,進行縮放與保存。
這樣,就在文件緩存中緩存了圖片,在回到上面LoadAndDisplayTask的run方法,在得到bitmap之後,就會判斷是否要 對bitmap進行預處理,預處理完的bitmap全部會緩存到記憶體緩存中。上面的操作都是建立在bitmap中記憶體緩存中取沒有取出來的情況,如果取出來就直接得到bitmap,然後從bitmap判斷是否進行後續的處理。

這裡,簡單說一下 preProcessor以及postProcessor,preProcessor是指對圖片進行預處理,比如加水印,如果加水印的圖片都會緩存到記憶體,postProcessor是對取出的bitmap做一些後續的操作,操作後將顯示出來。
最後得到的Bitmap會封裝成DisplayBitmapTask,調用上面提到的runtask方法,進行處理。

這樣,到此,發起圖片獲取需求,到圖片經過記憶體緩存,文件緩存,網路獲取後得到,然後再通過Handler回到UI線程的流程就分析完畢了。
其實,整個流程非常的簡單,清晰。只不過在考慮到了多種情況,使得代碼看上去很多。

下麵,將分析圖片載入框架最重要的一部分,緩存的設計。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 引言:最近在做一個iOS端的小游戲,想要實現在安裝時自動關聯好友的功能,就發帖詢問有沒有好的想法。在帖子中法想了這個不錯的SDK,通過它我們還實現了,安裝後自動進入好友游戲房間的功能。這裡我就分享一下,我在集成過程中,發現的一個小技巧。 首先,按照正常的思路,下載導入OpeninstallSDK到工 ...
  • `SVProgressHUD iOS`開發中比較常用的一個三方庫,用來在執行耗時操作或者指示用戶操作結果的場合,由於使用簡單,功能豐富,交互友好,被廣泛應用。本文從源碼的角度,解讀一下實現的過程,希望能起到拋磚引玉的作用。 一. 效果預覽 1. SVPIndefiniteAnimatedView 2 ...
  • 思路:獲取每項item的高度,並相加,再加上分割線的高度,作為整個ListView的高度,方法如下: ...
  • 說明:這篇文章是在百度上搜索“NSURLSession與NSURLConnection區別”查找到的資料,僅供自己學習理解,不屬於原創,本來想註明來源,但是發現很多內容相同的文章,也不知道誰才是真正的原創作者,所以就不寫明出處了!在這裡我向真正的原作者說聲感謝,謝謝你的分享! 1、 使用現狀 NSU ...
  • JB2/JB3/JB5/JB9版本: 1. 請修改 Launcher2/res/layout/qsb_bar.xml,如下:<include android:id="@+id/qsb_search_bar"layout="@layout/search_bar"android:visibility=" ...
  • 一、碎片的簡單用法(實現在一個活動中添加兩個碎片,並讓這兩個碎片平分活動空間) 1、新建一個FragmentTest項目; 新建一個左側碎片佈局left_fragment.xml,代碼如下:(只放置一個按鈕並水平居中顯示) 新建右側碎片佈局right_fragment.xml,代碼如下:(佈局背景設 ...
  • Android Studio 生成 Java Doc 出現“編碼GBK的不可映射字元”問題 錯誤的解決方案,複製粘貼一萬遍也是錯誤的,下麵是查找出來的,沒有用的解決方案(還有幾個,就例舉下麵這個): 這種坑人的、自己沒有試過的、浪費大家時間的方案就不要轉發了好嗎?不要轉發了好嗎?不要轉發了好嗎? 正 ...
  • 一、傳值分類 頁面傳值基本分為兩種:正向傳值和反向傳值。 二、傳值方式 傳值,最基本的無非就是代理傳值、通知傳值、block傳值等,還有大家經常用到的屬性傳值和單例傳值、存儲傳值等。 1、代理傳值 代理傳值,簡單明瞭、淺顯易懂,實際開發中也常用。 委托做的事情: 1.1、聲明委托變數 1.2 定義協 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...