转载请注明出处谢谢:http://blog.csdn.net/u011504118/article/details/44794819
第一次认识Universal-Image-Loader
(以下简称ImageLoader
)是因为在RecyclerView中加载图片时候出现了大规模的OOM,所以在网上搜到了这个开源框架(ImageLoader). 试了一下,果然有效果。可是总不能老是依赖外部库吧。于是想自己研究一下这个框架是怎么实现的。所以先搜一下有没有人研究过这个框架,发现了github上面有人对这个框架做了详细介绍的,附上github地址(源码分析). 但还是没有很详细。所以就打算自己研究一下了。
刚打开源码的时候被吓到了,感觉封装得好厉害,一点点慢慢看也不知道要看到什么时候,再加上看了这忘了那的,所以前几次分析失败了。
痛定思痛之后,决定重新打开源码来研究,这一次我不是毫无目的的看了,而是只看这个框架中最重要的一个函数,即图像的加载。好了,废话就说到这了,接下来开始分析吧。
说明:这里会抛弃很多的细节,只关注如何从网络上得到Bitmap对象
首先打开ImageLoader.java
并找到display
函数,这个函数有很多重载函数,只看那个实际实现的就好了。下面是里面我认为最核心的代码:
// 如果图像缓存在内存中
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {
....
} else {
....
// 加载图片的信息,这里不去关注它
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,options, listener, progressListener, engine.getLockForUri(uri));
// 实现了Runnable接口
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,defineHandler(options));
// 同步加载
if (options.isSyncLoading()) {
displayTask.run();
} else {
// 我们要找的函数。
engine.submit(displayTask);
}
}
接着,找到ImageLoaderEngine
的submit(LoadAndDisplayImageTask task)
函数,如下:
void submit(final LoadAndDisplayImageTask task) {
taskDistributor.execute(new Runnable() {
@Override
public void run() {
File image = configuration.diskCache.get(task.getLoadingUri());
boolean isImageCachedOnDisk = image != null && image.exists();
// 先跳过这个函数
initExecutorsIfNeed();
// 如果图像缓存在硬盘上
if (isImageCachedOnDisk) {
taskExecutorForCachedImages.execute(task);
} else {
// 我们要找的函数
taskExecutor.execute(task);
}
}
});
}
taskExecutor
的类型是java.util.concurrent.Executor
,关于这个类,我就不做介绍了,因为我也只是大概知道用它可以很方便的实现多线程。调用execute
方法后会执行task
中的run
方法。上面注释中提到,LoadAndDisplayImageTask
类实现了Runnable
接口,所以我们就去找到它实现的run
方法吧。
@Override
public void run() {
....
// 这一部分我想应该是将图像地址锁起来,防止错位的。
// 先忽略它,以后再来研究。
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
if (loadFromUriLock.isLocked()) {
L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
}
loadFromUriLock.lock();
....
// 先判断图片是否在内存缓存上
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
// 我们要找的函数
bmp = tryLoadBitmap();
....
}
}
接着在同一个类中找到tryLoadBitmap
函数。
....
// 判断是否在磁盘上
File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists()) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;
checkTaskNotActual();
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;
String imageUriForDecoding = uri;
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();
bitmap = decodeImage(imageUriForDecoding);
// 图片加载失败
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
在这里我有一点还没弄明白的是,在上面提到的,它已经对图片是否在磁盘上判断了一次,这里又判断了一次。希望有知道的朋友可以跟我说下哈。
接着找到decode
函数
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = imageAware.getScaleType();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
getDownloader(), options);
return decoder.decode(decodingInfo);
}
找到decode
函数后,发现它是封装在Decoder
的唯一接口,所以我们就只能找到decoder
对应的实现类了。
decoder= configuration.decoder;
找到后,发现它是从configuration
传进来的。而configuration
又是我们自己定义的配置,但问题是我们没有自己去定义Decoder
的继承类啊。看到这里,是不是有些晕了呢?别急,既然我们没有自定义,那么这个框架肯定给我们提供了一个默认的。我们的任务就是要找到这个默认的类。所以找到ImageLoaderConfiguration
这个类,找一下它有没有帮我们做什么。
public Builder imageDecoder(ImageDecoder imageDecoder) {
this.decoder = imageDecoder;
return this;
}
看到这里,有点失望了,还是找不到。但在build()
找到了这个:
public ImageLoaderConfiguration build() {
initEmptyFieldsWithDefaultValues();
return new ImageLoaderConfiguration(this);
}
顺藤摸瓜,找到initEmptyFieldsWithDefaultValues
函数。
private void initEmptyFieldsWithDefaultValues() {
....
if (decoder == null) {
// 在这里`writeLogs`的值为`false`
decoder = DefaultConfigurationFactory.createImageDecoder(writeLogs);
}
....
}
找了这么久,终于找到了~~~
接着找它的实现:
public static ImageDecoder createImageDecoder(boolean loggingEnabled) {
return new BaseImageDecoder(loggingEnabled);
}
最后在BaseImageDecoder
类中找到了decode
方法。
@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
// 获取输入流
InputStream imageStream = getImageStream(decodingInfo);
try {
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
imageStream = resetStream(imageStream, decodingInfo);
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
// 解析输入流
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
在这里,我们终于见到了熟悉的InputStream
和BitmapFactory
!!!
你以为就完了吗?还没找到它得到输入流的方式呢!接着继续找:
protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}
再次跟踪,发现getStream
是ImageDownloader
的一个接口,有了上面找decoder
的思路,downloader
就容易找了。
这里我就直接给出downloader
的实现类BaseImageDownloader
了。
@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);
}
}
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = createConnection(imageUri, extra);
int redirectCount = 0;
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}
InputStream imageStream;
try {
imageStream = conn.getInputStream();
} catch (IOException e) {
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}
终于,终于,终于找到它的下载方式了,没错,就是通过HttpURLConnection
实现的!!!
找了这么久总算是找到了Bitmap对象的获取方法。最大的感受就是这个框架封装得太厉害了。要用我之前的方法一点一点去理解,去吃透整个框架是很难的。所以我觉得最好的方法就是找到你的关注点,然后抛弃其它杂音,顺藤摸瓜找下去。生活不也需要如此吗?
温馨提示:查看源码的工具强烈推荐Source Insight, 简直就是神器!
第一次追踪源码,如果有写得不好的地方希望大家能够提出来~