天天看點

【曆史總結】Android-Universal-Image-Loader源碼分析

前言

​ImageLoader​

​​ 是 ​

​android​

​​ 使用中出現比較早(PS:即的剛接觸安卓項目的時候就用的是這個圖檔加載圖,算算已經快5年了),使用最多的一個開源圖檔加載庫了。随着​

​glide​

​​ , ​

​fresco​

​​ 和 ​

​picasso​

​​等圖檔加載的庫出現,​

​ImageLoader​

​使用變得越來越少。最近在看其他圖檔加載庫的源碼,順便補補之前錯過的一些事情。

代碼倉庫位址:​​Android-Universal-Image-Loader​​

文章目錄

  • ​​前言​​
  • ​​ImageLoader​​
  • ​​Config配置​​
  • ​​視圖​​
  • ​​加載回調​​
  • ​​圖檔展示​​
  • ​​位圖處理​​
  • ​​記憶體緩存​​
  • ​​圖檔解碼器​​
  • ​​磁盤緩存​​
  • ​​網絡下載下傳​​
  • ​​OkHttp網絡下載下傳​​
  • ​​ImageLoader​​
  • ​​圖檔加載引擎​​
  • ​​處理和展示圖檔任務​​
  • ​​加載和展示圖檔任務​​
  • ​​run(記憶體緩存)​​
  • ​​加載圖檔​​
  • ​​緩存圖檔到磁盤​​
  • ​​其他​​

ImageLoader

【曆史總結】Android-Universal-Image-Loader源碼分析

這個是 ​

​ImageLoader​

​​ 的架構,​

​ImageLader​

​ 圖檔加載庫的主要組成部分都包括在其中。

下邊這幅圖對應的是,組成上面架構的每個部分的對應的類實作:

【曆史總結】Android-Universal-Image-Loader源碼分析
  • ​ImageLoader​

    ​​ :為​

    ​ImageView​

    ​ 下載下傳和展示圖檔的單例;
  • ​DisplayImageOptions​

    ​ : 圖檔展示的配置項(加載中、空url、加載失敗預設圖等);
  • ​ImageLoaderConfiguration​

    ​​ :​

    ​ImageLoader​

    ​ 的配置項;
  • ​ImageAware​

    ​ :表示圖像感覺視圖,該視圖提供了圖像處理和顯示所需的所有屬性和行為;
  • ​ImageLoadingListener​

    ​ :監聽圖檔加載進度,開始、失敗、成功、取消;
  • ​ImageLoaderEngine​

    ​ :執行圖檔下載下傳和展現任務;
  • ​BitmapDisplayer​

    ​​ :展現​

    ​Bitmap​

    ​​ 在​

    ​ImageView​

    ​​ 上的時候可以修改這個​

    ​Bitmap​

    ​ 或添加展示的動畫效果;
  • ​BitmapProcessor​

    ​​ :可以處理原始的​

    ​Bitmap​

    ​ ;
  • ​MemoryCache​

    ​​ :​

    ​Bitmap​

    ​ 記憶體緩存接口;
  • ​DiskCache​

    ​ :磁盤緩存;
  • ​ImageDecoder​

    ​​ :根據​

    ​ImageDecodingInfo​

    ​資訊得到圖檔并根據參數将其轉換為 Bitmap。
  • ​ImageDownloader​

    ​​ :通過​

    ​URI​

    ​ 擷取圖檔;
  • ​DisplayBitmapTask​

    ​ :展示圖檔并進行回調;
  • ​ProcessAndDisplayImageTask​

    ​ :處理圖檔和展現圖檔的任務,用于加載記憶體緩存中的圖檔;
  • ​LoadAndDisplayImagTask​

    ​​ :處理加載和顯示圖像的任務,用于從Internet或檔案系統加載圖像為​

    ​Bitmap​

    ​;

Config配置

初始化配置參數,參數​

​configuration​

​​為​

​ImageLoader​

​的配置資訊,包括圖檔最大尺寸、任務線程池、磁盤緩存、下載下傳器、解碼器等等。

ImageLoaderConfiguration{
    final Resources resources;//上下文環境中的resource
    final int maxImageWidthForMemoryCache;//記憶體緩存最大寬度
    final int maxImageHeightForMemoryCache;//記憶體緩存最大高度
    final int maxImageWidthForDiskCache;//磁盤緩存最大寬度
    final int maxImageHeightForDiskCache;//磁盤緩存最大高度
    //在将圖像儲存到磁盤緩存之前先對其進行調整大小/壓縮處理
    final BitmapProcessor processorForDiskCache;
    final Executor taskExecutor;//自定義圖檔加載和展現的線程池
    final Executor taskExecutorForCachedImages;//自定義展現在磁盤上的圖檔的線程池
    final boolean customExecutor;//是否自定義下載下傳的線程池
    final boolean customExecutorForCachedImages;//是否自定義緩存圖檔的線程池
    //預設核心線程數和線程池容量為3
    final int threadPoolSize;
    //預設的線程優先級低兩級
    final int threadPriority;
    //LIFO,FIFO;預設為先進先出FIFO
    final QueueProcessingType tasksProcessingType;
    //記憶體緩存,預設為MemoryClass的八分之一,3.0之後為LargeMemoryClass的八分之一
    //如果開啟denyCacheImageMultipleSizesInMemory,那麼緩存為FuzzyKeyMemoryCache執行個體,隻判斷圖檔位址不判斷大小,如果相同那麼重新整理緩存
    final MemoryCache memoryCache;
    //LruDiskCache,大小預設存儲為Long.MAX_VALUE,預設最大數量為Long.MAX_VALUE;
    final DiskCache diskCache;
    //通過URI從網絡或檔案系統或應用程式資源中檢索圖像,預設為HttpURLConnection進行網絡下載下傳
    //提供了imageDownloader方法可以自定義,比如使用HttpClient或者OkHttp
    final ImageDownloader downloader;
    //将圖像解碼為Bitmap,将其縮放到所需大小
    final ImageDecoder decoder;
    //包含圖像顯示選項(預設圖設定以及其他預設選項)
    final DisplayImageOptions defaultDisplayImageOptions;
    //網絡禁止下載下傳器,一般不直接應用
    final ImageDownloader networkDeniedDownloader;
    //在慢速網絡上處理下載下傳
    final ImageDownloader slowNetworkDownloader;
}      

還有一個對于某個​

​ImageView​

​​ 進行展示設定的 ​

​DisplayImageOptions​

​ ,配置圖檔顯示的配置項。比如加載前、加載中、加載失敗應該顯示的占位圖檔,圖檔是否需要在磁盤緩存,是否需要在記憶體緩存等。

視圖

講視圖主要是想讓​

​ImageView​

​​ 與 ​

​ImageLoader​

​​ 聯系在一起來,​

​ImageLoader​

​​ 通過 ​

​ImageAware​

​ 接口實作圖檔在視圖上的展現。

public interface ImageAware {
  int getWidth();
  int getHeight();
  ViewScaleType getScaleType();
  View getWrappedView();
  boolean isCollected();
  int getId();
  boolean setImageDrawable(Drawable drawable);
  boolean setImageBitmap(Bitmap bitmap);
}      
  • ImageAware->ViewAware
  • ImageAware->ViewAware->ImageViewAware
  • ImageAware->NonViewAware

其中 ​

​ViewAware​

​​ 是抽象類,是以 ​

​ImageAware​

​​ 隻有 ​

​ImageViewAware​

​​ 和 ​

​NonViewAware​

​ 兩個實作類。

​NonViewAware​

​ 提供處理原始圖像所需的資訊,但不顯示圖像。當使用者隻需要加載和解碼圖像的時候可以使用它。

加載回調

主要進行圖檔加載過程中的事件監聽。

public interface ImageLoadingListener {
    //開始加載
    void onLoadingStarted(String imageUri, View view);
    //加載失敗
    void onLoadingFailed(String imageUri, View view, FailReason failReason);
    //加載完成
    void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);
    //取消加載
    void onLoadingCancelled(String imageUri, View view);
}      

圖檔展示

在​

​ImageAware​

​​中顯示​

​bitmap​

​​ 對象的接口。可在實作中對 ​

​bitmap​

​ 做一些額外處理,比如加圓角、動畫效果。

預設的​

​BitmapDisplay​

​​ 是 ​

​SimpleBitmapDisplayer​

​​ 僅僅實作了加載圖檔的功能,​

​ImageLoader​

​​ 還提供了​

​CircleBitmapDisplayer​

​​ 、​

​FadeInBitmapDisplayer​

​​ 和 ​

​RoundedBitmapDisplayer​

​ 等其他的實作。

public interface BitmapDisplayer {
  void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);
}
public final class SimpleBitmapDisplayer implements BitmapDisplayer {
  @Override
  public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
    imageAware.setImageBitmap(bitmap);
  }
}      

位圖處理

圖檔處理接口。可用于對圖檔預處理(​

​Pre-process​

​​)和後處理(​

​Post-process​

​​ ),這兩個處理器的配置都是在​

​DisplayImageOptions​

​ 進行設定。其中預處理是在圖檔擷取完緩存之前處理,後端處理是指在展示前的處理。

public interface BitmapProcessor {
  Bitmap process(Bitmap bitmap);
}      

記憶體緩存

記憶體緩存的是​

​Bitmap​

​​ ,預設的緩存容器是​

​LruMemoryCache​

​​ 。記憶體緩存的​

​Bitmap​

​ 都是通過資料流解碼生成的。

public interface MemoryCache {
    boolean put(String key, Bitmap value);
    Bitmap get(String key);
    Bitmap remove(String key);
    Collection<String> keys();
    void clear();
}
public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
    if (memoryCacheSize == 0) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        int memoryClass = am.getMemoryClass();
        if (hasHoneycomb() && isLargeHeap(context)) {
            memoryClass = getLargeMemoryClass(am);
        }
        memoryCacheSize = 1024 * 1024 * memoryClass / 8;
    }
    return new LruMemoryCache(memoryCacheSize);
}      

​LruMemoryCache​

​ 是區分size的,如果​

​ImageLoaderConfiguration​

​​ 設定 ​

​denyCacheImageMultipleSizesInMemory​

​​ 那麼緩存為 ​

​FuzzyKeyMemoryCache​

​​ 執行個體,隻判斷圖檔位址不判斷大小,如果相同那麼重新整理緩存。 ​

​FuzzyKeyMemoryCache​

​​ 隻是重寫了​

​MemoryCache​

​​ 的 ​

​put​

​ 方法。

圖檔解碼器

根據​

​ImageDecodingInfo​

​​資訊得到圖檔并根據參數将其轉換為 ​

​Bitmap​

​ 。

public interface ImageDecoder {
  Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
}
public static ImageDecoder createImageDecoder(boolean loggingEnabled) {
  return new BaseImageDecoder(loggingEnabled);
}      

​BaseImageDecoder​

​​ 是​

​ImageLoaderConfiguration​

​預設的解碼器。

public class BaseImageDecoder implements ImageDecoder {
    @Override
    public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
        Bitmap decodedBitmap;
        ImageFileInfo imageInfo;
        //通過ImageDecodingInfo中的資訊擷取資料流,圖檔下載下傳器部分會講怎麼擷取資料流
        InputStream imageStream = getImageStream(decodingInfo);
        if (imageStream == null) {
            L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
            return null;
        }
        try {
            //确定圖檔尺寸和旋轉角度,生成圖檔檔案資訊
            imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
            //資料流的遊标重置
            imageStream = resetStream(imageStream, decodingInfo);
            //生成控制Bitmap進行采樣的Option
            Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
            //将輸入流解碼為位圖
            decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
        } finally {
            IoUtils.closeSilently(imageStream);
        }
        if (decodedBitmap == null) {
            L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
        } else {
            //對Bitmmap進行縮放和旋轉
            decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
                    imageInfo.exif.flipHorizontal);
        }
        return decodedBitmap;
    }
    /****部分代碼省略***/
}      

在解碼的過程中我們之是以要重置遊标,是因為我們在讀頭資訊的時候已經讀出了部分資料,是以這裡要重置遊标得到完整的圖檔資料。

磁盤緩存

本地圖檔緩存,可向本地磁盤緩存儲存圖檔或從本地磁盤讀取圖檔。​

​LruDiskCache​

​​是​

​ImageLoaderConfiguration​

​​預設的磁盤緩存容器。這次緩存的圖檔檔案都是通過​

​InputStream​

​​ 儲存在磁盤上的,實作是通過調用 ​

​save​

​ 方法。

public interface DiskCache {
    File getDirectory();
    File get(String imageUri);
    boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;
    boolean save(String imageUri, Bitmap bitmap) throws IOException;
    boolean remove(String imageUri);
    void close();
    void clear();
}
public static DiskCache createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator,
        long diskCacheSize, int diskCacheFileCount) {
    File reserveCacheDir = createReserveDiskCacheDir(context);
    if (diskCacheSize > 0 || diskCacheFileCount > 0) {
        ///Android/data/[app_package_name]/cache/uil-images
        File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);
        try {
            //緩存目錄,緩存大小,緩存數量,緩存檔案名生成器都不能為空
            return new LruDiskCache(individualCacheDir, reserveCacheDir, diskCacheFileNameGenerator, diskCacheSize,
                    diskCacheFileCount);
        } catch (IOException e) {
            L.e(e);
            // continue and create unlimited cache
        }
    }
    File cacheDir = StorageUtils.getCacheDirectory(context);
    //UnlimitedDiskCache大小沒有限制
    return new UnlimitedDiskCache(cacheDir, reserveCacheDir, diskCacheFileNameGenerator);
}      

網絡下載下傳

擷取​

​Uri​

​​ 對應的 ​

​Stream​

​​ , ​

​extra​

​​ 為輔助的下載下傳器,可以通過​

​DisplayImageOptions​

​​ 得到​

​extraForDownloader​

​​ 。下載下傳主要有​

​http​

​​ 、​

​https​

​​ 、​

​file​

​​ 、​

​content​

​​ 、​

​assets​

​​ 和 ​

​drawable​

​ 。

public interface ImageDownloader {
    InputStream getStream(String imageUri, Object extra) throws IOException;
    /** Represents supported schemes(protocols) of URI. Provides convenient methods for work with schemes and URIs. */
    public enum Scheme {
        HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
        private String scheme;
        private String uriPrefix;
        Scheme(String scheme) {
            this.scheme = scheme;
            uriPrefix = scheme + "://";
        }
        /***部分代碼省略***/
    }
}      

​BaseImageDownloader​

​ 為預設的下載下傳器:内部通過下載下傳資源的類型的不同有着不同的實作。

public class BaseImageDownloader implements ImageDownloader {
    @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);
        }
    }
    /***部分代碼省略***/
}      

看一個從網絡請求中擷取​

​Stream​

​​ 的實作(​

​ImageLoader​

​預設的網絡下載下傳):

public class BaseImageDownloader implements ImageDownloader {
    protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
        //根據imageUri,建立HttpURLConnection對象
        HttpURLConnection conn = createConnection(imageUri, extra);
        int redirectCount = 0;
        //最多重定向請求5次
        while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
            conn = createConnection(conn.getHeaderField("Location"), extra);
            redirectCount++;
        }
        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;
        }
        //如果responseCode不是200那麼關閉請求抛出IO異常
        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());
    }
}      

OkHttp網絡下載下傳

隻需要在進行​

​ImageLoader​

​​配置的時候調用​

​ImageLoaderConfiguration.Builder​

​​的​

​imageDownloader​

​ 方法進行設定。

public class OkHttpImageDownloader extends BaseImageDownloader {
    private OkHttpClient client;
    public OkHttpImageDownloader(Context context, OkHttpClient client) {
        super(context);
        this.client = client;
    }
    @Override
    protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
        Request request = new Request.Builder().url(imageUri).build();
        ResponseBody responseBody = client.newCall(request).execute().body();
        InputStream inputStream = responseBody.byteStream();
        int contentLength = (int) responseBody.contentLength();
        return new ContentLengthInputStream(inputStream, contentLength);
    }
}      

ImageLoader

講完了組成的​

​ImageLoader​

​​ 的一整套圖檔加載流程的沒個部分:網絡下載下傳、磁盤緩存、資料解碼、記憶體緩存、位圖處理、圖檔展示和業務回調。下面我們看看​

​ImageLoader​

​是怎麼将這些部分是怎麼串在一起的。

使用雙重校驗鎖(DCL:double-checked locking)實作單例操作 ​​Java版的7種單例模式​​。

public class ImageLoader {
    private ImageLoaderConfiguration configuration;//圖檔加載配置資訊
    private ImageLoaderEngine engine;//圖檔加載引擎
    private ImageLoadingListener defaultListener = new SimpleImageLoadingListener();//預設的回調監聽
    private volatile static ImageLoader instance;//單例
    /** Returns singleton class instance */
    public static ImageLoader getInstance() {
        if (instance == null) {
            synchronized (ImageLoader.class) {
                if (instance == null) {
                    instance = new ImageLoader();
                }
            }
        }
        return instance;
    }
    //初始化方法
    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);
        }
    }
    /***其他代碼省略***/
}      

上面代碼是 ​

​ImageLoader​

​ 的構造初始化方法,接下分析它加載圖檔時候的調用:

public class ImageLoader {
    public void displayImage(String uri, ImageView imageView) {
            displayImage(uri, new ImageViewAware(imageView), null, null, null);
    }
    //最終加載圖檔的方法
    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);
        }
        //添加預設的空回調
        if (listener == null) {
            listener = defaultListener;
        }
        //添加預設的圖檔展示配置
        if (options == null) {
            options = configuration.defaultDisplayImageOptions;
        }
        //下載下傳位址為空
        if (TextUtils.isEmpty(uri)) {
            engine.cancelDisplayTaskFor(imageAware);//取消對于目前imageAware的展示任務
            listener.onLoadingStarted(uri, imageAware.getWrappedView());//回調展示開始
            //展示配置中有處理為空的url的預設圖
            if (options.shouldShowImageForEmptyUri()) {
                //給imageAware設定這個預設圖
                imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
            } else {
                imageAware.setImageDrawable(null);
            }
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);//回調展示結束
            return;
        }
        //擷取目前需要下載下傳的圖檔的size
        if (targetSize == null) {
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }
        //擷取記憶體緩存的key(url_width_height)
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
        //添加到執行引擎cacheKeysForImageAwares的容器中
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
        listener.onLoadingStarted(uri, imageAware.getWrappedView());//回調展示開始
        //從記憶體中擷取緩存的memoryCacheKey對應的bitmap
        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
        //bitmap不為空,而且沒有被回收
        if (bmp != null && !bmp.isRecycled()) {
            L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
            //如果需要展示加載的進度,預設是不設定BitmapProcessor處理器的
            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 {
                //從記憶體中加載bitmap設定給imageAware,
                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);//送出任務到加載引擎中
            }
        }
    }
    /***其他代碼省略***/
}      

​Imageloader​

​圖檔加載流程叙述:

  1. 校驗配置;
  2. 指派預設值(回調監聽、圖檔展現配置);
  3. 判斷下載下傳位址為空;

    3.1. 取消目前imageAware的圖檔展示任務;

    3.2. 如果圖檔展示配置有url為空的預設處理圖那麼加載預設圖;

  4. 擷取目前需要加載圖的size;
  5. 擷取緩存的key

    5.1. 根據key從記憶體緩存中擷取bitmap,且bitmap有效;

    5.1.1. 如果需要展現加載進度,那麼構造處理圖檔展示任務(ProcessAndDisplayImageTask)并執行(如果展現需要同步那麼直接展示,否則任務送出到線程池);

    5.1.2. 否則直接加載bitmap給目前的imageAware;

    5.2. 如果需要展現加載進度,那麼擷取圖檔展示配置中的加載狀态資源進行展示,準備下一步加載真實圖檔資源;

    5.2.1. 構造加載展示圖檔任務(LoadAndDisplayImageTask)并執行(如果展現需要同步那麼直接展示,否則任務送出到線程池);

圖檔加載引擎

雖然叫做圖檔加載引起,但其實它僅僅隻是一個任務分發處理器,負責分發​

​LoadAndDisplayImageTask​

​​和​

​ProcessAndDisplayImageTask​

​給具體的線程池去執行,以及任務的暫停等操作。

class ImageLoaderEngine {
    final ImageLoaderConfiguration configuration;//圖檔加載配置資訊
    private Executor taskExecutor;//configuration.taskExecutor
    private Executor taskExecutorForCachedImages;//configuration.taskExecutorForCachedImages
    private Executor taskDistributor;//配置設定任務的線程池為newCachedThreadPool
    //imageview的hashcode和下載下傳的key(url_width_height)
    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();
    ImageLoaderEngine(ImageLoaderConfiguration configuration) {
        this.configuration = configuration;
        taskExecutor = configuration.taskExecutor;
        taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;
        taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
    }
    /***其他代碼省略***/
}      

任務送出處理,主要做了不同類型的任務分發給對應的任務執行的線程池:

class ImageLoaderEngine {
    /** Submits task to execution pool */
    //執行從磁盤擷取和網絡上加載圖檔的任務
    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);
    }
    //任務線程池是否關閉,關閉則重新建立
    private void initExecutorsIfNeed() {
        if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) {
            taskExecutor = createTaskExecutor();
        }
        if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages)
                .isShutdown()) {
            taskExecutorForCachedImages = createTaskExecutor();
        }
    }
    //建立任務線程池
    private Executor createTaskExecutor() {
        return DefaultConfigurationFactory
                .createExecutor(configuration.threadPoolSize, configuration.threadPriority,
                configuration.tasksProcessingType);
    }
    /***其他代碼省略***/
}      

從​

​ImageLoader​

​​ 的​

​displayImage​

​​ 方法實作和 ​

​ImageLoaderEngine​

​​ 的任務分發可以看出來,​

​ImageLoader​

​​ 主要有兩種類型的任務 ​

​ProcessAndDisplayImageTask​

​​ 和 ​

​LoadAndDisplayImageTask​

​ 。

處理和展示圖檔任務

final class ProcessAndDisplayImageTask implements Runnable {
    /***部分代碼省略***/
    @Override
    public void run() {
        L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);
        //擷取圖檔展現配置中的圖檔處理器
        BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
        //擷取處理過後的Biamtp
        Bitmap processedBitmap = processor.process(bitmap); 
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
                LoadedFrom.MEMORY_CACHE);
        //如果isSyncLoading那麼調用displayBitmapTask的run方法,否則如果handler不為空切換到主線程執行displayBitmapTask.run
        LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
    }
}      

加載和展示圖檔任務

先看​

​LoadAndDisplayImageTask.runTask​

​ 方法:

static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
    if (sync) {//如果需要同步那麼在目前線程執行
        r.run();
    } else if (handler == null) {//handler為空切換線程到taskDistributor線程池中執行
        engine.fireCallback(r);
    } else {
        handler.post(r);//切換到handler主線程執行
    }
}      

run(記憶體緩存)

final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
    /***部分代碼省略***/
    @Override
    public void run() {
        //如果ImageLoader暫停執行任務(ImageLoader.pause方法被調用),那麼目前線程進入等待被喚醒(ImageLoader.resume方法被調用);
        //否則校驗目前任務是否有效(校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri)
        if (waitIfPaused()) return;
        //是否需要延遲加載(圖檔展示配置中如果delayBeforeLoading時間大于0)
        否則校驗目前任務是否有效(校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri)
        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 {
            //校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
            checkTaskNotActual();
            //先從記憶體緩存中擷取對應的Bitmap
            bmp = configuration.memoryCache.get(memoryCacheKey);
            //如果bitmap被回收或者為空
            if (bmp == null || bmp.isRecycled()) {
                //嘗試加載Bitmap(磁盤、資源、網絡等)
                bmp = tryLoadBitmap();
                //加載失敗直接傳回
                if (bmp == null) return; // listener callback already was fired
                //校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
                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);
                    }
                }
                //是否需要對這個Bitmap進行記憶體緩存
                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);
                }
            }
            //校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
            checkTaskNotActual();
            //檢驗是否目前線程被打斷
            checkTaskInterrupted();
        } catch (TaskCancelledException e) {
            //進行失敗處理
            fireCancelEvent();
            return;
        } finally {
            //釋放鎖
            loadFromUriLock.unlock();
        }
        //執行展示圖檔任務此處和ProcessAndDisplayImageTask任務後的展示邏輯相同
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        runTask(displayBitmapTask, syncLoading, handler, engine);
    }
    /***部分代碼省略***/
}      

任務是否有效:校驗目标​

​ImageAware​

​​是否已經被回收,或者​

​ImageAware​

​​需要加載的​

​uri​

​​已經不是目前的​

​uri​

​(被取消或者被代替)。

  1. 校驗​

    ​ImageLoader​

    ​是否暫停執行任務和目前的任務是否有效;
  2. 是否需要進行延遲加載,延遲加載後校驗目前是否任務有效;
  3. 擷取目前圖檔加載任務的鎖進行上鎖;
  4. 校驗目前是否任務有效後開始進行​

    ​Bitmap​

    ​​擷取;

    4.1 先從記憶體緩存中擷取對應的​​

    ​Bitmap​

    ​​;

    4.2 擷取​​

    ​Bitmap​

    ​​ 為空擷取已經被回收那麼嘗試加載​

    ​Bitmap​

    ​​;

    4.2.1​​

    ​Bitmap​

    ​​加載失敗直接傳回;

    4.2.2 校驗目前是否任務有效;

    4.2.3 檢驗是否目前線程被打斷;

    4.2.4 根據圖檔展示配置項是否要進行儲存前處理;

    4.2.5 是否需要對這個​​

    ​Bitmap​

    ​​進行記憶體緩存;

    4.3 根據圖檔展示配置項是否要進行展示前處理

    4.4 校驗目前是否任務有效;

    4.5 檢驗是否目前線程被打斷;

  5. 釋放鎖;
  6. 執行展示圖檔任務;

加載圖檔

final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
    /***部分代碼省略***/
    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;
                //校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
                checkTaskNotActual();
                bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));//進行圖檔解碼
            }
            //bitmap為空,或者長寬小于0重新進行資料擷取
            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());
                    }
                }
                //校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
                checkTaskNotActual();
                bitmap = decodeImage(imageUriForDecoding);//進行圖檔解碼
                //bitmap為空,或者長寬小于0進行異常處理
                if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                    fireFailEvent(FailType.DECODING_ERROR, null);
                }
            }
        } catch (){
            /***異常處理省略***/
        }
        return bitmap;
    }
    /***部分代碼省略***/
}      

緩存圖檔到磁盤

final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
    /***部分代碼省略***/
    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 {
        //通過download擷取資料流
        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);
            }
        }
    }
    //解碼圖像檔案,壓縮并重新儲存(會覆寫之前的檔案)
    private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
        boolean saved = false;
        //擷取磁盤緩存的檔案
        File targetFile = configuration.diskCache.get(uri);
        if (targetFile != null && targetFile.exists()) {
            ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
            //生成新的配置
            DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options)
                    .imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();
            ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
                    Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
                    getDownloader(), specialOptions);
            //對圖像檔案做解碼
            Bitmap bmp = decoder.decode(decodingInfo);
            //壓縮檔案
            if (bmp != null && configuration.processorForDiskCache != null) {
                L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
                bmp = configuration.processorForDiskCache.process(bmp);
                if (bmp == null) {
                    L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
                }
            }
            //重新儲存,覆寫之前的uri對應的緩存檔案
            if (bmp != null) {
                saved = configuration.diskCache.save(uri, bmp);
                bmp.recycle();
            }
        }
        return saved;
    }
    /***部分代碼省略***/
}      

其他

  • 取消目前​

    ​imageview​

    ​對應的任務
public void cancelDisplayTask(ImageView imageView) {
    engine.cancelDisplayTaskFor(new ImageViewAware(imageView));
}      
  • 拒絕或允許​

    ​ImageLoader​

    ​從網絡下載下傳圖像
public void denyNetworkDownloads(boolean denyNetworkDownloads) {
    engine.denyNetworkDownloads(denyNetworkDownloads);
}      
  • 設定​

    ​ImageLoader​

    ​​是否使用​

    ​FlushedInputStream​

    ​進行網絡下載下傳的選項
public void handleSlowNetwork(boolean handleSlowNetwork) {
    engine.handleSlowNetwork(handleSlowNetwork);
}      
  • 暫停ImageLoader。在ImageLoader#resume恢複之前,不會執行所有新的“加載和顯示”任務。
  • 已經運作的任務不會暫停。
public void pause() {
    engine.pause();
}      
  • 恢複等待的“加載和顯示”任務
public void resume() {
    engine.resume();
}      
  • 取消所有正在運作和計劃的顯示圖像任務
  • 還可以繼續使用​

    ​ImageLoader​

public void stop() {
    engine.stop();
}      
  • 取消所有正在運作和計劃的顯示圖像任務
  • 銷毀所有配置,重新使用ImageLoader需要進行初始化
public void destroy() {
    if (configuration != null) L.d(LOG_DESTROY);
    stop();
    configuration.diskCache.close();
    engine = null;
    configuration = null;
}      
  • 為了更友好的使用者體驗,在清單滑動過程中可以暫停加載(調用​

    ​pause​

    ​​和​

    ​resume​

    ​);
  • RGB_565代替ARGB_8888,減少占用記憶體;
  • 使用​

    ​memoryCache(new WeakMemoryCache())​

    ​​ 将記憶體中的​

    ​Bitmap​

    ​ 變為軟引用;

文章到這裡就全部講述完啦,若有其他需要交流的可以留言哦!!

繼續閱讀