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框架的大体思路,里面涉及的知识点非常多,无法面面俱到,初次分析有很多的不足之处,还是要多多学习大牛们的源码来开阔自身的眼界,努力提高自己代码的设计能力,路漫漫其修远兮。
- Android开源框架Universal-Image-Loader源码解析
- Android 开源框架Universal-Image-Loader完全解析(三)---源码解析
- Android 开源框架Universal-Image-Loader完全解析
- Android 开源框架Universal-Image-Loader完全解析
- Android 开源框架Universal-Image-Loader完全解析
- Android 开源框架Universal-Image-Loader完全解析
- Android 开源框架Universal-Image-Loader完全解析
- android 开源框架Universal-Image-Loader解析
- android 开源框架Universal-Image-Loader解析
- Android 开源框架Universal-Image-Loader完全解析
- Android 开源框架Universal-Image-Loader完全解析
- Android开源框架Universal-Image-Loader解析(一)
- Android 开源框架Universal-Image-Loader完全解析
- Android 图片加载框架Universal-Image-Loader源码解析
- Android开源框架Universal-Image-Loader
- 开源框架Android-Universal-Image-Loader
- Android 开源框架Universal-Image-Loader
- Android开源框架Universal-Image-Loader
- 选择排序之--简单选择排序,堆排序
- hdu1712分组背包
- 设计模式之策略模式
- GDOI 2016 Day1 T4 疯狂动物城
- 嵌入式电路板上 tcpdump 抓包方法
- Android开源框架Universal-Image-Loader源码解析
- Codeforces Round #352 (Div. 2) C. Recycling Bottles
- C++实现委托机制(三)——lambda表达式封装
- 无亲缘关系的客户与服务器
- HDU 1005 NumberSequence
- 数据结构实验之链表九:双向链表
- 格局与架构
- boost--timer库笔记
- STL 源码剖析读书笔记五:序列式容器之 heap、priority_queue、slist