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

概述

本文主要是用来探究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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值