62. ImageLoader源代码-流程分析

栏目: Android · 发布时间: 6年前

内容简介:根据Github上面的注释,ImageLoader是一个强大的,灵活的,高度可定制化的图片加载,缓存已经展示的框架。它提供了大量的可配置选项,可以很好的控制图片的加载和缓存进度。

一. ImageLoader简介

Android library #1 on GitHub. UIL aims to provide a powerful, flexible and highly customizable instrument for image loading, caching and displaying. It provides a lot of configuration options and good control over the image loading and caching process.

根据Github上面的注释,ImageLoader是一个强大的,灵活的,高度可定制化的图片加载,缓存已经展示的框架。它提供了大量的可配置选项,可以很好的控制图片的加载和缓存进度。

1.1 Github源代码地址

https://github.com/nostra13/Android-Universal-Image-Loader

1.2 主要类及其功能介绍

类名 意义
ImageLoader ImageLoader的主要操作入口类,比如初始化,请求加载图片。
ImageLoaderEngine ImageLoader的发动机,包含几个Executor线程池,可以执行各种任务,有些Executor可以在configuration中配置。
ImageViewAware 传入图片控件ImageView的包装,对ImageView弱引用(防止内存泄漏),封装了一些方法,可以更加方便的操作ImageView,比如获取宽高,设置图片显示等。
DisplayImageOptions 请求显示图片时的参数,比如默认图片,失败图片,是否使用内存缓存等等
ImageLoadingListener 图片加载的监听器,比如onLoadingStarted,onLoadingComplete
MemoryCache MemoryCache是图片内存缓存的一个接口,包括多种实现机制,比如Lru, FIFO, LargestLimited等
DiskCache 图片磁盘缓存接口,包括多种缓存命名算法,比如md5,hashcode等
ImageLoadingInfo 内存中没有找到图片,准备去其他地方找图片的时候,为了便于操作封装的对象,比如图片uri,memorykey, imageLoadinglistener,progressListener, loadFromUriLock
LoadedFrom 枚举类型,表明图片从哪里获取,包括3种类型 NETWORK(网络), DISC_CACHE(磁盘,sd卡), MEMORY_CACHE(内存)
ImageLoaderConfiguration 非常重要的对象,在Application中初始化,包含了MemoryCache,DiskCache,ImageDownloader,ImageDecoder等
ImageDownloader 图片下载接口,有些实现子类,比如BaseImageDownloader,SlowNetworkImageDownloader,NetworkDeniedImageDownloader
BaseImageDownloader 基本的图片下载类,支持网络,assets, content, drawable等图片获取
SlowNetworkImageDownloader 底网速下图片获取
BitmapDisplayer 图片显示抽象类,包括各种图片显示效果,比如最普通的显示图片,圆角图片显示等

1.3 代码包及其含义

62. ImageLoader源代码-流程分析

包名 作用
com.nostra13.universalimageloader.cache.disc 磁盘缓存命名和存储的算法实现,比如md5和hashcode名称,限制使用时间存储等
com.nostra13.universalimageloader.cache.memory 内存缓存算法的实现类,包括先进先出,Lru等算法
com.nostra13.universalimageloader.core ImageLoader的核心代码和主要工作流程类,比如ImageLoader,ImageLoaderConfiguration,ImageLoaderEngine等。
com.nostra13.universalimageloader.core.assist 辅助类,
com.nostra13.universalimageloader.core.decode 解码,比如从磁盘文件解码成Bitmap
com.nostra13.universalimageloader.core.display 图片显示效果类,比如圆角,淡入效果等
com.nostra13.universalimageloader.core.download 图片下载类,支持网络下载图片,文件读取图片,assets图片,drawable,已经contentProvider读取图片
com.nostra13.universalimageloader.core.imageaware ImageView的封装,提供了对ImageView的便捷操作,比如获取ImageView高度宽度,是否被回收等
com.nostra13.universalimageloader.core.listener 监听器,包括图片加载监听,加载进度监听,列表滑动监听
com.nostra13.universalimageloader.core.process 外放给调用者处理图片的能力,获取到图片之后,在显示之前,调用者可以设置此监听器,处理图片,比如切割图片。
com.nostra13.universalimageloader.utils 工具类
  1. 4 图片加载序列图

62. ImageLoader源代码-流程分析

二. 简单使用

https://www.cnblogs.com/yimi-yangguang/p/5715350.html

2.1 Application

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
                .memoryCacheExtraOptions(480, 800) // default = device screen ,默认为屏幕宽高 dimensions,内存缓存的最大宽高
                .diskCacheExtraOptions(480, 800, null)//磁盘缓存最大宽高,默认不限制
                .threadPriority(Thread.NORM_PRIORITY - 2) // default //线程优先级
                .denyCacheImageMultipleSizesInMemory() //阻止内存中多尺寸缓存
                .memoryCacheSize(2 * 1024 * 1024) //配置缓存大小
                .memoryCacheSizePercentage(13) // default //缓存百分比
                .diskCacheSize(50 * 1024 * 1024) //磁盘缓存大小,只在使用默认缓存有效
                .diskCacheFileCount(100)  //磁盘缓存文件数,只在使用默认缓存有效
                .writeDebugLogs() //打印调试日志
                .build();
       ImageLoader.getInstance().init(config);//初始化         
   }
}

2.2 加载图片

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView imageView = findViewById(R.id.img_test);

        ImageLoader imageLoader = ImageLoader.getInstance();

        DisplayImageOptions options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.ic_launcher_background) // resource or drawable
                .showImageForEmptyUri(R.drawable.ic_launcher_background) // resource or drawable
                .showImageOnFail(R.drawable.ic_launcher_background) // resource or drawable
                .resetViewBeforeLoading(false)  // default
                .delayBeforeLoading(1000)
                .postProcessor(new BitmapProcessor() {
                    @Override
                    public Bitmap process(Bitmap bitmap) {
                        Log.d("sandy", "process bitmap...");
                        return bitmap;
                    }
                })
                .showImageOnLoading(R.drawable.ic_launcher_foreground)
                .cacheInMemory(false) // default
                .cacheOnDisk(false) // default
                .considerExifParams(false) // default
                .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
                .bitmapConfig(Bitmap.Config.ARGB_8888) // default
                .build();

        imageLoader.displayImage("http://img3.imgtn.bdimg.com/it/u=2200166214,500725521&fm=27&gp=0.jpg",
                imageView, options, new ImageLoadingListener() {
                    @Override
                    public void onLoadingStarted(String imageUri, View view) {
                        Log.d("sandy", "onLoadingStarted imageUri: " + imageUri);
                    }

                    @Override
                    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
                        Log.d("sandy", "onLoadingFailed imageUri: " + imageUri
                        + " failReason: " + failReason);
                    }

                    @Override
                    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                        Log.d("sandy", "onLoadingComplete imageUri: " + imageUri);
                    }

                    @Override
                    public void onLoadingCancelled(String imageUri, View view) {
                        Log.d("sandy", "onLoadingCancelled imageUri: " + imageUri);
                    }
                }, new ImageLoadingProgressListener(){
                    @Override
                    public void onProgressUpdate(String imageUri, View view, int current, int total) {
                        Log.d("sandy", "onProgressUpdate current: " + current + " total: " + total);
                    }
                });
    }
}

三. 流程分析-初始化

按照上面的使用方法,进行ImageLoader的源代码分析,首先看Application的onCreate里面ImageLoader的代码。

3.1 初始化ImageLoaderConfiguration

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
                .memoryCacheExtraOptions(480, 800) // default = device screen ,默认为屏幕宽高 dimensions,内存缓存的最大宽高
                .diskCacheExtraOptions(480, 800, null)//磁盘缓存最大宽高,默认不限制
                .threadPriority(Thread.NORM_PRIORITY - 2) // default //线程优先级
                .denyCacheImageMultipleSizesInMemory() //阻止内存中多尺寸缓存
                .memoryCacheSize(2 * 1024 * 1024) //配置缓存大小
                .memoryCacheSizePercentage(13) // default //缓存百分比
                .diskCacheSize(50 * 1024 * 1024) //磁盘缓存大小,只在使用默认缓存有效
                .diskCacheFileCount(100)  //磁盘缓存文件数,只在使用默认缓存有效
                .writeDebugLogs() //打印调试日志
                .build();

这是一个典型的构造者模式,构造者模式一般适用于属性比较多的场景。

在设置完各种属性后,最后来看看build方法。

3.1.1 build

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

首先会调用initEmptyFieldsWithDefaultValues,如果用户没有设置一些属性,那么就会为他们初始化默认值。

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

最后build方法可以产生出一个ImageLoaderConfiguration对象

return new ImageLoaderConfiguration(this);

得到ImageLoaderConfiguration这个配置对象后,接下来就会利用它来初始化ImageLoader

ImageLoader.getInstance().init(config);//初始化

继续往下分析,首先看ImageLoader.getInstance()

3.2 ImageLoader.getInstance()

ImageLoader imageLoader = ImageLoader.getInstance();

继续看ImageLoader.getInstance()方法

3.2.1 ImageLoader.getInstance

/** Returns singleton class instance */
    public static ImageLoader getInstance() {
        if (instance == null) {
            synchronized (ImageLoader.class) {
                if (instance == null) {
                    instance = new ImageLoader();
                }
            }
        }
        return instance;
    }

getInstance可以看出是一个单例模式,而且是做了效率优化(两层if判断,第一层可以过滤大部分访问,从而减少进入synchronized锁的次数)。

3.3 ImageLoader.init(config)

/**
     * Initializes ImageLoader instance with configuration.<br />
     * If configurations was set before ( {@link #isInited()} == true) then this method does nothing.<br />
     * To force initialization with new configuration you should {@linkplain #destroy() destroy ImageLoader} at first.
     *
     * @param configuration {@linkplain ImageLoaderConfiguration ImageLoader configuration}
     * @throws IllegalArgumentException if <b>configuration</b> parameter is null
     */
    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);
        }
    }

根据注释,我们利用传入的configuration对象初始化ImageLoader,如果这个configuration之前已经被设置过(isInit=true),那么就不会发生什么。

如果想用现在的configuration替换之前的configuration对象,那么需要先调用ImageLoader.destory()方法进行销毁。

如果一些正常的话,就出产生一个ImageLoaderEngine对象,

3.3.1 ImageLoaderEngine

private Executor taskExecutor;
private Executor taskExecutorForCachedImages;
private Executor taskDistributor;

ImageLoaderEngine(ImageLoaderConfiguration configuration) {
        this.configuration = configuration;

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

        taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
    }

ImageLoaderEngine把传入的configuration保存起来,然后包含了几个Executor,用来执行各种异步任务,所以叫做Engine,发动机。

其中taskExecutor和taskExecutorForCachedImages是从configuration里面传进来的,那换句话就是说,是可以我们在configuration中配置的,然后自己也创建了一个taskDistributor 这个Executor。

这样Application里面初始化流程久分析完成了,接下来看Activity里面怎么使用ImageLoader

四. 流程分析-加载图片

先继续贴一段请求加载图片的代码,在Activity的onCreate里面。

首先

  1. 首先设置布局文件
  2. findViewById找到想要展示图片的ImageView控件
  3. 初始化展示的配置,当然也可以不用,ImageLoader可以使用默认的。
  4. 调用imageLoader.displayImage方法请求加载图片,其中可以有图片地址,图片控件,展示配置参数,加载图片的监听器,加载进度监听器。
protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ImageView imageView = findViewById(R.id.img_test);
            ImageLoader imageLoader = ImageLoader.getInstance();
            DisplayImageOptions options = new DisplayImageOptions.Builder()
                    .showImageOnLoading(R.drawable.ic_launcher_background) // resource or drawable
                    .showImageForEmptyUri(R.drawable.ic_launcher_background) // resource or drawable
                    .showImageOnFail(R.drawable.ic_launcher_background) // resource or drawable
                    .resetViewBeforeLoading(false)  // default
                    .delayBeforeLoading(1000)
                    .postProcessor(new BitmapProcessor() {
                        @Override
                        public Bitmap process(Bitmap bitmap) {
                            Log.d("sandy", "process bitmap...");
                            return bitmap;
                        }
                    })
                    .showImageOnLoading(R.drawable.ic_launcher_foreground)
    //                .displayer(new RoundedBitmapDisplayer(5))
                    .cacheInMemory(false) // default
                    .cacheOnDisk(false) // default
                    .considerExifParams(false) // default
                    .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
                    .bitmapConfig(Bitmap.Config.ARGB_8888) // default
                    .build();

            imageLoader.displayImage("http://img3.imgtn.bdimg.com/it/u=2200166214,500725521&fm=27&gp=0.jpg",
                    imageView, options, new ImageLoadingListener() {
                        @Override
                        public void onLoadingStarted(String imageUri, View view) {
                            Log.d("sandy", "onLoadingStarted imageUri: " + imageUri);
                        }

                        @Override
                        public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
                            Log.d("sandy", "onLoadingFailed imageUri: " + imageUri
                            + " failReason: " + failReason);
                        }

                        @Override
                        public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                            Log.d("sandy", "onLoadingComplete imageUri: " + imageUri);
                        }

                        @Override
                        public void onLoadingCancelled(String imageUri, View view) {
                            Log.d("sandy", "onLoadingCancelled imageUri: " + imageUri);
                        }
                    }, new ImageLoadingProgressListener(){
                        @Override
                        public void onProgressUpdate(String imageUri, View view, int current, int total) {
                            Log.d("sandy", "onProgressUpdate current: " + current + " total: " + total);
                        }
                    });
        }

DisplayImageOptions图片展示参数,你可以不指定,也可以指定,表示一些展示的参数,比如默认图片(在网络图片还没有加载出来之前显示),加载失败图片,是否从内存加载,这些后面再分析,不涉及流程的分析。

所以继续看ImageLoader.displayImage(xxx)方法

4.1 ImageLoader.displayImage

public void displayImage(String uri, ImageView imageView, DisplayImageOptions options,
            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        displayImage(uri, new ImageViewAware(imageView), options, listener, progressListener);
    }

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        displayImage(uri, imageAware, options, null, listener, progressListener);
    }

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

        if (targetSize == null) {
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
        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);

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

4.2 ImageViewAware

在displayImage的时候,会使用ImageView对象,初始化一个ImageViewAware对象。

new ImageViewAware(imageView)

ImageViewAware的继承关系如下:

里面主要是做了一个View的弱引用,可以访问传入的ImageView的一些属性,比如高度宽度,设置显示图片等等。

protected Reference<View> viewRef;

之所以搞出一个ImageViewAware,是因为ImageLoader想方便操作传入的ImageView对象。

下面来看displayImage(xx)里面具体的内容

4.3 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);
   }
   if (listener == null) {
      listener = defaultListener;
   }
   if (options == null) {
      options = configuration.defaultDisplayImageOptions;
   }

   ...
}

private void checkConfiguration() {
   if (configuration == null) {
      throw new IllegalStateException(ERROR_NOT_INIT);
   }
}

首先会检查configuration,checkConfiguration,如果configuration==null,那么就会报错。也就是说如果没有调用之前我们说的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);
        }
    }

然后imageAware是否null,如果null,那么就报错

接着检查,listener, options是否为null,如果是null,那么设置为default值。

继续往下面看代码

4.4 加载空图片地址

如果传入的图片地址是null,那么将走下面的的分支

if (TextUtils.isEmpty(uri)) {
   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;
}

上面这段代码做了下面几件事

  1. 清空engine里面取消对这个ImageAware的缓存
  2. 调用listener.onLoadingStarted,如果你传入了listener,那么这个时候就会回调。
  3. 判断options里面是否有为空时候的图片,如果有,那么就把ImageView设置这张图片
  4. 如果没有设置为空时候的图片,那么ImageView设置图片为null.
  5. 然后调用listener.onLoadingComplete方法

4.5 初始化ImageSize

if (targetSize == null) {
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }

public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) {
        int width = imageAware.getWidth();
        if (width <= 0) width = maxImageSize.getWidth();

        int height = imageAware.getHeight();
        if (height <= 0) height = maxImageSize.getHeight();

        return new ImageSize(width, height);
    }

根据传入的ImageView获取高度和宽度,如果没有宽度和高度,就用最大的宽度和高度。

4.6 获取Image Memory key

private static final String URI_AND_SIZE_SEPARATOR = "_";
private static final String WIDTH_AND_HEIGHT_SEPARATOR = "x";

String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);

public static String generateKey(String imageUri, ImageSize targetSize) {
        return new StringBuilder(imageUri).append(URI_AND_SIZE_SEPARATOR).append(targetSize.getWidth()).append(WIDTH_AND_HEIGHT_SEPARATOR).append(targetSize.getHeight()).toString();
    }

产生Image Key的方式是: 图片URL_图片宽度x图片高度

4.7 缓存ImageAware和Image Memory Key

engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
        cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
    }

4.8 通知Listener回调onLoadingStarted

listener.onLoadingStarted(uri, imageAware.getWrappedView());

4.9 根据Memory Key从缓存里面获取图片

Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);

final MemoryCache memoryCache;

public interface MemoryCache {
    /**
     * Puts value into cache by key
     *
     * @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into
     * cache
     */
    boolean put(String key, Bitmap value);

    /** Returns value by key. If there is no value for key then null will be returned. */
    Bitmap get(String key);

    /** Removes item by key */
    Bitmap remove(String key);

    /** Returns all keys of cache */
    Collection<String> keys();

    /** Remove all items from cache */
    void clear();
}

MemoryCache是图片内存缓存的一个接口,包括多种实现机制,比如Lru, FIFO, LargestLimited等,如下图:

62. ImageLoader源代码-流程分析

具体缓存算法可以后续分析

4.10 从内存中获取到图片

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

如果从内存里面获取到了图片,那么就准备显示,又分成两种情况,看业务需不需要重新处理图片,如果图片显示选项设置了shouldPostProcess,就像

DisplayImageOptions options = new DisplayImageOptions.Builder()
                .postProcessor(new BitmapProcessor() {
                    @Override
                    public Bitmap process(Bitmap bitmap) {
                        Log.d("sandy", "process bitmap...");
                        return bitmap;
                    }
                })

那么就产生一个ProcessAndDisplayImageTask

final class ProcessAndDisplayImageTask implements Runnable {
    ...
    @Override
    public void run() {
        L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);

        //获取图片显示选项中的processor对象
        BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
        //回调processor.process来处理图片
        Bitmap processedBitmap = processor.process(bitmap);
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
                LoadedFrom.MEMORY_CACHE);
        LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
    }
}

在ProcessAndDisplayImageTask里面首先获取到BitmapProcessor

BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();

然后回调processor.process方法()

Bitmap processedBitmap = processor.process(bitmap);

然后新建一个DisplayBitmapTask对象,用来显示图片和回调listener的回调方法,如下:

final class DisplayBitmapTask implements Runnable {
    @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);
            displayer.display(bitmap, imageAware, loadedFrom);
            engine.cancelDisplayTaskFor(imageAware);
            listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
        }
    }
}

那如果使用者不需要自己另外处理图片,那么就直接显示好了。

if (options.shouldPostProcess()) {
    ...
} else {
    options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
    listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}

4.11 内存中没有图片

如果内存中没有获取到图片,比如第一次加载图片,那该怎么办呢?

if (bmp != null && !bmp.isRecycled()) {
            ...
        } 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);
            }
        }
  1. 先判断是否需要在加载图片的过程中,显示loading图片,如果是,那么先显示loading图片。
  2. 如果需要在加载前先清空图片,那么就把ImageView显示图片设置成null。
  3. 然后封装出一个ImageLoadingInfo对象,因为要操作的属性是在有点多。
  4. 然后封装出一个LoadAndDisplayImageTask对象,接下来所有的逻辑就封装在LoadAndDisplayImageTask对象里面,我们重点看LoadAndDisplayImageTask的实现。

4.12 LoadAndDisplayImageTask加载和显示图片

final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
    @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();

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

4.12.1 首先判断waitIfPaused,engine是否在暂停状态,如果是,那么就直接return了。

if (waitIfPaused()) return;

4.12.2 然后根据图片显示参数里面判断这次请求是否需要延迟,如果是,那么就延迟指定的时间。

if (delayIfNeed()) return;

private boolean delayIfNeed() {
   if (options.shouldDelayBeforeLoading()) {
      L.d(LOG_DELAY_BEFORE_LOADING, options.getDelayBeforeLoading(), memoryCacheKey);
      try {
         Thread.sleep(options.getDelayBeforeLoading());
      } catch (InterruptedException e) {
         L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
         return true;
      }
      return isTaskNotActual();
   }
   return false;
}

4.12.3 根据Uri进行锁操作

loadFromUriLock.lock();

loadFromUriLock是ImageLoader里面和uri关联的,所以这里锁的话意味着同一个uri,一个时间只能有一个请求。

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

4.12.4 检查ImageView是否被回收

try {
    checkTaskNotActual();
    ...
} catch (TaskCancelledException e) {
    fireCancelEvent();
    return;
} finally {
    loadFromUriLock.unlock();
}

private void checkTaskNotActual() throws TaskCancelledException {
   checkViewCollected();
   checkViewReused();
}

如果ImageView已经被回收,那就没必要去请求图片加载了,直接抛异常,然后catch住,结束task任务

private void checkViewCollected() throws TaskCancelledException {
   if (isViewCollected()) {
      throw new TaskCancelledException();//会被上面的catch捕获
   }
}

4.12.5 再次从内存里面读取图片

bmp = configuration.memoryCache.get(memoryCacheKey);

那为什么需要再次从内存里面读取图片呢,还记得上面这个锁吗?如果是同一个url,发起两个请求,那么就会锁住一个,第二个请求形成等待,在等待完成后,那么正常的话就会从内存里面读取到图片,不要从网络上再次请求。

4.12.6 从内存中成功获取到图片

if (bmp == null || bmp.isRecycled()) {
   ...
} else {
   loadedFrom = LoadedFrom.MEMORY_CACHE;
   L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
}

我们先不分析if分支,if分支是从内存中没有获取到图片,先看else

else里面没有做什么事情,只是简单的打印了一下日志(经过等待从内存中获取图片成功),然后把获取的类型设置成LoadedFrom.MEMORY_CACHE。

LoadedFrom包括3种类型,网络,磁盘,内存。可以参考文章最开始的概念介绍。

4.12.7 如果从内存中没有获取图片

如果从内存里面没有获取到图片,那就走if分支,也就是bmp == null || bmp.isRecycled()

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 {
   ...
}

首先就会看到 bmp = tryLoadBitmap(); 尝试获取Bitmap,这个方法包含的东西很多,我们可以待会再讲。我们先看if分支后面的逻辑。

如果没有获取到bmp,比如指定的Uri是错误,那么就直接返回。

接下来继续判断ImageView是否被回收以及线程是否被中断,如果都通过之后,那么就判断是否需要处理图片,如果需要,那么就回调processor.process(bmp)来处理图片。

bmp = options.getPreProcessor().process(bmp);

最后,判断bmp != null以及是否需要存入内存(调用的地方可以设置是否需要存入内存),如果需要的话(一般都需要),那么就会存入内存缓存。

存入内存缓存后,那么下次就可以从内存中获取了图片了。

那接下来分析tryLoadBitmap()

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

首先是尝试从磁盘获取

File imageFile = configuration.diskCache.get(uri);

如果获取到了,那么读取这个文件,读出Bitmap,同时把类型标记成从磁盘读取。

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

如果从磁盘上面获取是失败,获取磁盘上面没有这个文件,那么bitmap == null

于是,就会从网络上尝试获取图片,如下:

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);
   }
}
  1. 先把类型设置成网络获取 loadedFrom = LoadedFrom.NETWORK;
  2. 然后尝试从网络获取图片并缓存图片到磁盘上-tryCacheImageOnDisk,对于这个方法的名称持保守态度,怪怪的。
  3. 然后解析出bitmap
    bitmap = decodeImage(imageUriForDecoding);

4.12.9 tryCacheImageOnDisk下载图片并存入磁盘

/** @return <b>true</b> - if image was downloaded successfully; <b>false</b> - otherwise */
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
   L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);

   boolean loaded;
   try {
      loaded = downloadImage();
      if (loaded) {
         int width = configuration.maxImageWidthForDiskCache;
         int height = configuration.maxImageHeightForDiskCache;
         if (width > 0 || height > 0) {
            L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
            resizeAndSaveImage(width, height); // TODO : process boolean result
         }
      }
   } catch (IOException e) {
      L.e(e);
      loaded = false;
   }
   return loaded;
}

private boolean downloadImage() throws IOException {
   InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
   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);
      }
   }
}
  1. 首先从网络下载图片downloadImage
  2. 把图片存入磁盘resizeAndSaveImage(width, height);

那从网络下载图片并存入磁盘和内存的代码就分析完了,最后回到上面LoadAndDisplayImageTask.run方法,看看后面的代码。

4.13 给调用者处理图片

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

图片已经拿到了,所以如果需要回调处理图片的话,现在回调一次。

4.14 显示图片

DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);

创建了一个 DisplayBitmapTask来显示图片

final class DisplayBitmapTask implements Runnable {
    private final BitmapDisplayer displayer;
    ...

   @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);
         displayer.display(bitmap, imageAware, loadedFrom);
         engine.cancelDisplayTaskFor(imageAware);
         listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
      }
   }
}

显示图片的时候又封装了一个displayer, displayer封装了图片显示的效果,比如圆角之类的 。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Big Java Late Objects

Big Java Late Objects

Horstmann, Cay S. / 2012-2 / 896.00元

The introductory programming course is difficult. Many students fail to succeed or have trouble in the course because they don't understand the material and do not practice programming sufficiently. ......一起来看看 《Big Java Late Objects》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

SHA 加密
SHA 加密

SHA 加密工具