Universal-Image-Loader(1)

UniversalImageLoader

[TOC]

本章主要介紹UniversalImageLoader的主要實現(xiàn)原理,和使用方法

在介紹之前,理解《Android開發(fā)藝術(shù)探索》的第十二章Bitmap的加載和Cache是十分重要的,因為該章節(jié)介紹了ImageLoader的最核心的工作原理,這樣能夠為更好地理解UIL打下堅實的基礎(chǔ)。因此建議先去閱讀理解該章的內(nèi)容。

由該章的內(nèi)容中可以知道,ImageLoader最重要的幾點(diǎn)工作如下

  • 圖片的同步加載
  • 圖片的異步加載
  • 圖片壓縮
  • 內(nèi)存緩存
  • 磁盤緩存
  • 網(wǎng)絡(luò)拉取

因此我們從這幾點(diǎn)出發(fā),結(jié)合UniversalImageLoader關(guān)鍵部分的源碼來具體分析UIL的實現(xiàn)原理以及如果使用。

1.UIL中主要工作類

從上面ImageLoader的主要工作中可以得知大概需要以下幾個類:

  1. ImageLoader:外界使用的主要工作類
  2. 線程池:用于圖片的網(wǎng)絡(luò)加載
  3. Handler:用于圖片的異步傳送加載
  4. ImageView:用于加載圖片的控件
  5. ImageResizer:用于圖片的壓縮
  6. LruCache:用于圖片的內(nèi)存緩存
  7. DiskLruCache:用于圖片的磁盤緩存

對應(yīng)以上的工作,UIL中重要的幾個類有:

  1. ImageLoader:主要工作類
  2. ImageLoaderConfiguration:配置類,用于配置大部分工作的類成員,包括線程池成員,內(nèi)存緩存成員,磁盤緩存成員,圖片下載器,圖片解碼器和顯示圖片參數(shù)等等。該類的作用就是參與ImageLoader的初始化并配置好正常工作的參數(shù),因此該類很重要
  3. ImageLoaderEngine:用于線程池的管理,是一個管理類。該類與ImageLoaderConfiguration類的區(qū)別是,Configuration只是用于配置線程池,而真正使用線程池工作的是ImageLoaderEngine類
  4. ImageLoadingInfo:用于配置顯示圖片的信息類,包括圖片的url、圖片的加載大小、顯示圖片的控件和用于配置顯示圖片的配置類DisplayImageOptions等等
  5. DisplayImageOptions:顯示圖片參數(shù)的配置類,其中包含了Handler用于異步加載,BitmapDisplayer用于最終顯示圖片。該類和ImageLoadingInfo的關(guān)系是,DisplayImageOptions包含于ImageLoadingInfo之中,畢竟DisplayImageOptions也是跟圖片的顯示參數(shù)有關(guān)系的
  6. LoadAndDisplayImageTask:封裝了獲取圖片并顯示的任務(wù),用于被線程池執(zhí)行
  7. ImageDownloader:用于下載圖片,即獲取圖片的輸入流
  8. ImageDecoder:用于圖片解碼壓縮,可以理解成將輸入流轉(zhuǎn)換成Bitmap的過程,因此一般ImageDecoder和ImageDownloader是一起工作的,一個負(fù)責(zé)獲取圖片的輸入流,一個負(fù)責(zé)將輸入流轉(zhuǎn)換成Bitmap圖片對象,其中就包含了按需求壓縮圖片的功能
  9. MemoryCache:內(nèi)存緩存類
  10. DiskCache:磁盤緩存類

UIL中的主要工作大體上是由前5個類完成的,其實也很好理解,ImageLoader是向外提供的主要接口,ImageLoaderConfiguration為ImageLoader正常工作而提供配置參數(shù),ImageLoaderEngine是在ImageLoader中的主要工作成員,ImageLoadingInfo是為了配置最后顯示圖片的參數(shù)比如如何顯示、顯示在哪里之類的,畢竟ImageLoader顧名思義是跟圖片的顯示密不可分的,因此沒有DisplayImageOptions怎么讓圖片得到正確的顯示。


2.UIL的工作流程

在深入源碼分析UIL之前,我們先來了解UIL的工作流程,這樣帶著大局觀去看程序就不會陷入了源碼中不能自拔,導(dǎo)致只見樹木不見森林的情況。因為了解了整體的工作流程之后,在看源碼的時候就能迅速的找到重點(diǎn)流程而忽略細(xì)節(jié)的處理,有助于能夠迅速地大體將框架學(xué)習(xí)一遍。因此在學(xué)習(xí)任何源碼之前可以的話,最好先去搜索相關(guān)的工作原理,在掌握了一定原理之后再去看源碼則會達(dá)到事半功倍的效果。

UIL的工作流程其實跟《Android開發(fā)藝術(shù)探索》中ImageLoader的工作流程大致一樣,就是利用3級緩存策略
內(nèi)存緩存->磁盤緩存->網(wǎng)絡(luò)拉取

接下來如果一開始就從頭到尾分析源碼的工作流程可能會導(dǎo)致思路不清晰,因為其中用到了許多類,如果在分析工作流程的過程中再去分析一個個類的作用會將思路中斷,不利于對整個框架的學(xué)習(xí),因此下面會先對一個個在工作流程中會被用到的一些比較關(guān)鍵的類進(jìn)行分析,最后再通過源碼將整個過程過一遍。


3.ImageLoaderConfiguration

ImageLoaderConfiguration作為一個配置類,對于ImageLoader正常工作來說必不可少,因為它主要是為了配置好使ImageLoader正常工作的各種類。
ImageLoaderConfiguration采用的是建造者Builder模式來創(chuàng)建對象,雖然它擁有很多類成員,但是有一些是在Configuration對象被創(chuàng)建的時候初始化好了的。
從下面的代碼可以看出,在UIL內(nèi)部,最重要的無外乎線程池Executor、內(nèi)存緩存MemoryCache、磁盤緩存DiskCache、圖片下載器ImageDownloader和圖片顯示配置參數(shù)DisplayImageOptions這幾個類等等。
其中一些默認(rèn)的參數(shù)是,線程池的大小默認(rèn)是3,任務(wù)隊列采用的是FIFO,有默認(rèn)的圖片下載器BaseImageDownLoader,圖片解碼器BaseImageDecoder和默認(rèn)顯示圖片配置DisplayImageOptions

// 下面這些Configuration類中所有的可選方法,可以看出有些屬性是默認(rèn)的
File cacheDir = StorageUtils.getCacheDirectory(context);
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
        .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
        .diskCacheExtraOptions(480, 800, null)
        .taskExecutor(...)
        .taskExecutorForCachedImages(...)
        .threadPoolSize(3) // default
        .threadPriority(Thread.NORM_PRIORITY - 2) // default
        .tasksProcessingOrder(QueueProcessingType.FIFO) // default
        .denyCacheImageMultipleSizesInMemory()
        .memoryCache(new LruMemoryCache(2 * 1024 * 1024))
        .memoryCacheSize(2 * 1024 * 1024)
        .memoryCacheSizePercentage(13) // default
        .diskCache(new UnlimitedDiskCache(cacheDir)) // default
        .diskCacheSize(50 * 1024 * 1024)
        .diskCacheFileCount(100)
        .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
        .imageDownloader(new BaseImageDownloader(context)) // default
        .imageDecoder(new BaseImageDecoder()) // default
        .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
        .writeDebugLogs()
        .build();


我們從源碼中看默認(rèn)項是怎么實現(xiàn)的,在源碼中我們可以看到,在build()的時候會判斷一些屬性是否為null,如果為null則調(diào)用對應(yīng)的創(chuàng)建方法構(gòu)造一個默認(rèn)對象。可以看出默認(rèn)被構(gòu)造的有taskExecutor、diskCache、memoryCache、downloader、decoder和defaultDisplayImageOptions等等,這些都是ImageLoader加載顯示一個圖片所需要的類。因此實際上不要手動配置Configuration,只需要一個build的configuration也能使ImageLoader正常工作。

/** Builds configured {@link ImageLoaderConfiguration} object */
public ImageLoaderConfiguration build() {
    initEmptyFieldsWithDefaultValues();
    return new ImageLoaderConfiguration(this);
}

private void initEmptyFieldsWithDefaultValues() {
    if (taskExecutor == null) {
        taskExecutor = DefaultConfigurationFactory
                .createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
    } else {
        customExecutor = true;
    }
    if (taskExecutorForCachedImages == null) {
        taskExecutorForCachedImages = DefaultConfigurationFactory
                .createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
    } else {
        customExecutorForCachedImages = true;
    }
    if (diskCache == null) {
        if (diskCacheFileNameGenerator == null) {
            diskCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();
        }
        diskCache = DefaultConfigurationFactory
                .createDiskCache(context, diskCacheFileNameGenerator, diskCacheSize, diskCacheFileCount);
    }
    if (memoryCache == null) {
        memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
    }
    if (denyCacheImageMultipleSizesInMemory) {
        memoryCache = new FuzzyKeyMemoryCache(memoryCache, MemoryCacheUtils.createFuzzyKeyComparator());
    }
    if (downloader == null) {
        downloader = DefaultConfigurationFactory.createImageDownloader(context);
    }
    if (decoder == null) {
        decoder = DefaultConfigurationFactory.createImageDecoder(writeLogs);
    }
    if (defaultDisplayImageOptions == null) {
        defaultDisplayImageOptions = DisplayImageOptions.createSimple();
    }
}

另:BitmapDisplayer 默認(rèn)初始化在DisplayImageOptions中
Handler 默認(rèn)初始化在ImageLoader的displayImage中,然后傳送到LoadAndDisplayImageTask當(dāng)中
DisplayImageOptions 默認(rèn)初始化在ImageLoader的loadImage中


4.ImageLoaderEngine

線程池的管理類,因為主要的任務(wù)都是通過線程池來完成的,因此可以認(rèn)為ImageLoader里面干活的就是ImageLoaderEngine類里面的線程池。
下面主要介紹ImageLoaderEngine的構(gòu)造方法和兩個主要工作方法

/**
 * 構(gòu)造方法,可以看到利用的是Configuration類當(dāng)中的參數(shù)來配置本身的線程池成員
 */
ImageLoaderEngine(ImageLoaderConfiguration configuration) {
    this.configuration = configuration;

    taskExecutor = configuration.taskExecutor;
    taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;

    taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
}

/** 執(zhí)行任務(wù)的方法,可以看到內(nèi)部是利用線程池成員來執(zhí)行的*/
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.execute(task);
            }
        }
    });
}

/** Submits task to execution pool */
void submit(ProcessAndDisplayImageTask task) {
    initExecutorsIfNeed();
    taskExecutorForCachedImages.execute(task);
}


5.LoadAndDisplayImageTask & DisplayBitmapTask

UIL工作中最主要的任務(wù)類,其中LoadAndDisplayImageTask包含了DisplayBitmapTask,從名稱上就能看出關(guān)系,LoadAndDisplayImageTask是下載和顯示圖片,而DisplayBitmapTask只是顯示顯示圖片。
其實這個任務(wù)類也是UIL的核心工作類,有必要深刻地理解其工作流程。

下面是LoadAndDisplayImageTask的run方法,這里跟之前學(xué)過ImageLoader的同步加載圖片流程差不多,這里主要分為幾步:

  • 加上同步鎖:loadFromUriLock.lock();
  • 從內(nèi)存緩存中獲取圖片:bmp = configuration.memoryCache.get(memoryCacheKey);
  • 從磁盤緩存、網(wǎng)絡(luò)中獲取圖片:bmp = tryLoadBitmap();
  • 利用DisplayBitmapTask異步顯示圖片:runTask(displayBitmapTask, syncLoading, handler, engine);注意該方法中有利用到Handler用于異步加載圖片,而該Handler是在ImageLoader的displayImage中被默認(rèn)初始化的
final class LoadAndDisplayImageTask
{
    ...

    @Override
    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();

            //從內(nèi)存緩存中獲取
            bmp = configuration.memoryCache.get(memoryCacheKey);
            if (bmp == null || bmp.isRecycled()) {
                //如果內(nèi)存中獲取不到則從磁盤、網(wǎng)絡(luò)中獲取
                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);
                    }
                }

                //在從磁盤或者網(wǎng)絡(luò)獲取到圖片之后將其加載到內(nèi)存里面
                if (bmp != null && options.isCacheInMemory()) {
                    L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
                    configuration.memoryCache.put(memoryCacheKey, bmp);
                }
            } else {
                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();
        }

        //在獲取了圖片之后將其放進(jìn)DisplayBitmapTask中顯示圖片
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        runTask(displayBitmapTask, syncLoading, handler, engine);
    }

    /**
     * 該方法的作用是先從磁盤緩存里獲取數(shù)據(jù)
     * 如果沒有則調(diào)用tryCacheImageOnDisk()從網(wǎng)絡(luò)獲取圖片并將其存到磁盤中
     *     然后再從磁盤中獲取圖片
     * 因此整個流程是:
     *     磁盤緩存獲取->網(wǎng)絡(luò)獲取并存到磁盤緩存并從磁盤緩存獲取
     */
    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 = 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;
                //從網(wǎng)絡(luò)上獲取圖片存到磁盤中
                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;
    }

    /** 
     * 從網(wǎng)絡(luò)中獲取圖片存儲到磁盤緩存中,通過downloadImage()實現(xiàn)的
     *     而后的resizeAndSaveImage(width, height)是為了根據(jù)實際需求將圖片壓縮并重新存入磁盤緩存
     *     
     * 下載成功則返回true
     */
    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;
    }

    /**
     * 該方法主要分為兩步:
     * 1.從網(wǎng)絡(luò)獲取圖片輸入流:InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
     *     其中Downloader在Configuration被創(chuàng)建的時候被默認(rèn)初始化成BaseImageDownloader
     * 2.通過輸入流將圖片存入磁盤緩存:configuration.diskCache.save(uri, is, this);
     */
    private boolean downloadImage() throws IOException {
        InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
        if (is == null) {
            L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
            return false;
        } else {
            try {
                return configuration.diskCache.save(uri, is, this);
            } finally {
                IoUtils.closeSilently(is);
            }
        }
    }

}

上面的任務(wù)是圖片的同步加載過程,這是通過線程池執(zhí)行的,最后還要將圖片顯示出來,此時就需要轉(zhuǎn)到主線程設(shè)置圖片,也就是通過Handler來操作,而顯示圖片的過程是在DisplayBitmapTask中實現(xiàn)的,下面來看一下DisplayBitmapTask的運(yùn)行過程。

final class DisplayBitmapTask
{
    /**
     * 在該方法中,主要的作用是在加載圖片的過程中觸發(fā)各個回調(diào)函數(shù)
     * 其中最主要的是通過displayer將圖片加載顯示到imageAware控件當(dāng)中
     *
     * 注:該任務(wù)應(yīng)該運(yùn)行在主線程
     */
    @Override
    public void run() {
        if (imageAware.isCollected()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else if (isViewWasReused()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else {
            L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
            //將圖片bitmap加載到imageAware中用于顯示
            displayer.display(bitmap, imageAware, loadedFrom);
            engine.cancelDisplayTaskFor(imageAware);
            listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
        }
    }

}

上面DisplayBitmapTask只是定義好了如何加載圖片到控件當(dāng)中,真正被轉(zhuǎn)移到主線程中執(zhí)行還是在LoadAndDisplayImageTask中的runTask方法中,可以看到,runTask方法中是通過參數(shù)中的handler將DisplayBitmapTask執(zhí)行到主線程中的。

final class LoadAndDisplayImageTask
{
    ...
        @Override
    public void run() {

        ...

        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        //displayBitmapTask執(zhí)行
        runTask(displayBitmapTask, syncLoading, handler, engine);
    }

    /**
     * 從該方法中可以發(fā)現(xiàn),displayBitmapTask是通過handler的post達(dá)到運(yùn)行在主線程中加載圖片的效果
     */
    static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
            if (sync) {
                r.run();
            } else if (handler == null) {
                engine.fireCallback(r);
            } else {
                handler.post(r);
            }
        }
}

6.ImageLoadingInfo

ImageLoadingInfo信息類的作用類似于ImageLoaderConfiguration配置類,只是擔(dān)當(dāng)了信息參數(shù)儲存的角色,實際并不做任何工作。
比如ImageLoaderConfiguration配置類是為ImageLoader正常工作流程提供參數(shù),比如線程池、緩存和下載器等等。
而ImageLoadingInfo的作用則是為圖片的加載顯示提供參數(shù),比如圖片的url、圖片的顯示控件、目標(biāo)顯示大小和顯示配置類DisplayImageOptions等等,注意DisplayImageOptions的作用是提供在顯示圖片時所需的參數(shù)和一些回調(diào)接口,下面有詳細(xì)說明。 這里只需要記住ImageLoadingInfo的作用就是跟圖片加載顯示有關(guān)。


final class ImageLoadingInfo {

    final String uri;
    final String memoryCacheKey;
    final ImageAware imageAware;
    final ImageSize targetSize;
    final DisplayImageOptions options;
    final ImageLoadingListener listener;
    final ImageLoadingProgressListener progressListener;
    final ReentrantLock loadFromUriLock;

    public ImageLoadingInfo(String uri, ImageAware imageAware, ImageSize targetSize, String memoryCacheKey,
            DisplayImageOptions options, ImageLoadingListener listener,
            ImageLoadingProgressListener progressListener, ReentrantLock loadFromUriLock) {
        this.uri = uri;
        this.imageAware = imageAware;
        this.targetSize = targetSize;
        this.options = options;
        this.listener = listener;
        this.progressListener = progressListener;
        this.loadFromUriLock = loadFromUriLock;
        this.memoryCacheKey = memoryCacheKey;
    }
}


7.DisplayImageOptions

DisplayImageOptions跟圖片的最終顯示密切相關(guān),而上面ImageLoadingInfo只是提供了一些參數(shù),但并不直接跟圖片顯示有關(guān)系,DisplayImageOptions與圖片如何正確顯示才是密切相關(guān)的,因此配置好DisplayImageOptions是ImageLoader正確工作的重要一環(huán),跟ImageLoaderConfiguration一樣對于ImageLoader來說是必不可少的。
可以說在ImageLoader中,ImageLoaderConfiguration決定了圖片如何加載,DisplayImageOptions決定了圖片如何正確顯示,每張圖片的加載都離不開DisplayImageOptions的配置參數(shù)。
以下是跟ImageLoaderConfiguration一樣的所有可選的顯示圖片參數(shù)配置。其中較重要的參數(shù)有cacheInMemory,cacheOnDisk這兩個參數(shù)默認(rèn)是false,即不使用緩存,displayer決定圖片顯示的樣式,這幾個一般在創(chuàng)建Options對象時需要重新配置,尤其是緩存。

注:如果DisplayImageOptions沒有手動地創(chuàng)建賦予ImageLoader,則在ImageLoaderConfiguration中會默認(rèn)創(chuàng)建一個Options對象,下面程序中后面帶有default的就是默認(rèn)配置的參數(shù)。

// DON'T COPY THIS CODE TO YOUR PROJECT! This is just example of ALL options using.
DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.ic_stub) // resource or drawable
        .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
        .showImageOnFail(R.drawable.ic_error) // resource or drawable
        .resetViewBeforeLoading(false)  // default
        .delayBeforeLoading(1000)
        .cacheInMemory(false) // default
        .cacheOnDisk(false) // default
        .preProcessor(...)
        .postProcessor(...)
        .extraForDownloader(...)
        .considerExifParams(false) // default
        .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
        .bitmapConfig(Bitmap.Config.ARGB_8888) // default
        .decodingOptions(...)
        .displayer(new SimpleBitmapDisplayer()) // default
        .handler(new Handler()) // default
        .build();


8.ImageDownloader

圖片下載器,即通過圖片的URI來獲取到圖片的InputStream。ImageDownloader是一個interface,主要方法就只有g(shù)etStream(String imgUri, Object extra),即外界通過該方法便可通過imgUri獲取到圖片的InputStream。UIL中有實現(xiàn)了該接口的BaseImageDownloader,我們來看看它是怎么實現(xiàn)的。

關(guān)注重點(diǎn):

  1. ImageDownloader的作用主要是向外提供一個下載的接口方法getStream(String imgUri, Object extra)
  2. 默認(rèn)連接超時時間為5s,讀取超時時間為20s
  3. 可以從多個地方比如網(wǎng)絡(luò)、文件、Content、Assets等地方獲取圖片輸入流
  4. getStreamFromNetwork從網(wǎng)絡(luò)獲取圖片輸入流:
    • 采用HttpURLConnection作為連接類
    • 如果服務(wù)器返回3xx則進(jìn)行重定向
    • 將InputStream封裝成ContentLengthInputStream返回
  5. getStreamFromFile從文件獲取圖片輸入流
  6. ...

總的來說,ImageDownLoader的作用就是從圖片的uri獲取到相應(yīng)的InputStream

public class BaseImageDownloader implements ImageDownloader {
    //連接超時時間
    public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
    //讀取超時時間
    public static final int DEFAULT_HTTP_READ_TIMEOUT = 20 * 1000; // milliseconds

    //緩存大小
    protected static final int BUFFER_SIZE = 32 * 1024; // 32 Kb
    //允許使用的用于URI中的字符
    protected static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";
    //最大重定向的次數(shù),用于在服務(wù)器返回3xx時進(jìn)行重定向
    protected static final int MAX_REDIRECT_COUNT = 5;

    ...

    /**
     * 構(gòu)造方法,可以自定義連接超時和讀取超時時間
     *     默認(rèn)分別是5s和20s
     */
    public BaseImageDownloader(Context context) {
        this(context, DEFAULT_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT);
    }

    public BaseImageDownloader(Context context, int connectTimeout, int readTimeout) {
        this.context = context.getApplicationContext();
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
    }

    /**
     * 實現(xiàn)ImageLoader方法,也是最主要的方法
     *     可以看出可以分別從網(wǎng)絡(luò)、文件、Content、Assets等等地方獲取圖片輸入流
     *  其中extra是傳到DisplayImageOptions.Builder中的extraForDownloader,該參數(shù)可以為null
     */
    @Override
    public InputStream getStream(String imageUri, Object extra) throws IOException {
        switch (Scheme.ofUri(imageUri)) {
            case HTTP:
            case HTTPS:
                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);
        }
    }

    /**
     * 從網(wǎng)絡(luò)的uri獲取到InputStream
     * 
     */
    protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
        HttpURLConnection conn = createConnection(imageUri, extra);

        //重定向
        int redirectCount = 0;
        while (conn.getResponseCode() / 100 == 3 && redirectCount < 
        MAX_REDIRECT_COUNT) {
            conn = createConnection(conn.getHeaderField("Location"), extra);
            redirectCount++;
        }

        //通過HttpURLConnection獲取InputStream
        InputStream imageStream;
        try {
            imageStream = conn.getInputStream();
        } catch (IOException e) {
            // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
            IoUtils.readAndCloseStream(conn.getErrorStream());
            throw e;
        }

        if (!shouldBeProcessed(conn)) {
            IoUtils.closeSilently(imageStream);
            throw new IOException("Image request failed with response code " + conn.getResponseCode());
        }

        return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
    }

    //創(chuàng)建HttpURLConnection連接
    protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
        String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
        HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
        conn.setConnectTimeout(connectTimeout);
        conn.setReadTimeout(readTimeout);
        return conn;
    }

    /**
     * 從文件獲取圖片輸入流
     */
    protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
        String filePath = Scheme.FILE.crop(imageUri);
        if (isVideoFileUri(imageUri)) {
            return getVideoThumbnailStream(filePath);
        } else {
            BufferedInputStream imageStream = new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE);
            return new ContentLengthInputStream(imageStream, (int) new File(filePath).length());
        }
    }

    ...

}

9.ImageDecoder

ImageDecoder是用于圖片的解碼,說是解碼這么高大上,其實就是將ImageLoader獲取到的圖片InputStream轉(zhuǎn)換成Bitmap而已,概括來說,就是直接利用BitmapFactory.decodeStream()就能完成該功能。而ImageDecoder則可以理解成將圖片按照需求解碼,主要是按照需求計算采樣率、縮放和旋轉(zhuǎn)圖片。

主要關(guān)注重點(diǎn):

  1. ImageDecoder只向外提供了一個解碼圖片的接口方法decode(ImageDecodingInfo decodingInfo)
  2. ImageDecodingInfo是一個存儲相關(guān)的解碼參數(shù)的信息類,跟ImageLoadingInfo一樣只是用于存儲配置參數(shù),并不工作,只是在圖片解碼的過程中提供相應(yīng)的參數(shù),這些參數(shù)對解碼來說都是必不可少的
  3. 根據(jù)ImageDecodingInfo對象獲取到設(shè)置圖片的采樣率、縮放、旋轉(zhuǎn)等等屬性參數(shù)
  4. 通過decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);獲取到符合采樣率條件的Bitmap
  5. 通過decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, imageInfo.exif.flipHorizontal);獲取到最終符合縮放和旋轉(zhuǎn)條件的Bitmap

總結(jié)一下ImageDecoder的作用就是將ImageLoader獲取到的圖片的輸入流InputStream轉(zhuǎn)換成滿足需求的Bitmap對象
ImageLoader+ImageDecoder便可以通過圖片的Url獲取到滿足需求的Bitmap對象

public class BaseImageDecoder implements ImageDecoder {

    ...

    /**
     * 將圖片從URI中解碼出來,其中圖片是符合需要的采樣率、縮放、旋轉(zhuǎn)等需求的
     */
    @Override
    public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
        Bitmap decodedBitmap;
        ImageFileInfo imageInfo;

        //獲取圖片輸入流
        InputStream imageStream = getImageStream(decodingInfo);
        if (imageStream == null) {
            L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
            return null;
        }
        try {
            //從ImageDecodingInfo對象當(dāng)中提取解碼參數(shù)將其設(shè)置到Options當(dāng)中
            imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
            imageStream = resetStream(imageStream, decodingInfo);
            Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
            //通過將Options獲取到符合條件的Bitmap
            decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
        } finally {
            IoUtils.closeSilently(imageStream);
        }

        if (decodedBitmap == null) {
            L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
        } else {
            //將Bitmap縮放和旋轉(zhuǎn)成滿足需求的Bitmap
            decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
                    imageInfo.exif.flipHorizontal);
        }
        return decodedBitmap;
    }


}

下面是ImageDecodingInfo信息類
可以看出其中跟圖片相關(guān)的屬性有ImagegeSize,ImageScaleType,Options等等,這些屬性決定了圖片被解碼出來的格式,比如大小,規(guī)模,采樣率等等。

public class ImageDecodingInfo {

    private final String imageKey;
    private final String imageUri;
    private final String originalImageUri;
    private final ImageSize targetSize;

    private final ImageScaleType imageScaleType;
    private final ViewScaleType viewScaleType;

    private final ImageDownloader downloader;
    private final Object extraForDownloader;

    private final boolean considerExifParams;
    private final Options decodingOptions;

    public ImageDecodingInfo(String imageKey, String imageUri, String originalImageUri, ImageSize targetSize, ViewScaleType viewScaleType,
                             ImageDownloader downloader, DisplayImageOptions displayOptions) {
        this.imageKey = imageKey;
        this.imageUri = imageUri;
        this.originalImageUri = originalImageUri;
        this.targetSize = targetSize;

        this.imageScaleType = displayOptions.getImageScaleType();
        this.viewScaleType = viewScaleType;

        this.downloader = downloader;
        this.extraForDownloader = displayOptions.getExtraForDownloader();

        considerExifParams = displayOptions.isConsiderExifParams();
        decodingOptions = new Options();
        copyOptions(displayOptions.getDecodingOptions(), decodingOptions);
    }
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容