Android开源框架Universal-Image-Loader源码解析

来源:互联网 发布:如何看待陈光标知乎 编辑:程序博客网 时间:2024/04/30 07:07

概述

本文主要是用来探究Universal-Image-Loader内部源码构造,进一步了解作者的设计思想,当然不了解里面的代码结构,也并不会影响对这个开源框架的运用,关于如何使用可以关注我的上一篇文章Android开源框架Universal-Image-Loader应用。

源码阅读

关于源码阅读的方式,我采用框架使用的步骤顺序,这样便能更方便追踪代码的运行逻辑。

1.关于ImageLoaderConfiguration的配置

我们先从运用的第一步入手,在使用该框架加载网络图片之前(当然也可以加载本地图片资源),需要设置其中的全局变量,该类的名称为:ImageLoaderConfiguration。
在上一篇文章中,我们知道该类中可以使用框架中默认的配置,调用方式为:ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(context);
进去方法中的代码:

    /**     * Creates default configuration for {@link ImageLoader} <br />     * <b>Default values:</b>     * <ul>     * <li>maxImageWidthForMemoryCache = device's screen width</li>     * <li>maxImageHeightForMemoryCache = device's screen height</li>     * <li>maxImageWidthForDiscCache = unlimited</li>     * <li>maxImageHeightForDiscCache = unlimited</li>     * <li>threadPoolSize = {@link Builder#DEFAULT_THREAD_POOL_SIZE this}</li>     * <li>threadPriority = {@link Builder#DEFAULT_THREAD_PRIORITY this}</li>     * <li>allow to cache different sizes of image in memory</li>     * <li>memoryCache = {@link DefaultConfigurationFactory#createMemoryCache(int)}</li>     * <li>discCache = {@link UnlimitedDiscCache}</li>     * <li>imageDownloader = {@link DefaultConfigurationFactory#createImageDownloader(Context)}</li>     * <li>imageDecoder = {@link DefaultConfigurationFactory#createImageDecoder(boolean)}</li>     * <li>discCacheFileNameGenerator = {@link DefaultConfigurationFactory#createFileNameGenerator()}</li>     * <li>defaultDisplayImageOptions = {@link DisplayImageOptions#createSimple() Simple options}</li>     * <li>tasksProcessingOrder = {@link QueueProcessingType#FIFO}</li>     * <li>detailed logging disabled</li>     * </ul>     * */    public static ImageLoaderConfiguration createDefault(Context context) {        return new Builder(context).build();    }

上述方法是ImageLoaderConfiguration的静态方法,主要是用来实例化静态内部类Builder,并调用build方法。我们继续进入build方法中查看。

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

从方法名称中我们可以看出,里面的方法使用来给Builder类中的变量设置默认值,进入这个方法中

private void initEmptyFiledsWithDefaultValues() {            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 (discCache == null) {                if (discCacheFileNameGenerator == null) {                    discCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();                }                discCache = DefaultConfigurationFactory.createDiscCache(context, discCacheFileNameGenerator, discCacheSize, discCacheFileCount);            }            if (memoryCache == null) {                memoryCache = DefaultConfigurationFactory.createMemoryCache(memoryCacheSize);            }            if (denyCacheImageMultipleSizesInMemory) {                memoryCache = new FuzzyKeyMemoryCache<String, Bitmap>(memoryCache, MemoryCacheUtil.createFuzzyKeyComparator());            }            if (downloader == null) {                downloader = DefaultConfigurationFactory.createImageDownloader(context);            }            if (decoder == null) {                decoder = DefaultConfigurationFactory.createImageDecoder(loggingEnabled);            }            if (defaultDisplayImageOptions == null) {                defaultDisplayImageOptions = DisplayImageOptions.createSimple();            }        }

我们首先分析taskExecutor的初始化,继续进入createExecutor的方法中,如下:

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

从传入的参数threadPoolSize=3,得知创建一个大小为3的工作线程池。同理,taskExecutorForCachedImages同样是一个大小为3的线程池。
接着分析discCacheFileNameGenerator的初始化,这个类主要是为磁盘高速缓存中生成文件名称,进入代码中分析:

    /** Creates {@linkplain HashCodeFileNameGenerator default implementation} of FileNameGenerator */    public static FileNameGenerator createFileNameGenerator() {        return new HashCodeFileNameGenerator();    }/** * Names image file as image URI {@linkplain String#hashCode() hashcode} *  * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.3.1 */    public class HashCodeFileNameGenerator implements FileNameGenerator {    @Override    public String generate(String imageUri) {        return String.valueOf(imageUri.hashCode());    }}

从上面代码中可以看出,该文件的名称主要是从传入的图片地址的的hashcode得来,确保文件的名称的唯一性。
完成discCacheFileNameGenerator的初始化之后,就开始磁盘缓存对象discCache,接着看代码:

    /** Creates default implementation of {@link DisckCacheAware} depends on incoming parameters */    public static DiscCacheAware createDiscCache(Context context, FileNameGenerator discCacheFileNameGenerator, int discCacheSize, int discCacheFileCount) {        if (discCacheSize > 0) {            File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);            return new TotalSizeLimitedDiscCache(individualCacheDir, discCacheFileNameGenerator, discCacheSize);        } else if (discCacheFileCount > 0) {            File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);            return new FileCountLimitedDiscCache(individualCacheDir, discCacheFileNameGenerator, discCacheFileCount);        } else {            File cacheDir = StorageUtils.getCacheDirectory(context);            return new UnlimitedDiscCache(cacheDir, discCacheFileNameGenerator);        }    }        /**     * Returns application cache directory. Cache directory will be created on SD card     * <i>("/Android/data/[app_package_name]/cache")</i> if card is mounted. Else - Android defines cache directory on     * device's file system.     *      * @param context Application context     * @return Cache {@link File directory}     */    public static File getCacheDirectory(Context context) {        File appCacheDir = null;        if (Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {            appCacheDir = getExternalCacheDir(context);        }        if (appCacheDir == null) {            appCacheDir = context.getCacheDir();        }        return appCacheDir;    }        private static File getExternalCacheDir(Context context) {        File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");        File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");        if (!appCacheDir.exists()) {            if (!appCacheDir.mkdirs()) {                L.w("Unable to create external cache directory");                return null;            }            try {                new File(appCacheDir, ".nomedia").createNewFile();            } catch (IOException e) {                L.i("Can't create \".nomedia\" file in application external cache directory");            }        }        return appCacheDir;    }

由于我们这里传入的discCacheSize和discCacheFileCount都为0,所以首先去查看该设备是否存在SD卡,如果存在则生成SD卡缓存目录/Android/data/[app_package_name]/cache,否则使用应用程序内部的缓存目录
data/data/[app_package_name]/cache。接着初始化对象磁盘缓存对象UnlimitedDiscCache。
继续看memoryCache初始化,看代码:

    /**     * Creates default implementation of {@link MemoryCacheAware} depends on incoming parameters: <br />     * {@link LruMemoryCache} (for API >= 9) or {@link LRULimitedMemoryCache} (for API < 9).<br />     * Default cache size = 1/8 of available app memory.     */    public static MemoryCacheAware<String, Bitmap> createMemoryCache(int memoryCacheSize) {        if (memoryCacheSize == 0) {            memoryCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);        }        MemoryCacheAware<String, Bitmap> memoryCache;        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {            memoryCache = new LruMemoryCache(memoryCacheSize);        } else {            memoryCache = new LRULimitedMemoryCache(memoryCacheSize);        }        return memoryCache;    }

从上面我们可以看出内存缓存使用的LRU缓存策略,缓存大小设置为app可使用内存的1/8大小。
继续看downloader对象的初始化,看代码:

    /** Creates default implementation of {@link ImageDownloader} - {@link BaseImageDownloader} */    public static ImageDownloader createImageDownloader(Context context) {        return new BaseImageDownloader(context);    }

得知实例化对象为BaseImageDownloader对象。看一下内部重要的方法:

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

从上得知,该类可以读取各个目录下的图片资源,并转化为流,不仅仅是网络图片。
继续看decoder对象,代码如下:

    /** Creates default implementation of {@link ImageDecoder} - {@link BaseImageDecoder} */    public static ImageDecoder createImageDecoder(boolean loggingEnabled) {        return new BaseImageDecoder(loggingEnabled);    }

上面直接初始化对象BaseImageDecoder,继续看defaultDisplayImageOptions的类初始化,代码:

    /**     * Creates options appropriate for single displaying:     * <ul>     * <li>View will <b>not</b> be reset before loading</li>     * <li>Loaded image will <b>not</b> be cached in memory</li>     * <li>Loaded image will <b>not</b> be cached on disc</li>     * <li>{@link ImageScaleType#IN_SAMPLE_POWER_OF_2} decoding type will be used</li>     * <li>{@link Bitmap.Config#ARGB_8888} bitmap config will be used for image decoding</li>     * <li>{@link SimpleBitmapDisplayer} will be used for image displaying</li>     * </ul>     *      * These option are appropriate for simple single-use image (from drawables or from Internet) displaying.     */    public static DisplayImageOptions createSimple() {        return new Builder().build();    }    /** Builds configured {@link DisplayImageOptions} object */    public DisplayImageOptions build() {        return new DisplayImageOptions(this);    }

看以看出,直接创建一个DisplayImageOptions类,所有属性使用Builder中初始化的赋值。
方法执行到这里,就结束了。以上的属性赋值都是针对ImageLoaderConfiguration内部静态类Builder的属性的操作。完成对Builder中属性初始化之后,接着执行new ImageLoaderConfiguration(this),将Builder自身传进构造方法中,看一下代码执行:

    private ImageLoaderConfiguration(final Builder builder) {        context = builder.context;        maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;        maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;        maxImageWidthForDiscCache = builder.maxImageWidthForDiscCache;        maxImageHeightForDiscCache = builder.maxImageHeightForDiscCache;        imageCompressFormatForDiscCache = builder.imageCompressFormatForDiscCache;        imageQualityForDiscCache = builder.imageQualityForDiscCache;        taskExecutor = builder.taskExecutor;        taskExecutorForCachedImages = builder.taskExecutorForCachedImages;        threadPoolSize = builder.threadPoolSize;        threadPriority = builder.threadPriority;        tasksProcessingType = builder.tasksProcessingType;        discCache = builder.discCache;        memoryCache = builder.memoryCache;        defaultDisplayImageOptions = builder.defaultDisplayImageOptions;        loggingEnabled = builder.loggingEnabled;        downloader = builder.downloader;        decoder = builder.decoder;        customExecutor = builder.customExecutor;        customExecutorForCachedImages = builder.customExecutorForCachedImages;        networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader);        slowNetworkDownloader = new SlowNetworkImageDownloader(downloader);        reserveDiscCache = DefaultConfigurationFactory.createReserveDiscCache(context);    }

从上面代码知道,在这个构造方法中,将传入的Builder的属性一一赋值给ImageLoaderConfiguration对象的属性,从而完成了ImageLoaderConfiguration的初始化。该类内部采用的是一种建造者模式的设计模式。至此完成的ImageLoaderConfiguration配置初始化。
上面的操作就完成了ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this)这一步;
还有不少用户根据自己项目的需求配置ImageLoaderConfiguration的参数,比如:

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)                .threadPriority(Thread.NORM_PRIORITY - 2)                .denyCacheImageMultipleSizesInMemory()                .diskCacheFileNameGenerator(new Md5FileNameGenerator())                .diskCacheSize(50 * 1024 * 1024)// 50 MiB                .tasksProcessingOrder(QueueProcessingType.LIFO)                .writeDebugLogs() // Remove for release app                .build();

上面这种形式其实与createDefault的方法差别不大,它主要是正对一些属性进行定制化,其他没有被设置的属性还是会继续调用系统默认设置的属性。

2.配置初始化生效

完成上述的配置初始化后,就要让配置生效。执行代码为:ImageLoader.getInstance().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) {            if (configuration.loggingEnabled) L.d(LOG_INIT_CONFIG);            engine = new ImageLoaderEngine(configuration);            this.configuration = configuration;        } else {            L.w(WARNING_RE_INIT_CONFIG);        }    }

从代码中看出来,源码ImageLoader对象持有ImageLoaderConfiguration的引用,这个方法的作用就是将ImageLoaderConfiguration的实例赋值到ImageLoader中configuration的属性。这个方法比较简单。

3.显示图片

完成上面两步之后,开始最后的步骤了,就是将图片资源显示到ImageView控件上来。使用的方法为:ImageLoader.getInstance().displayImage(url, imageView, options, listener );

上代码:

    /**     * Adds display image task to execution pool. Image will be set to ImageView when it's turn.<br />     * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call     *      * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")     * @param imageView {@link ImageView} which should display image     * @param options {@linkplain DisplayImageOptions Display image options} for image displaying. If <b>null</b> -     *            default display image options     *            {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from     *            configuration} will be used.     * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on UI     *            thread.     *      * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before     * @throws IllegalArgumentException if passed <b>imageView</b> is null     */    public void displayImage(String uri, ImageView imageView, DisplayImageOptions options, ImageLoadingListener listener) {        checkConfiguration();        if (imageView == null) {            throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);        }        if (listener == null) {            listener = emptyListener;        }        if (options == null) {            options = configuration.defaultDisplayImageOptions;        }        if (uri == null || uri.length() == 0) {            engine.cancelDisplayTaskFor(imageView);            listener.onLoadingStarted(uri, imageView);            if (options.shouldShowImageForEmptyUri()) {                imageView.setImageResource(options.getImageForEmptyUri());            } else {                imageView.setImageBitmap(null);            }            listener.onLoadingComplete(uri, imageView, null);            return;        }        ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageView, configuration.maxImageWidthForMemoryCache,                configuration.maxImageHeightForMemoryCache);        String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);        engine.prepareDisplayTaskFor(imageView, memoryCacheKey);        listener.onLoadingStarted(uri, imageView);        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);        if (bmp != null && !bmp.isRecycled()) {            if (configuration.loggingEnabled) L.i(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);            if (options.shouldPostProcess()) {                ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener,                        engine.getLockForUri(uri));                ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo, options.getHandler());                engine.submit(displayTask);            } else {                options.getDisplayer().display(bmp, imageView);                listener.onLoadingComplete(uri, imageView, bmp);            }        } else {            if (options.shouldShowStubImage()) {                imageView.setImageResource(options.getStubImage());            } else {                if (options.isResetViewBeforeLoading()) {                    imageView.setImageBitmap(null);                }            }            ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, engine.getLockForUri(uri));            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo, options.getHandler());            engine.submit(displayTask);        }    }

上面的代码中imageView就是你用来显示图片的控件,不能为空。listener如果为空,就会使用SimpleImageLoadingListener的实例化对象。看一下其中的代码:

/** * A convenient class to extend when you only want to listen for a subset of all the image loading events. This * implements all methods in the {@link ImageLoadingListener} but does nothing. *  * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.4.0 */public class SimpleImageLoadingListener implements ImageLoadingListener {    @Override    public void onLoadingStarted(String imageUri, View view) {        // Empty implementation    }    @Override    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {        // Empty implementation    }    @Override    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {        // Empty implementation    }    @Override    public void onLoadingCancelled(String imageUri, View view) {        // Empty implementation    }}

从上面我们知道,该类也是继承自ImageLoadingListener接口,并重写了接口的方法,方法提为空。接着如果options为空的化,就会调用之前configuration初始化时的默认配置。接着判断uri是否为空,如果为空,则取消线程中为该imageView显示图片的任务。直接将imageForEmptyUri中的图片传入其中(如果imageForEmptyUri不等于0的话),如果imageForEmptyUri为0,就直接设置imageView为空,跳出该方法。
如果上述条件都不符合的话,方法继续执行,定义imageview的大小,生成imageview在内存缓存中的key,然后通过key值,得到bitmap对象。如果bitmap不为空,生成一个显示任务,控制线程池提交该任务。至此便完成了imageView的图片显示功能。

以上就是Universal-Image-Loader框架的大体思路,里面涉及的知识点非常多,无法面面俱到,初次分析有很多的不足之处,还是要多多学习大牛们的源码来开阔自身的眼界,努力提高自己代码的设计能力,路漫漫其修远兮。

1 0
原创粉丝点击