您的位置:首页 > 移动开发 > Android开发

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

2016-05-17 20:20 344 查看

概述

本文主要是用来探究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框架的大体思路,里面涉及的知识点非常多,无法面面俱到,初次分析有很多的不足之处,还是要多多学习大牛们的源码来开阔自身的眼界,努力提高自己代码的设计能力,路漫漫其修远兮。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: