深度拆解!Android Universal-Image-Loader图片加载请求全流程源码解析
一、图片加载请求处理核心流程概述
在Android开发中,图片加载是影响应用性能与用户体验的关键环节。Universal-Image-Loader作为经典的图片加载框架,其图片加载请求处理模块通过多层缓存校验、异步任务调度和智能重试机制,实现了高效稳定的图片加载。整个流程从用户发起加载请求开始,历经缓存命中判断、网络请求调度、图片解码渲染等核心步骤。接下来,我们将从源码层面逐步解析每个环节的实现细节。
二、加载请求的构建与参数传递
2.1 ImageLoader.displayImage方法解析
图片加载的入口是ImageLoader
类的displayImage
方法,该方法接收图片地址、目标视图和配置参数,启动加载流程:
public class ImageLoader {
public void displayImage(String imageUri, ImageView imageView, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
// 校验目标视图是否为空,若为空则抛出异常
if (imageView == null) {
throw new IllegalArgumentException("ImageView can not be null");
}
// 获取当前线程的内存缓存策略
MemoryCacheAware<String, Bitmap> memoryCache = getMemoryCache();
// 构建图片加载任务
LoadAndDisplayImageTask task = new LoadAndDisplayImageTask(imageUri, imageView, options,
memoryCache, listener, progressListener, null, this);
// 提交任务到任务队列
taskExecutor.execute(task);
}
}
该方法首先验证目标视图的有效性,然后获取内存缓存实例,构建LoadAndDisplayImageTask
任务对象,并提交到线程池执行。其中DisplayImageOptions
用于配置加载过程中的显示策略,如占位图、错误图等。
2.2 任务构建类LoadAndDisplayImageTask
LoadAndDisplayImageTask
类封装了完整的图片加载逻辑,其构造函数接收所有必要参数:
public class LoadAndDisplayImageTask implements Runnable {
private final String imageUri; // 图片地址
private final ImageView imageView; // 目标视图
private final DisplayImageOptions options; // 显示配置
private final MemoryCacheAware<String, Bitmap> memoryCache; // 内存缓存
private final ImageLoadingListener listener; // 加载监听器
private final ImageLoadingProgressListener progressListener; // 进度监听器
private final ImageLoaderEngine engine; // 加载引擎
public LoadAndDisplayImageTask(String imageUri, ImageView imageView, DisplayImageOptions options,
MemoryCacheAware<String, Bitmap> memoryCache,
ImageLoadingListener listener,
ImageLoadingProgressListener progressListener,
ExecutorService handlerExecutor, ImageLoader imageLoader) {
this.imageUri = imageUri;
this.imageView = imageView;
this.options = options;
this.memoryCache = memoryCache;
this.listener = listener;
this.progressListener = progressListener;
// 获取图片加载引擎实例
this.engine = imageLoader.getEngine();
}
@Override
public void run() {
// 执行图片加载核心逻辑
loadImage();
}
}
构造函数将参数赋值给类成员变量,并获取图片加载引擎实例。run
方法调用loadImage
开始执行加载任务。
三、多层缓存校验机制
3.1 内存缓存优先读取
loadImage
方法首先尝试从内存缓存中获取图片:
public class LoadAndDisplayImageTask {
private void loadImage() {
// 从内存缓存中获取Bitmap
Bitmap bitmap = memoryCache.get(imageUri);
if (bitmap != null && !bitmap.isRecycled()) {
// 内存缓存命中,直接显示图片
displayImage(bitmap);
} else {
// 内存缓存未命中,继续检查磁盘缓存
loadImageFromDisc();
}
}
private void displayImage(Bitmap bitmap) {
// 在主线程更新UI显示图片
handler.post(new Runnable() {
@Override
public void run() {
options.getDisplayer().display(imageView, bitmap);
}
});
}
}
若内存缓存中存在图片且未被回收,直接通过displayImage
方法在主线程更新UI显示;否则进入磁盘缓存检查流程。
3.2 磁盘缓存读取逻辑
磁盘缓存读取通过loadImageFromDisc
方法实现:
public class LoadAndDisplayImageTask {
private void loadImageFromDisc() {
// 从磁盘缓存中获取图片文件
File imageFile = discCache.get(imageUri);
if (imageFile != null && imageFile.exists()) {
try {
// 解码磁盘中的图片文件
Bitmap bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
if (bitmap != null) {
// 解码成功,将图片存入内存缓存并显示
memoryCache.put(imageUri, bitmap);
displayImage(bitmap);
} else {
// 解码失败,继续网络加载
loadImageFromNetwork();
}
} catch (IOException e) {
// 读取磁盘文件失败,触发错误回调
listener.onLoadingFailed(imageUri, imageView, e);
}
} else {
// 磁盘缓存未命中,触发网络加载
loadImageFromNetwork();
}
}
private Bitmap decodeImage(String imageUri) throws IOException {
// 根据配置获取图片解码格式
DecodeFormat decodeFormat = options.getDecodeFormat();
// 创建图片解码选项
BitmapFactory.Options decodeOptions = ImageDecoder.decodeImageOptions(options, decodeFormat);
// 执行图片解码
return ImageDecoder.decodeImage(Scheme.FILE.wrap(imageUri), decodeOptions);
}
}
该方法先从磁盘缓存中查找图片文件,若存在则进行解码。解码成功后将图片存入内存缓存并显示;若失败或未找到文件,则进入网络加载流程。
四、网络请求调度与图片下载
4.1 网络请求触发
当缓存均未命中时,通过loadImageFromNetwork
方法发起网络请求:
public class LoadAndDisplayImageTask {
private void loadImageFromNetwork() {
// 触发加载开始回调
listener.onLoadingStarted(imageUri, imageView);
try {
// 从网络下载图片数据
Bitmap bitmap = downloadImage(imageUri);
if (bitmap == null) {
// 下载失败,触发加载失败回调
listener.onLoadingFailed(imageUri, imageView, new IOException("Bitmap is null"));
} else {
// 下载成功,存入缓存并显示
if (options.isCacheOnDisc()) {
// 存入磁盘缓存
discCache.put(imageUri, new FileOutputStream(discCache.get(imageUri)));
}
memoryCache.put(imageUri, bitmap);
displayImage(bitmap);
}
} catch (IOException e) {
// 网络请求异常,触发错误回调
listener.onLoadingFailed(imageUri, imageView, e);
}
}
private Bitmap downloadImage(String imageUri) throws IOException {
// 创建图片下载器实例
ImageDownloader downloader = imageLoader.getImageDownloader();
// 执行网络下载
InputStream imageStream = downloader.getStream(imageUri, options);
if (imageStream == null) {
throw new IOException("Stream is null for uri " + imageUri);
}
try {
// 从输入流解码图片
return BitmapFactory.decodeStream(imageStream);
} finally {
// 关闭输入流
IoUtils.closeSilently(imageStream);
}
}
}
该方法先触发加载开始回调,然后通过ImageDownloader
执行网络下载。下载成功后,若配置了磁盘缓存则写入文件,并将图片存入内存缓存后显示;失败则触发错误回调。
4.2 网络请求重试机制
BaseImageDownloader
类实现了基础的网络请求逻辑,并包含重试机制:
public class BaseImageDownloader implements ImageDownloader {
private static final int DEFAULT_CONNECT_TIMEOUT = 5 * 1000; // 默认连接超时时间
private static final int DEFAULT_READ_TIMEOUT = 30 * 1000; // 默认读取超时时间
private static final int MAX_RETRIES = 3; // 最大重试次数
@Override
public InputStream getStream(String imageUri, DisplayImageOptions options) throws IOException {
HttpURLConnection conn = null;
InputStream imageStream = null;
for (int i = 0; i < MAX_RETRIES; i++) {
try {
// 创建HTTP连接
URL url = new URL(imageUri);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
conn.setReadTimeout(DEFAULT_READ_TIMEOUT);
conn.setDoInput(true);
// 连接服务器
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
// 获取图片输入流
imageStream = conn.getInputStream();
break;
}
} catch (IOException e) {
// 连接失败,关闭连接并重试
IoUtils.closeSilently(conn);
} finally {
IoUtils.closeSilently(imageStream);
}
}
if (imageStream == null) {
// 所有重试失败,抛出异常
throw new IOException("Couldn't connect to " + imageUri);
}
return imageStream;
}
}
通过循环实现最多3次重试,每次请求设置连接超时和读取超时时间,确保网络请求的稳定性。
五、图片解码与渲染优化
5.1 图片解码策略
ImageDecoder
类负责图片解码,支持多种格式和配置:
public class ImageDecoder {
public static Bitmap decodeImage(String imageUri, BitmapFactory.Options decodeOptions) throws IOException {
// 根据图片地址解析Scheme(如http、file)
Scheme scheme = Scheme.ofUri(imageUri);
switch (scheme) {
case HTTP:
case HTTPS:
// 网络图片解码
return decodeStreamFromNetwork(imageUri, decodeOptions);
case FILE:
// 本地文件图片解码
return decodeStreamFromFile(imageUri, decodeOptions);
case ASSETS:
// Assets目录图片解码
return decodeStreamFromAssets(imageUri, decodeOptions);
case DRAWABLE:
// Drawable资源图片解码
return decodeStreamFromDrawable(imageUri, decodeOptions);
default:
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
}
}
private static Bitmap decodeStreamFromNetwork(String imageUri, BitmapFactory.Options decodeOptions) throws IOException {
// 创建图片下载器获取输入流
ImageDownloader downloader = ImageLoader.getInstance().getImageDownloader();
InputStream imageStream = downloader.getStream(imageUri, null);
try {
// 解码输入流
return BitmapFactory.decodeStream(imageStream, null, decodeOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
}
}
根据图片地址的Scheme类型,选择不同的解码方式,并支持通过BitmapFactory.Options
配置解码参数,如采样率、色彩模式等。
5.2 图片渲染与线程安全
图片显示通过Displayer
接口实现,默认实现类SimpleBitmapDisplayer
确保在主线程更新UI:
public class SimpleBitmapDisplayer implements ImageDisplayer {
@Override
public void display(ImageView imageView, Bitmap bitmap) {
// 设置图片到ImageView
imageView.setImageBitmap(bitmap);
}
}
displayImage
方法通过handler.post
将显示任务提交到主线程,保证UI更新的线程安全:
public class LoadAndDisplayImageTask {
private final Handler handler = new Handler(Looper.getMainLooper());
private void displayImage(Bitmap bitmap) {
handler.post(new Runnable() {
@Override
public void run() {
options.getDisplayer().display(imageView, bitmap);
}
});
}
}
六、任务队列管理与并发控制
6.1 任务队列结构
Universal-Image-Loader使用LinkedBlockingQueue
管理待执行的加载任务:
public class ImageLoader {
private final BlockingQueue<Runnable> taskQueue; // 任务队列
private final ExecutorService taskExecutor; // 线程池
public ImageLoader() {
// 创建有界任务队列
taskQueue = new LinkedBlockingQueue<Runnable>();
// 创建固定大小线程池
taskExecutor = Executors.newFixedThreadPool(Configuration.DEFAULT_THREAD_POOL_SIZE,
new PriorityThreadFactory("ImageLoader", Configuration.DEFAULT_THREAD_PRIORITY));
}
public void execute(Runnable task) {
// 将任务提交到队列
taskQueue.offer(task);
}
}
LinkedBlockingQueue
的特性保证了任务的有序执行,配合固定大小线程池实现并发控制。
6.2 任务优先级与执行策略
通过QueueProcessingType
枚举支持两种任务处理顺序:
public enum QueueProcessingType {
FIFO, // 先进先出
LIFO // 后进先出
}
在初始化时可配置任务处理策略:
public class Configuration {
public QueueProcessingType tasksProcessingOrder = QueueProcessingType.FIFO;
}
不同策略适用于不同场景,例如FIFO适合按顺序加载图片,LIFO适合优先加载最新请求的图片。
通过对Universal-Image-Loader图片加载请求处理的源码解析,我们完整还原了从请求发起、缓存校验、网络调度到图片渲染的全流程。每个环节通过精巧的设计实现了高效的资源利用与稳定的加载性能。理解这些机制不仅能帮助开发者优化图片加载体验,还能为自定义图片加载框架提供宝贵的设计思路。