深度拆解!Android Universal-Image-Loader图片加载请求全流程源码解析(2)

深度拆解!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图片加载请求处理的源码解析,我们完整还原了从请求发起、缓存校验、网络调度到图片渲染的全流程。每个环节通过精巧的设计实现了高效的资源利用与稳定的加载性能。理解这些机制不仅能帮助开发者优化图片加载体验,还能为自定义图片加载框架提供宝贵的设计思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android 小码蜂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值