美文网首页
带你细细品读Android-Universal-Image-Lo

带你细细品读Android-Universal-Image-Lo

作者: WHOKNOWME | 来源:发表于2016-11-10 10:57 被阅读107次

    首先,本人非常钦佩和仰慕 Android-Universal-Image-Loader 的作者:Sergey Tarasevich

    关于风:
    自己从没有写过类似源码品读这样的博客,瞬间觉得有点压力和激动。一方面是这么一个写的如此精妙和伟大的库,让我等小辈去评头论足,实在显得不够尊重。另一方面则是,如此一个广泛使用的,从2011年就面世的库,一旦自己能力不够,导致品读的不到位,抑或是误导了广大的用户,这个责任就难当了。所以此时此刻,自己的心情是忐忑的,是惶恐的,是复杂和纠结的。

    在此,先感谢 Sergey Tarasevich,再感谢各位读者。

    品读这么伟大的库,一篇博客是不够的,那么两篇?也是不够的!废话少说!
    首先抛出一个问题:如何阅读一个开源库?从什么地方入手?
    我的思路是从如何使用方面入手,比如Universal-Image-Loader库的使用方式:

    1.在App的入口处全局配置ImageLoader.

    public static void initImageLoader(Context context) {
        // This configuration tuning is custom. You can tune every option, you may tune some of them,
        // or you can create default configuration by
        //  ImageLoaderConfiguration.createDefault(this);
        // method.
        ImageLoaderConfiguration.Builder config = new 
            ImageLoaderConfiguration.Builder(context);
        config.threadPriority(Thread.NORM_PRIORITY - 2);
        config.denyCacheImageMultipleSizesInMemory();//拒绝在内存中缓存2种size的图片策略
        config.diskCacheFileNameGenerator(new Md5FileNameGenerator());//磁盘缓存文件名的产生方案,你选择采用其他策略
        config.diskCacheSize(50 * 1024 * 1024); // 磁盘缓存大小 50 MiB
        config.tasksProcessingOrder(QueueProcessingType.LIFO);//配置任务队列的执行方式
        config.writeDebugLogs(); // Remove for release app
    
        // Initialize ImageLoader with configuration.
        ImageLoader.getInstance().init(config.build());
    }
    

    ImageLoader的可配置的信息非常丰富,上面所列的只是一部分。这种全局配置的策略是很值推广,ImageLoader本身有自己的默认策略,但是默认毕竟是默认的,无法满足不同业务的需求。所以我们在开发一款工具类开源库的时候,可以将这种插件式的设计思想引入到你的库当中。

    2.图片显示调用:ImageLoader.getInstance().displayImage()

    这个方法的背后隐藏了这个库的所有秘密。ImageLoader.getInstance()采用单例的设计模式:

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

    双重检查的单例模式,单例模式的实现方法之一,保证全局只有一个实例存在,节约了频繁创建和销毁对象时系统资源的开销,提高了我们系统的性能。
    获取ImageLoader的实例之后,就开始调用displayImage()方法去显示图片了,从源码中我们可以看到,在ImageLoader这个类中多次重载了displayImage()的方法。但是核心的方法往往只有一个,而且这个核心的方法也非常有意思,我们一起来看看。

    public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
                ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
            checkConfiguration();
            
            ... //此处省略部分源码
            
            if (TextUtils.isEmpty(uri)) {//过滤url为空的情况
                engine.cancelDisplayTaskFor(imageAware);
                listener.onLoadingStarted(uri, imageAware.getWrappedView());
                if (options.shouldShowImageForEmptyUri()) {
                    imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));//显示你配置url为空时显示的图片
                } 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);//根据图片url生产一个键
            //将imageView的hashcode与url产生的键做一个map映射
            engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
    
            listener.onLoadingStarted(uri, imageAware.getWrappedView());
            //获取内存中缓存的bitmap
            Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
            if (bmp != null && !bmp.isRecycled()) {//如果内存中存在bmp
                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 {
                ... //此处省略部分源码
                //下面是处理图片的网络下载或者加载本地磁盘缓存图片的逻辑
                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加载图片的主要逻辑。后面的几篇博客我会继续解读ImageLoader源码。会有这么几个方面:

    1. ImageLoaderEngine 的源码解读
    2. BaseImageDownloader 的源码解读
    3. ImageLoader 的内存缓存策略
    4. ImageLoader 的磁盘缓存策略
    5. ImageLoader 的接口编程思想

    有问题欢迎留言!

    相关文章

      网友评论

          本文标题:带你细细品读Android-Universal-Image-Lo

          本文链接:https://www.haomeiwen.com/subject/nxvouttx.html