Universal Image Loader源码分析

Universal Image Loader源码分析


基本组件

UIL是一个强大的,高度定制的图片加载缓存器,支持:

  • 支持任务线程池、下载器、解码器、内存及磁盘缓存、显示选项等等的配置。
  • 包含内存缓存和磁盘缓存两级缓存。
  • 支持多线程,支持异步和同步加载。
  • 支持多种缓存算法、下载进度监听、ListView图片错乱解决等

每一部分都可以从中学到很多内容,本文仅从UIL架构、基本组件、重点知识点等方面出发分析,结构设计图如下:
UIL
整个库主要组件包括:

  • ImageLoader 图片加载器,对外的主要API,采用单例模式,用于图片的加载和显示
  • ImageLoaderEngine LoadAndDisplayImageTask和ProcessAndDisplayImageTask任务分发器,负责分发任务给具体的线程池
  • ImageLoaderConfiguration ImageLoader参数配置项,包括图片最大尺寸、线程池、缓存、下载器、解码器等等
  • Cache(MemoryCache & DiskCache) 详见上两篇文章
  • ImageDownloader 图片下载接口
  • ImageDecoder 将图片转换成Bitmap的接口
  • BitmapProcessor 图片处理接口,可用于对图片预处理和后处理,比如加个水印,Bitmap process(Bitmap bitmap);
  • BitmapDisplayer 在ImageAware中显示bitmap对象的接口,有圆角、动画效果的实现,默认实现SimpleBitmapDisplayer{imageAware.setImageBitmap(bitmap)}
  • DisplayImageOptions 图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在 memory 缓存等
  • ImageAware 需要显示图片的对象的接口,可包装View表示某个需要显示图片的View
  • LoadAndDisplayImageTask 处理并显示图片的Task,实现了Runnable接口,用于从网络、文件系统或内存获取图片并解析,然后调用DisplayBitmapTask在ImageAware中显示图片
  • ProcessAndDisplayImageTask 处理并显示图片的Task,实现了Runnable接口
  • DisplayBitmapTask 显示图片的Task,实现了Runnable接口,必须在主线程调用

ImageLoader

主要函数:
getInstance()获得ImageLoader的单例,int(ImageLoaderConfiguration configuration)初始化UIL配置参数,并初始化ImageLoaderEngine引擎,最主要的调用函数displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)表示异步加载显示,loadImageSync()表示同步加载
displayImage()函数流程图如下:
displayImage

ImageLoaderConfiguration

ImageLoader Configuration is global for application. You should set it once. 内部有个Builder静态类,用于构建参数。
返回默认配置函数:

public static ImageLoaderConfiguration createDefault(Context context) {
    return new Builder(context).build();
}

最终调用的Custom配置build()函数:

public ImageLoaderCofiguration build() {
    //初始化空参数
    initEmptyFieldsWithDefaultValues();
    return new ImagerLoaderConfiguration(this);
}

常规配置项如下:

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

ImageLoaderEngine

ImageLoaderEngine主要负责分发任务,定义了三个Executor:

  • taskExecutor 执行从源获取图片任务的Executor
  • taskExecutorForCachedImages 执行从缓存获取图片任务的Executor
  • taskDistributor 任务分发线程池,任务指LoadAndDisplayImageTask和ProcessAndDisplayImageTask,因为只需要分发给上面的两个Executor去执行任务,不存在较耗时或阻塞操作,所以用无并发数(Int最大值)限制的线程池即可
    任务分发到线程池的函数:
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);
            }
        }
    });
}

task executor和task distributor的创建线程池函数来自DefaultConfigurationFactory类:

/** Creates default implementation of task executor */
public static Executor createExecutor(int threadPoolSize, int threadPriority,
        QueueProcessingType tasksProcessingType) {
    boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;
    BlockingQueue<Runnable> taskQueue =
            lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
    return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
            createThreadFactory(threadPriority, "uil-pool-"));
}

/** Creates default implementation of task distributor */
public static Executor createTaskDistributor() {
    return Executors.newCachedThreadPool(createThreadFactory(Thread.NORM_PRIORITY, "uil-pool-d-"));
}

JAVA线程池

Executor接口规定了线程池的接口,内部只有一个execute(Runnable r)方法,ExecutorService接口继承自Executor,包含了线程池逻辑操作的方法,ThreadPoolExecutor其实是实现了ExecutorService接口的实体类,线程池实际表现为ExecutorService类的一個实例。创建一个ThreadPoolExecutor需要以下参数:

public ThreadPoolExecutor(
        int corePoolSize, //线程池的基本大小,即使有空闲线程也会创建新线程
        int maximumPoolSize, //线程池允许的最大线程数
        long keepAliveTime, //是指线程池中的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率
        TimeUnit unit, //keepAliveTime保持时间的单位
        BlockingQueue<Runnable> workQueue, //任务队列,the queue to use for holding tasks before they are executed. This queue will hold only the runnable tasks submitted by the execute() method,有ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,PriorityBlockingQueue几种
        ThreadFactory threadFactory, //用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字
        RejectedExecutionHandler handler //饱和策略, 当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常
){}

上面的createTaskDistributor()是通过Executors静态工厂创建线程池,它也是一个ThreadPoolExecutor对象,Executors工厂提供了几种常用的线程池场景供使用:

  • newFixedThreadPool:创建一个定长的线程池。达到最大线程数后,线程数不再增长。如果一个线程由于非预期Exception而结束,线程池会补充一个新的线程
  • newCachedThreadPool:创建一个可缓存的线程池。当池长度超过处理需求时,可以回收空闲的线程
  • newSingleThreadPool:创建一个单线程executor
  • newScheduledThreadPool:创建一个定长的线程池,而且支持定时的以及周期性的任务执行。类似于Timer。但是,Timer是基于绝对时间,对系统时钟的改变是敏感的,而ScheduledThreadPoolExecutor只支持相对时间

合理配置线程池,首先要分析任务特性,包括CPU密集型任务、IO密集型任务和混合型任务,任务优先级有高有低,任务执行时间有长有短,任务是否存在依赖性等,CPU密集型任务配置尽可能小的线程,如配置Ncpu+1个线程的线程池。IO密集型任务则由于线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。
执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。
依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。
在UIL中,taskDistributor线程池每执行一个新线程时都会读取下磁盘,属于IO操作,另外图片缓存加载时会大量创建线程,这些线程执行时间很短,存活时间也不必太长,所以设计该线程池为normal优先级的无并发大小限制的线程池,适合many short-lived asynchronous tasks; taskExecutor和taskExecutorForCachedImages涉及网络和磁盘的读取和写入,比较耗时,这里线程数设为3,线程优先级设为4

ImageDownloader

图片下载接口,内部只有一个InputStream getStream(String imageUri, Object extra)函数,和内部定义的枚举Scheme,定义UIL支持的图片来源。BaseImageDownloader是ImageDownloader的具体实现类,得到各种Scheme的InputStream。其中最主要的getStreamFromNetwork()实现如下:

protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
    HttpURLConnection conn = createConnection(imageUri, extra);
    ...
    InputStream imageStream;
    try {
        imageStream = conn.getInputStream;
    } catch (IOExcption e) {
        IoUtils.readAndCloseStream(conn.getErrorStream);
        throw e;
    }
    ...
}

protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
    String encodeUrl = Uri.encode(url, ALLOWED_URI_CHARS);
    HttpURLConnection conn = (HttpURLConnection) new URL(encodeUrl).openConnection();
    conn.setConnectTimeout(connectTimeout);
    connect.setReadTimeout(readTimeout);
    return conn;
}

ImageDecoder

将图片转换成Bitmap的接口,内部只有抽象函数Bitmap decode(ImageDecodingInfo decodingInfo) throw IOException; BaseImageDecoder实现了ImageDecoder

public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
    Bitmap decodedBitmap;
    ImageFileInfo imageInfo;

    InputStream imageStream = getImageStream(decodingInfo);
    ...
    try {
        imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
        imageStream = resetStream(imageStream, decodingInfo);
        Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
        decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
    } finally {
        IoUtils.closeSilently(imageStream);
    }
    ...
}

DisplayImageOptions

同ImageLoaderConfiguration,通过Builder静态内部类完成配置,Display Options(DisplayImageOptions) are local for every display task (ImageLoader.displayImage(…) call)

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

ImageAware

接口主要定义了包装View的函数,ViewAware实现了该接口,利用WeakReference来Wrap View防止内存泄露,Reference viewRef = new WeakReference(view); ImageViewAware继承ViewAware,封装了Android ImageView来显示图片
一个对象被回收要满足两个条件:没有任何应用指向它、GC被运行,Java中引入weak reference,相对于普通的stong reference,针对cache中的reference回收问题
Object car = new Car(); //只要c还指向car object,c就会被GC
WeakReference weakCar = new WeakReference(car); //声明一个weak reference
当要获得weak reference引用的object时,首先需要判断它是否已经被回收weakCar.get();
WeakReference的一个特点是它何时被回收是不可确定的,因为这是由GC运行的不确定性所确定的。所以,一般用weak reference引用的对象是有价值被cache,而且很容易被重新被构建,且很消耗内存的对象

LoadAndDisplayImageTask

run函数,获取图片并显示:

bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
    bmp = tryLoadBitmap();
    ...
    ...
    ...
    if (bmp != null && options.isCacheInMemory()) {
        L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
        configuration.memoryCache.put(memoryCacheKey, bmp);
    }
}
……
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);

流程图如下:
LoadAndDisplayImageTask
tryLoadBitmap()函数:

File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists()) {
    ...
    bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
    ...
    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);
    ...
}

tryCacheImageOnDisk()函数下载图片并存储在磁盘内,根据磁盘缓存图片最长宽高的配置处理图片,loaded = downloadImage();
downloadImage()函数:

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

ProcessAndDisplayImageTask

public void run() {
    ...
    BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
    Bitmap processedBitmap = processor.process(bitmap);
    DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
            LoadedFrom.MEMORY_CACHE);
    LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}

DisplayBitmapTask

public void run() {
    //首先判断imageAware是否被 GC 回收,如果是直接调用取消加载回调接口
    if (imageAware.isCollected()) {
        L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
        listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
    //否则判断imageAware是否被复用,如果是直接调用取消加载回调接口
    } else if (isViewWasReused()) {
        L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
        listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
    //否则调用displayer显示图片,并将imageAware从正在加载的 map 中移除。调用加载成功回调接口
    } else {
        L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
        displayer.display(bitmap, imageAware, loadedFrom);
        engine.cancelDisplayTaskFor(imageAware);
        listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
    }
}
/** Checks whether memory cache key (image URI) for current ImageAware is actual */
private boolean isViewWasReused() {
    String currentCacheKey = engine.getLoadingUriForView(imageAware);
    return !memoryCacheKey.equals(currentCacheKey);
}

对于 ListView 或是 GridView 这类会缓存 Item 的 View 来说,单个 Item 中如果含有 ImageView,在滑动过程中可能因为异步加载及 View 复用导致图片错乱,这里对imageAware是否被复用的判断就能很好的解决这个问题。原因类似:Android ListView 滑动过程中图片显示重复错位闪烁问题原因及解决方案

Listener

  • ImageLoadingListener 图片加载各种时刻的回调接口
  • ImageLoadingProgressListener Image加载进度的回调接口
  • PauseOnScrollListener 可在View滚动过程中暂停图片加载的Listener,实现了OnScrollListener

参考链接:
1. http://www.cnblogs.com/kissazi2/p/3966023.html
2. http://www.tuicool.com/articles/imyueq
3. http://a.codekk.com/detail/Android/huxian99/Android%20Universal%20Image%20Loader%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值