Android Glide 的显示与回调模块原理分析

81 阅读38分钟

一、引言

在当今的 Android 应用开发中,图片处理是一个至关重要的环节。从应用的图标展示到复杂的图片画廊,图片的加载和显示直接影响着用户体验。Glide 作为一款功能强大且广泛使用的图片加载库,凭借其高效的性能、丰富的功能和简洁的 API,成为了开发者的首选。其中,显示与回调模块更是 Glide 的核心部分,它负责将加载好的图片资源准确无误地显示在目标视图上,并在整个过程中提供各种回调机制,让开发者能够实时掌握图片加载的状态。

在接下来的内容中,我们将深入 Glide 的源码,详细剖析显示与回调模块的工作原理。从目标视图的绑定到图片的加载、解码,再到资源的显示和过渡动画的执行,每一个步骤都将进行细致的分析,并结合具体的代码片段进行解释。同时,我们也会探讨回调模块是如何工作的,包括加载开始、失败、成功和取消等不同状态下的回调处理。此外,还会介绍如何自定义显示和回调逻辑,以及一些性能优化和注意事项。通过对 Glide 显示与回调模块的深入理解,开发者可以更好地利用 Glide 的功能,优化应用的图片加载流程,提升用户体验。

二、Glide 显示模块详细分析

2.1 目标视图绑定流程

2.1.1 Glide.with() 方法

当我们开始使用 Glide 加载图片时,首先会调用 Glide.with() 方法。这个方法的主要作用是获取一个 RequestManager 对象,该对象负责管理图片加载请求的生命周期。以下是 Glide.with() 方法的几种常见重载形式:

java

// 从 Activity 上下文获取 RequestManager 对象
public static RequestManager with(@NonNull Activity activity) {
    // 获取当前 Activity 的应用上下文
    Context context = activity.getApplicationContext();
    // 获取 RequestManagerRetriever 实例,用于管理 RequestManager
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    // 通过 RequestManagerRetriever 获取与当前 Activity 关联的 RequestManager
    return retriever.get(activity);
}

// 从 FragmentActivity 上下文获取 RequestManager 对象
public static RequestManager with(@NonNull FragmentActivity activity) {
    Context context = activity.getApplicationContext();
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(activity);
}

// 从 Fragment 上下文获取 RequestManager 对象
public static RequestManager with(@NonNull Fragment fragment) {
    Context context = fragment.getActivity().getApplicationContext();
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(fragment);
}

这些方法最终都会调用 RequestManagerRetriever.get() 方法来获取 RequestManager 对象。RequestManagerRetriever 是一个单例类,负责管理 RequestManager 的创建和缓存。以下是 RequestManagerRetriever.get() 方法的部分实现:

java

// 获取 RequestManagerRetriever 单例实例
public static RequestManagerRetriever get() {
    // 使用静态内部类实现单例模式,确保线程安全
    return RequestManagerRetrieverHolder.INSTANCE;
}

// 静态内部类,用于持有 RequestManagerRetriever 单例实例
private static final class RequestManagerRetrieverHolder {
    // 单例实例
    private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();
}

// 根据 Activity 获取 RequestManager 对象
public RequestManager get(@NonNull Activity activity) {
    // 检查当前线程是否为主线程
    if (Util.isOnBackgroundThread()) {
        // 如果在后台线程,使用应用上下文获取 RequestManager
        return get(activity.getApplicationContext());
    } else {
        // 获取 Activity 的 FragmentManager
        android.app.FragmentManager fm = activity.getFragmentManager();
        // 通过 FragmentManager 和 Activity 上下文获取 RequestManager
        return supportFragmentGet(activity, fm, null, isActivityVisible(activity));
    }
}

// 根据 Fragment 获取 RequestManager 对象
public RequestManager get(@NonNull Fragment fragment) {
    Context context = fragment.getActivity();
    if (context == null) {
        throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
    }
    if (Util.isOnBackgroundThread()) {
        return get(context.getApplicationContext());
    } else {
        // 获取 Fragment 的子 FragmentManager
        android.app.FragmentManager fm = fragment.getChildFragmentManager();
        return supportFragmentGet(context, fm, fragment, fragment.isVisible());
    }
}

// 支持 Fragment 的方式获取 RequestManager 对象
private RequestManager supportFragmentGet(@NonNull Context context,
                                          @NonNull android.app.FragmentManager fm,
                                          @Nullable android.app.Fragment parentHint,
                                          boolean isParentVisible) {
    // 获取一个空的 Fragment,用于监听生命周期
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
    // 获取该 Fragment 关联的 RequestManager
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        // 如果 RequestManager 为空,则创建一个新的 RequestManager
        Glide glide = Glide.get(context);
        requestManager = factory.build(glide, current.getLifecycle(), current.getRequestManagerTreeNode(), context);
        // 将新创建的 RequestManager 关联到 Fragment 上
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

通过上述代码可以看出,Glide.with() 方法的核心是通过 RequestManagerRetriever 来获取与当前上下文关联的 RequestManager 对象。RequestManager 会根据不同的上下文(如 ActivityFragment)来管理图片加载请求的生命周期,确保在上下文销毁时自动取消未完成的请求,避免内存泄漏。

2.1.2 load() 方法

在获取到 RequestManager 对象后,我们会调用 load() 方法来指定要加载的图片资源。load() 方法有多种重载形式,支持加载不同类型的资源,如网络图片、本地文件、资源 ID 等。以下是一些常见的 load() 方法实现:

java

// 加载网络图片
public RequestBuilder<Drawable> load(@Nullable String string) {
    // 创建一个 RequestBuilder 对象,并设置要加载的模型为字符串类型的 URL
    return asDrawable().load(string);
}

// 加载本地文件
public RequestBuilder<Drawable> load(@Nullable File file) {
    return asDrawable().load(file);
}

// 加载资源 ID
public RequestBuilder<Drawable> load(@DrawableRes int resourceId) {
    return asDrawable().load(resourceId);
}

load() 方法会返回一个 RequestBuilder 对象,该对象用于构建图片加载请求。RequestBuilder 提供了一系列的配置方法,如 placeholder()error()override() 等,用于设置图片加载的各种参数。以下是 RequestBuilder 的部分代码:

java

// RequestBuilder 类,用于构建图片加载请求
public class RequestBuilder<TranscodeType> implements Cloneable {
    private final Glide glide;
    private final RequestManager requestManager;
    private final Class<TranscodeType> transcodeClass;
    private Object model;
    private Drawable placeholderDrawable;
    private Drawable errorDrawable;
    private int overrideWidth;
    private int overrideHeight;

    // 构造函数,初始化 Glide、RequestManager 和转码类型
    public RequestBuilder(Glide glide, RequestManager requestManager, Class<TranscodeType> transcodeClass) {
        this.glide = glide;
        this.requestManager = requestManager;
        this.transcodeClass = transcodeClass;
    }

    // 设置要加载的模型
    public RequestBuilder<TranscodeType> load(@Nullable Object model) {
        this.model = model;
        return this;
    }

    // 设置占位图
    public RequestBuilder<TranscodeType> placeholder(@DrawableRes int resourceId) {
        this.placeholderDrawable = glide.getDrawable(resourceId);
        return this;
    }

    // 设置错误图
    public RequestBuilder<TranscodeType> error(@DrawableRes int resourceId) {
        this.errorDrawable = glide.getDrawable(resourceId);
        return this;
    }

    // 设置图片的尺寸
    public RequestBuilder<TranscodeType> override(int width, int height) {
        this.overrideWidth = width;
        this.overrideHeight = height;
        return this;
    }

    // 构建请求
    private Request buildRequest(Target<TranscodeType> target) {
        // 创建一个 SingleRequest 对象,该对象表示一个具体的图片加载请求
        return SingleRequest.obtain(
                glide,
                model,
                transcodeClass,
                requestOptions,
                overrideWidth,
                overrideHeight,
                priority,
                target,
                requestListener,
                requestCoordinator,
                context,
                isMemoryCacheable,
                useUnlimitedSourceGeneratorsPool,
                useAnimationPool,
                onlyRetrieveFromCache
        );
    }
}

RequestBuilder 类通过链式调用的方式,让开发者可以方便地配置图片加载请求的各种参数。最终,通过 buildRequest() 方法构建一个 SingleRequest 对象,该对象表示一个具体的图片加载请求。

2.1.3 into() 方法

最后,我们会调用 into() 方法将图片加载到目标视图上。into() 方法会根据目标视图的类型创建相应的 Target 对象,并将构建好的请求对象与目标视图关联起来。以下是 into() 方法的详细实现:

java

// 将图片加载到指定的 ImageView 上
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
    // 确保该方法在主线程中调用,因为更新 UI 操作必须在主线程进行
    Util.assertMainThread();
    if (target == null) {
        throw new IllegalArgumentException("You must pass in a non null Target");
    }
    if (!isModelSet) {
        throw new IllegalArgumentException("You must first set a model (try #load())");
    }
    // 构建请求对象,该对象包含了图片加载的各种配置信息
    Request request = buildRequest(target);
    // 获取当前目标视图上之前的请求
    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
            && !isSkipMemoryCacheWithCompletePreviousRequest(previous)) {
        // 如果新请求和之前的请求等价,并且不需要跳过内存缓存
        request.recycle(); // 回收新请求对象,避免资源浪费
        if (!Preconditions.checkNotNull(previous).isRunning()) {
            // 如果之前的请求没有在运行,则重新启动它
            previous.begin();
        }
        return target;
    }
    // 清除目标视图上之前的请求
    requestManager.clear(target);
    // 将新请求设置到目标视图上
    target.setRequest(request);
    // 让请求管理器跟踪这个请求,以便管理其生命周期
    requestManager.track(target, request);
    return target;
}

// 将图片加载到指定的 ImageView 上的简化版本
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    // 创建一个 ImageViewTarget 对象,用于处理将图片显示在 ImageView 上的操作
    return into(
            glideContext.buildImageViewTarget(view, transcodeClass),
            /* targetListener= */ null);
}

// 将图片加载到指定的 Target 对象上,并设置监听器
private <Y extends Target<TranscodeType>> Y into(
        @NonNull Y target,
        @Nullable RequestListener<TranscodeType> targetListener) {
    // 确保该方法在主线程中调用
    Util.assertMainThread();
    if (target == null) {
        throw new IllegalArgumentException("You must pass in a non null Target");
    }
    if (!isModelSet) {
        throw new IllegalArgumentException("You must first set a model (try #load())");
    }
    // 合并监听器
    RequestListener<TranscodeType> requestListener =
            targetListener == null
                   ? this.requestListener
                    : new MultiRequestListener<>(Arrays.asList(targetListener, this.requestListener));
    // 构建请求
    Request request = buildRequest(target, requestListener, /*parentCoordinator=*/ null);
    // 获取当前目标视图上之前的请求
    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
            && !isSkipMemoryCacheWithCompletePreviousRequest(previous)) {
        // 如果新请求和之前的请求等价,并且不需要跳过内存缓存
        request.recycle();
        if (!Preconditions.checkNotNull(previous).isRunning()) {
            // 如果之前的请求没有在运行,则重新启动它
            previous.begin();
        }
        return target;
    }
    // 清除目标视图上之前的请求
    requestManager.clear(target);
    // 设置新请求到目标视图
    target.setRequest(request);
    // 让请求管理器跟踪这个请求
    requestManager.track(target, request);
    return target;
}

在 into() 方法中,首先会进行一系列的参数检查,确保传入的目标视图和模型是有效的。然后根据目标视图构建一个新的请求对象,并检查是否可以复用之前的请求。如果可以复用,就回收新请求并重新启动旧请求;否则,清除旧请求,设置新请求,并让请求管理器跟踪这个新请求。

2.2 图片加载与解码

2.2.1 数据源选择

在请求开始执行后,Glide 会根据图片的来源选择合适的数据源进行数据获取。数据源的选择是通过 DataFetcher 来实现的,不同的数据源对应不同的 DataFetcher 实现类。以下是数据源选择的部分代码:

java

// DataFetcherGenerator 接口,用于生成 DataFetcher
interface DataFetcherGenerator {
    // 尝试获取数据
    boolean startNext();
    // 取消数据获取
    void cancel();
}

// SourceGenerator 类,用于处理源数据的获取
class SourceGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {
    private final DecodeHelper<?> helper;
    private final DataFetcherGenerator.FetcherReadyCallback cb;
    private int sourceIdIndex;
    private DataFetcher<?> currentFetcher;

    // 构造函数,初始化 DecodeHelper 和回调接口
    public SourceGenerator(DecodeHelper<?> helper, DataFetcherGenerator.FetcherReadyCallback cb) {
        this.helper = helper;
        this.cb = cb;
    }

    @Override
    public boolean startNext() {
        // 获取要加载的模型
        List<ModelLoader<Object, ?>> modelLoaders = helper.getModelLoaders();
        if (sourceIdIndex >= modelLoaders.size()) {
            return false;
        }
        // 获取当前的 ModelLoader
        ModelLoader<Object, ?> modelLoader = modelLoaders.get(sourceIdIndex++);
        // 创建一个 DataFetcher
        currentFetcher = modelLoader.buildLoadData(helper.getModel(), helper.getWidth(), helper.getHeight(), helper.getOptions());
        if (currentFetcher != null) {
            // 启动 DataFetcher 进行数据获取
            currentFetcher.loadData(helper.getPriority(), this);
            return true;
        }
        return startNext();
    }

    @Override
    public void cancel() {
        if (currentFetcher != null) {
            // 取消当前的 DataFetcher
            currentFetcher.cancel();
        }
    }

    @Override
    public void onDataReady(Object data) {
        // 数据获取成功,通知回调接口
        cb.onDataFetcherReady(currentFetcher, data, null, DataSource.REMOTE, null);
    }

    @Override
    public void onLoadFailed(@NonNull Exception e) {
        // 数据获取失败,通知回调接口
        cb.onDataFetcherFailed(currentFetcher, e, null, DataSource.REMOTE);
    }
}

SourceGenerator 类负责处理源数据的获取。在 startNext() 方法中,会根据 DecodeHelper 中保存的 ModelLoader 列表,依次尝试创建 DataFetcher 并启动数据获取。如果数据获取成功,会调用 onDataReady() 方法通知回调接口;如果失败,会调用 onLoadFailed() 方法。

2.2.2 数据加载

DataFetcher 是负责从数据源获取数据的接口,不同的数据源有不同的 DataFetcher 实现类。以网络数据源为例,HttpUrlFetcher 是用于从网络获取数据的 DataFetcher 实现类。以下是 HttpUrlFetcher 的部分代码:

java

// HttpUrlFetcher 类,用于从网络获取数据
public class HttpUrlFetcher implements DataFetcher<InputStream> {
    private final HttpUrlGlideUrl url;
    private final int timeout;
    private InputStream stream;
    private HttpURLConnection urlConnection;

    // 构造函数,初始化 URL 和超时时间
    public HttpUrlFetcher(HttpUrlGlideUrl url, int timeout) {
        this.url = url;
        this.timeout = timeout;
    }

    @Override
    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
        long startTime = LogTime.getLogTime();
        try {
            // 打开网络连接
            stream = loadDataWithRedirects(url.toURL(), 0, null, url.getHeaders());
            // 数据加载成功,通知回调接口
            callback.onDataReady(stream);
        } catch (IOException e) {
            // 数据加载失败,通知回调接口
            callback.onLoadFailed(e);
        } finally {
            LogTime.getElapsedMillis(startTime, "HttpUrlFetcher: finished http url fetcher fetch in");
        }
    }

    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
        if (redirects >= MAX_REDIRECTS) {
            throw new IOException("Too many (> " + MAX_REDIRECTS + ") redirects!");
        } else {
            try {
                if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                    throw new IOException("In re-direct loop");
                }
            } catch (URISyntaxException e) {
                // Do nothing, this is best effort.
            }
            // 打开 HTTP 连接
            urlConnection = (HttpURLConnection) url.openConnection();
            for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
                // 设置请求头
                urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
            }
            urlConnection.setConnectTimeout(timeout);
            urlConnection.setReadTimeout(timeout);
            urlConnection.setUseCaches(false);
            urlConnection.setDoInput(true);
            urlConnection.setInstanceFollowRedirects(false);
            // 连接服务器
            urlConnection.connect();
            if (isCancelled) {
                return null;
            }
            int statusCode = urlConnection.getResponseCode();
            if (statusCode / 100 == 2) {
                // 状态码为 2xx,表示请求成功
                return getStreamForSuccessfulRequest(urlConnection);
            } else if (statusCode / 100 == 3) {
                // 状态码为 3xx,表示重定向
                String redirectUrlString = urlConnection.getHeaderField("Location");
                if (TextUtils.isEmpty(redirectUrlString)) {
                    throw new IOException("Received empty or null redirect url");
                }
                URL redirectUrl = new URL(url, redirectUrlString);
                return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
            } else {
                if (statusCode == -1) {
                    throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
                }
                throw new HttpException(urlConnection.getResponseMessage(), statusCode);
            }
        }
    }

    private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection) throws IOException {
        if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
            // 如果没有设置内容编码,直接获取输入流
            int contentLength = urlConnection.getContentLength();
            stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
        } else {
            // 如果设置了内容编码,使用相应的输入流
            stream = urlConnection.getInputStream();
        }
        return stream;
    }

    @Override
    public void cleanup() {
        try {
            if (stream != null) {
                stream.close();
            }
        } catch (IOException e) {
            // Ignored
        }
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
        stream = null;
        urlConnection = null;
    }

    @Override
    public void cancel() {
        isCancelled = true;
    }

    @NonNull
    @Override
    public Class<InputStream> getDataClass() {
        return InputStream.class;
    }

    @NonNull
    @Override
    public DataSource getDataSource() {
        return DataSource.REMOTE;
    }
}

HttpUrlFetcher 的 loadData() 方法会打开网络连接,设置请求头,连接服务器并获取响应。如果请求成功,会返回输入流;如果遇到重定向,会递归调用 loadDataWithRedirects() 方法进行重定向处理。

2.2.3 图片类型检测

在获取到图片的二进制数据后,Glide 会使用 ImageHeaderParser 类解析图片的头部信息,确定图片的类型。以下是 ImageHeaderParser 的部分代码:

java

// ImageHeaderParser 类,用于解析图片的头部信息
public class ImageHeaderParser {
    private static final int GIF_HEADER = 0x474946;
    private static final int PNG_HEADER = 0x89504E47;
    private static final int JPEG_HEADER = 0xFFD8FF;
    private static final int WEBP_HEADER_RIFF = 0x52494646;
    private static final int WEBP_HEADER_WEBP = 0x57454250;

    private final InputStream is;

    // 构造函数,初始化输入流
    public ImageHeaderParser(InputStream is) {
        this.is = is;
    }

    // 获取图片类型
    public ImageType getType() throws IOException {
        byte[] header = new byte[12];
        int read = StreamUtils.read(is, header);
        if (read < 12) {
            return ImageType.UNKNOWN;
        }
        int firstFourBytes = ((header[0] & 0xFF) << 24) | ((header[1] & 0xFF) << 16) | ((header[2] & 0xFF) << 8) | (header[3] & 0xFF);
        if (firstFourBytes == GIF_HEADER) {
            return ImageType.GIF;
        } else if (firstFourBytes == PNG_HEADER) {
            return ImageType.PNG;
        } else if ((firstFourBytes & JPEG_HEADER) == JPEG_HEADER) {
            return ImageType.JPEG;
        } else if (firstFourBytes == WEBP_HEADER_RIFF) {
            int secondFourBytes = ((header[8] & 0xFF) << 24) | ((header[9] & 0xFF) << 16) | ((header[10] & 0xFF) << 8) | (header[11] & 0xFF);
            if (secondFourBytes == WEBP_HEADER_WEBP) {
                return ImageType.WEBP;
            }
        }
        return ImageType.UNKNOWN;
    }

    public enum ImageType {
        GIF,
        PNG,
        JPEG,
        WEBP,
        UNKNOWN
    }
}

ImageHeaderParser 的 getType() 方法会读取图片的前 12 个字节,根据不同的头部信息判断图片的类型。

2.2.4 解码器选择

根据图片类型和配置,Glide 会选择合适的解码器进行解码操作。解码器的选择是通过 ResourceDecoder 来实现的,不同的图片类型对应不同的 ResourceDecoder 实现类。以下是解码器选择的部分代码:

java

// DecodeHelper 类,用于辅助解码操作
class DecodeHelper<Transcode> {
    private final List<ResourceDecoder<File, Transcode>> cacheFileDecoders = new ArrayList<>();
    private final List<ResourceDecoder<InputStream, Transcode>> sourceDecoders = new ArrayList<>();

    // 获取适合的解码器
    public ResourceDecoder<File, Transcode> getCacheDecoder() {
        for (ResourceDecoder<File, Transcode> decoder : cacheFileDecoders) {
            if (decoder.handles(file, options)) {
                return decoder;
            }
        }
        return null;
    }

    public ResourceDecoder<InputStream, Transcode> getSourceDecoder() {
        for (ResourceDecoder<InputStream, Transcode> decoder : sourceDecoders) {
            if (decoder.handles(inputStream, options)) {
                return decoder;
            }
        }
        return null;
    }
}

DecodeHelper 类会维护一个解码器列表,在 getSourceDecoder() 和 getCacheDecoder() 方法中,会遍历解码器列表,找到适合当前图片类型和配置的解码器。

2.2.5 解码操作

找到合适的解码器后,会调用其 decode() 方法进行解码操作。以 BitmapDecoder 为例,以下是其部分代码:

java

// BitmapDecoder 类,用于解码 Bitmap
public class BitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
    private final BitmapPool bitmapPool;
    private final Downsampler downsampler;

    // 构造函数,初始化 BitmapPool 和 Downsampler
    public BitmapDecoder(BitmapPool bitmapPool, Downsampler downsampler) {
        this.bitmapPool = bitmapPool;
        this.downsampler = downsampler;
    }

    @Override
    public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {
        // 检查是否可以处理该输入流
        return downsampler

2.2.5 解码操作(续)

java

    @Override
    public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {
        // 检查是否可以处理该输入流
        return downsampler.handles(source, options);
    }

    @Override
    public Resource<Bitmap> decode(@NonNull InputStream source, int width, int height, @NonNull Options options) throws IOException {
        // 调用 Downsampler 进行实际的解码操作
        Bitmap downsampled = downsampler.decode(source, bitmapPool, width, height, options);
        if (downsampled == null) {
            return null;
        }
        // 将解码后的 Bitmap 包装成 Resource 对象
        return BitmapResource.obtain(downsampled, bitmapPool);
    }
}

在 BitmapDecoder 中,handles() 方法会调用 Downsampler 的 handles() 方法来判断是否可以处理当前的输入流。而 decode() 方法则会调用 Downsampler 的 decode() 方法进行实际的解码操作。Downsampler 是一个核心的解码类,它会根据图片的尺寸、配置等信息对图片进行下采样,以减少内存占用。以下是 Downsampler 的部分代码:

java

// Downsampler 类,用于对图片进行下采样
public class Downsampler {
    private static final int MARK_POSITION = 1024 * 16;

    // 解码方法
    public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, Options options) throws IOException {
        // 标记输入流的位置,以便后续重置
        is.mark(MARK_POSITION);
        try {
            // 获取图片的类型
            ImageHeaderParser.ImageType type = getImageType(is);
            if (type == ImageHeaderParser.ImageType.UNKNOWN) {
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Failed to determine image type from stream, decoding as default.");
                }
            }
            // 获取图片的尺寸信息
            int[] inDimens = getDimensions(is, type);
            int inWidth = inDimens[0];
            int inHeight = inDimens[1];
            // 计算采样率
            int sampleSize = getSampleSize(inWidth, inHeight, outWidth, outHeight);
            // 重置输入流
            is.reset();
            // 获取图片的配置信息
            Bitmap.Config config = getConfig(is, type, options);
            // 从 BitmapPool 中获取一个可复用的 Bitmap
            Bitmap downsampled = pool.getDirty(inWidth / sampleSize, inHeight / sampleSize, config);
            if (downsampled == null) {
                downsampled = Bitmap.createBitmap(inWidth / sampleSize, inHeight / sampleSize, config);
            }
            // 进行实际的解码操作
            Bitmap decoded = decodeStream(is, downsampled, pool, sampleSize, options);
            if (decoded != downsampled) {
                // 如果解码后的 Bitmap 与从池里获取的不同,回收从池里获取的 Bitmap
                pool.put(downsampled);
            }
            return decoded;
        } catch (IOException e) {
            try {
                is.reset();
            } catch (IOException resetException) {
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Exception reseting to re-decode", resetException);
                }
            }
            throw e;
        }
    }

    private ImageHeaderParser.ImageType getImageType(InputStream is) throws IOException {
        // 使用 ImageHeaderParser 解析图片类型
        ImageHeaderParser parser = new ImageHeaderParser(is);
        return parser.getType();
    }

    private int[] getDimensions(InputStream is, ImageHeaderParser.ImageType type) throws IOException {
        // 创建一个 BitmapFactory.Options 对象,用于获取图片尺寸信息
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        // 根据图片类型选择合适的解码方式获取尺寸
        if (type == ImageHeaderParser.ImageType.GIF) {
            // 对于 GIF 图片的特殊处理
            // ...
        } else {
            // 普通图片的解码获取尺寸
            BitmapFactory.decodeStream(is, null, options);
        }
        return new int[]{options.outWidth, options.outHeight};
    }

    private int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
        // 计算采样率,以减少内存占用
        int sampleSize = 1;
        if (outWidth > 0 && outHeight > 0) {
            // 根据输出尺寸和输入尺寸计算采样率
            sampleSize = Math.max(1, Math.min(inWidth / outWidth, inHeight / outHeight));
        }
        return sampleSize;
    }

    private Bitmap.Config getConfig(InputStream is, ImageHeaderParser.ImageType type, Options options) {
        // 获取图片的配置信息,如 ARGB_8888 等
        // ...
        return Bitmap.Config.ARGB_8888;
    }

    private Bitmap decodeStream(InputStream is, Bitmap downsampled, BitmapPool pool, int sampleSize, Options options) {
        // 创建一个 BitmapFactory.Options 对象,用于实际的解码操作
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        decodeOptions.inSampleSize = sampleSize;
        decodeOptions.inPreferredConfig = getConfig(is, getImageType(is), options);
        decodeOptions.inBitmap = downsampled;
        // 进行解码操作
        Bitmap result = BitmapFactory.decodeStream(is, null, decodeOptions);
        if (result == null && downsampled != null) {
            // 如果解码失败,将从池里获取的 Bitmap 放回池里
            pool.put(downsampled);
        }
        return result;
    }
}

Downsampler 的 decode() 方法首先会标记输入流的位置,然后获取图片的类型、尺寸信息,计算采样率,接着从 BitmapPool 中获取一个可复用的 Bitmap,最后进行实际的解码操作。getDimensions() 方法会使用 BitmapFactory.Options 的 inJustDecodeBounds 属性来获取图片的尺寸信息,而不实际解码图片。getSampleSize() 方法会根据输入尺寸和输出尺寸计算采样率,以减少内存占用。decodeStream() 方法会使用 BitmapFactory.decodeStream() 进行实际的解码操作,并尝试复用从 BitmapPool 中获取的 Bitmap

2.3 资源显示流程

2.3.1 Target 接口及实现

Target 接口是 Glide 显示模块的核心接口,它定义了图片加载完成后将资源显示到目标视图的操作。以下是 Target 接口的代码:

java

// Target 接口定义了图片加载完成后将资源显示到目标视图的操作
public interface Target<R> {
    // 当图片加载开始时调用
    void onLoadStarted(@Nullable Drawable placeholder);
    // 当图片加载失败时调用
    void onLoadFailed(@Nullable Drawable errorDrawable);
    // 当图片加载成功时调用
    void onResourceReady(@NonNull R resource, @Nullable Transition<? super R> transition);
    // 当图片加载被取消时调用
    void onLoadCleared(@Nullable Drawable placeholder);
    // 获取目标视图的宽度
    int getWidth();
    // 获取目标视图的高度
    int getHeight();
    // 当目标视图被销毁时调用
    void onStart();
    // 当目标视图恢复时调用
    void onStop();
    // 当目标视图被回收时调用
    void onDestroy();
    // 设置当前的请求
    void setRequest(@Nullable Request request);
    // 获取当前的请求
    @Nullable
    Request getRequest();
}

不同的目标视图会有不同的 Target 实现类,例如 ImageViewTarget 是专门用于处理将图片资源显示在 ImageView 上的操作。以下是 ImageViewTarget 的部分代码:

java

// ImageViewTarget 类是 Target 接口的抽象实现类,用于处理将图片资源显示在 ImageView 上的操作
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> implements Transition.ViewAdapter {
    public ImageViewTarget(ImageView view) {
        super(view);
    }

    @Override
    public Drawable getCurrentDrawable() {
        return view.getDrawable();
    }

    @Override
    public void setDrawable(Drawable drawable) {
        view.setImageDrawable(drawable);
    }

    @Override
    public void onLoadStarted(@Nullable Drawable placeholder) {
        super.onLoadStarted(placeholder);
        setResourceInternal(null);
        setDrawable(placeholder);
    }

    @Override
    public void onLoadFailed(@Nullable Drawable errorDrawable) {
        super.onLoadFailed(errorDrawable);
        setResourceInternal(null);
        setDrawable(errorDrawable);
    }

    @Override
    public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
        if (transition == null || !transition.transition(resource, this)) {
            setResourceInternal(resource);
        } else {
            maybeUpdateAnimatable(resource);
        }
    }

    @Override
    public void onLoadCleared(@Nullable Drawable placeholder) {
        super.onLoadCleared(placeholder);
        setResourceInternal(null);
        setDrawable(placeholder);
    }

    protected abstract void setResource(@Nullable Z resource);

    private void setResourceInternal(@Nullable Z resource) {
        // 保存动画资源
        maybeUpdateAnimatable(resource);
        // 设置资源到 ImageView 上
        setResource(resource);
    }

    private void maybeUpdateAnimatable(@Nullable Z resource) {
        if (resource instanceof Animatable) {
            Animatable animatable = (Animatable) resource;
            animatable.start();
        }
    }
}

ImageViewTarget 实现了 Target 接口的大部分方法,并且提供了一些辅助方法来处理图片资源的显示和动画效果。在 onLoadStarted()onLoadFailed() 和 onLoadCleared() 方法中,会先调用父类的相应方法,然后设置资源为空,并显示占位图或错误图。在 onResourceReady() 方法中,会根据是否设置了过渡动画来决定如何显示图片资源。如果没有过渡动画或者过渡动画执行失败,会调用 setResourceInternal() 方法直接设置资源;否则,会调用 maybeUpdateAnimatable() 方法更新动画资源。

2.3.2 onResourceReady 方法详细处理

当图片加载和解码完成后,会调用 Target 对象的 onResourceReady 方法。以 SingleRequest 类为例,以下是其调用 onResourceReady 方法的代码:

java

// SingleRequest 类的 onResourceReady 方法,用于处理图片加载成功的情况
private void onResourceReady(@NonNull Resource<R> resource, @NonNull DataSource dataSource, boolean isFirstResource) {
    // 标记请求完成
    status = Status.COMPLETE;
    // 记录加载结束的时间,用于性能统计
    loadEndTime = LogTime.getLogTime();
    boolean anyListenerHandledUpdatingTarget = false;
    // 遍历所有的请求监听器
    for (RequestListener<R> listener : requestListeners) {
        // 调用监听器的 onResourceReady 方法
        anyListenerHandledUpdatingTarget |= listener.onResourceReady(resource.get(), model, target, dataSource, isFirstResource);
    }
    if (!anyListenerHandledUpdatingTarget) {
        // 如果没有监听器处理该资源,则调用 Target 对象的 onResourceReady 方法
        target.onResourceReady(resource.get(), getTransition(dataSource));
    }
    // 通知请求管理器请求成功
    requestManager.onRequestSuccess(this);
}

在 onResourceReady 方法中,首先会标记请求完成,记录加载结束的时间。然后遍历所有的请求监听器,调用它们的 onResourceReady 方法。如果有监听器处理了该资源,anyListenerHandledUpdatingTarget 会被设置为 true;否则,会调用 Target 对象的 onResourceReady 方法将资源显示到目标视图上。最后通知请求管理器请求成功。

2.3.3 资源设置到视图

在 ImageViewTarget 的 onResourceReady 方法中,会根据是否有过渡动画来决定如何设置资源。如果没有过渡动画或者过渡动画执行失败,会调用 setResourceInternal 方法,该方法会调用 setResource 方法将资源设置到 ImageView 上。不同的 ImageViewTarget 子类会实现 setResource 方法,以不同的方式设置资源。例如 BitmapImageViewTarget 的 setResource 方法:

java

// BitmapImageViewTarget 类,用于将 Bitmap 资源显示在 ImageView 上
public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> {
    public BitmapImageViewTarget(ImageView view) {
        super(view);
    }

    @Override
    protected void setResource(@Nullable Bitmap resource) {
        view.setImageBitmap(resource);
    }
}

BitmapImageViewTarget 的 setResource 方法直接将 Bitmap 资源设置到 ImageView 上。

2.4 过渡动画执行

2.4.1 Transition 接口及实现

Transition 接口用于定义图片显示时的动画效果。以下是 Transition 接口的代码:

java

// Transition 接口用于定义图片显示时的动画效果
public interface Transition<R> {
    // 执行过渡动画的方法
    boolean transition(R current, ViewAdapter adapter);

    // 用于获取和设置视图的 Drawable 的适配器接口
    interface ViewAdapter {
        // 获取当前视图的 Drawable
        Drawable getCurrentDrawable();
        // 设置视图的 Drawable
        void setDrawable(Drawable drawable);
    }
}

不同的动画效果会有不同的 Transition 实现类,例如 CrossFadeTransition 用于实现淡入淡出的动画效果。以下是 CrossFadeTransition 的代码:

java

// CrossFadeTransition 类用于实现淡入淡出的过渡动画
public class CrossFadeTransition implements Transition<Drawable> {
    private static final int DEFAULT_DURATION_MS = 300;
    private final int duration;
    private final boolean isCrossFadeEnabled;

    public CrossFadeTransition(int duration, boolean isCrossFadeEnabled) {
        this.duration = duration;
        this.isCrossFadeEnabled = isCrossFadeEnabled;
    }

    @Override
    public boolean transition(Drawable current, ViewAdapter adapter) {
        // 获取当前视图上的 Drawable
        Drawable previous = adapter.getCurrentDrawable();
        // 创建一个 TransitionDrawable 对象,用于实现过渡动画
        TransitionDrawable transitionDrawable = new TransitionDrawable(
                new Drawable[]{previous == null ? new ColorDrawable(Color.TRANSPARENT) : previous, current});
        // 设置是否启用交叉淡入效果
        transitionDrawable.setCrossFadeEnabled(isCrossFadeEnabled);
        // 启动过渡动画
        transitionDrawable.startTransition(duration);
        // 将 TransitionDrawable 设置到目标视图上
        adapter.setDrawable(transitionDrawable);
        return true;
    }
}

CrossFadeTransition 的 transition 方法会创建一个 TransitionDrawable 对象,将之前的 Drawable 和当前的 Drawable 作为参数传入。然后设置过渡动画的持续时间和是否启用交叉淡入效果,并启动过渡动画。最后将 TransitionDrawable 设置到目标视图上,从而实现淡入淡出的过渡效果。

2.4.2 过渡动画的触发

在 ImageViewTarget 的 onResourceReady 方法中,如果设置了过渡动画,会调用 transition 方法触发动画:

java

@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
        setResourceInternal(resource);
    } else {
        maybeUpdateAnimatable(resource);
    }
}

如果过渡动画执行成功,会调用 maybeUpdateAnimatable 方法更新动画资源;否则,会直接调用 setResourceInternal 方法设置资源。

三、Glide 回调模块详细分析

3.1 回调监听器注册

3.1.1 RequestListener 接口

RequestListener 接口用于监听图片加载的状态。以下是 RequestListener 接口的代码:

java

// RequestListener 接口用于监听图片加载的状态
public interface RequestListener<R> {
    // 当图片加载失败时调用
    boolean onLoadFailed(@Nullable GlideException e, Object model, Target<R> target, boolean isFirstResource);
    // 当图片加载成功时调用
    boolean onResourceReady(R resource, Object model, Target<R> target, DataSource dataSource, boolean isFirstResource);
}

onLoadFailed 方法在图片加载失败时调用,返回 true 表示该错误已经被处理,Glide 不会再调用 Target 对象的 onLoadFailed 方法;返回 false 表示该错误未被处理,Glide 会继续调用 Target 对象的 onLoadFailed 方法。onResourceReady 方法在图片加载成功时调用,返回 true 表示该资源已经被处理,Glide 不会再调用 Target 对象的 onResourceReady 方法;返回 false 表示该资源未被处理,Glide 会继续调用 Target 对象的 onResourceReady 方法。

3.1.2 注册回调监听器

在使用 Glide 加载图片时,可以通过 addListener 方法注册 RequestListener 监听器。以下是 RequestBuilder 类的 addListener 方法的代码:

java

// RequestBuilder 类的 addListener 方法,用于添加请求监听器
public RequestBuilder<TranscodeType> addListener(@NonNull RequestListener<TranscodeType> listener) {
    // 将监听器添加到监听器列表中
    this.requestListeners.add(listener);
    return this;
}

addListener 方法会将传入的 RequestListener 对象添加到请求的监听器列表中。这样,在图片加载的不同阶段,就会触发相应的回调方法。

3.2 回调触发流程

3.2.1 加载开始回调

在请求开始执行时,会调用 Target 对象的 onLoadStarted 方法,可用于显示加载中的占位图。以下是 SingleRequest 类的 begin 方法中触发加载开始回调的代码:

java

// SingleRequest 类的 begin 方法,用于开始执行请求
@Override
public void begin() {
    // 记录请求开始的时间,用于性能统计
    startTime = LogTime.getLogTime();
    if (model == null) {
        // 如果模型为空,则加载失败
        onLoadFailed(new GlideException("Received null model"), DataSource.LOCAL, false);
        return;
    }
    // 获取占位图
    placeholderDrawable = getPlaceholderDrawable();
    if (isFirstResource()) {
        // 如果是第一次加载资源,则显示占位图
        target.onLoadStarted(placeholderDrawable);
    }
    // 开始从缓存中加载资源
    loadFromCache();
}

在 begin 方法中,首先记录请求开始的时间,然后检查模型是否为空。如果模型为空,则调用 onLoadFailed 方法处理加载失败的情况。接着获取占位图,如果是第一次加载资源,则调用 Target 对象的 onLoadStarted 方法显示占位图。最后开始从缓存中加载资源。

3.2.2 加载失败回调

在图片加载失败时,会调用 RequestListener 的 onLoadFailed 方法和 Target 对象的 onLoadFailed 方法。以下是 SingleRequest 类的 onLoadFailed 方法的代码:

java

// SingleRequest 类的 onLoadFailed 方法,用于处理图片加载失败的情况
private void onLoadFailed(@NonNull GlideException e, @NonNull DataSource dataSource, boolean isFirstResource) {
    // 标记请求失败
    status = Status.FAILED;
    // 获取错误提示图
    Drawable errorDrawable = getErrorDrawable();
    boolean anyListenerHandledUpdatingTarget = false;
    // 遍历所有的请求监听器
    for (RequestListener<R> listener : requestListeners) {
        // 调用监听器的 onLoadFailed 方法
        anyListenerHandledUpdatingTarget |= listener.onLoadFailed(e, model, target, isFirstResource);
    }
    if (!anyListenerHandledUpdatingTarget) {
        // 如果没有监听器处理该错误,则调用 Target 对象的 onLoadFailed 方法
        target.onLoadFailed(errorDrawable);
    }
    // 通知请求管理器请求失败
    requestManager.onRequestFailed(this);
}

在 onLoadFailed 方法中,首先标记请求失败,然后获取错误提示图。接着遍历所有的请求监听器,调用它们的 onLoadFailed 方法。如果有监听器处理了该错误,anyListenerHandledUpdatingTarget 会被设置为 true;否则,会调用 Target 对象的 onLoadFailed 方法显示错误提示图。最后通知请求管理器请求失败。

3.2.3 加载成功回调

在图片加载成功时,会调用 RequestListener 的 onResourceReady 方法和 Target 对象的 onResourceReady 方法。以下是 SingleRequest 类的 onResourceReady 方法的代码:

java

// SingleRequest 类的 onResourceReady 方法,用于处理图片加载成功的情况
private void onResourceReady(@NonNull Resource<R> resource, @NonNull DataSource dataSource, boolean isFirstResource) {
    // 标记请求完成
    status = Status.COMPLETE;
    // 记录加载结束的时间,用于性能统计
    loadEndTime = LogTime.getLogTime();
    boolean anyListenerHandledUpdatingTarget = false;
    // 遍历所有的请求监听器
    for (RequestListener<R> listener : requestListeners) {
        // 调用监听器的 onResourceReady 方法
        anyListenerHandledUpdatingTarget |= listener.onResourceReady(resource.get(), model, target, dataSource, isFirstResource);
    }
    if (!anyListenerHandledUpdatingTarget) {
        // 如果没有监听器处理该资源,则调用 Target 对象的 onResourceReady 方法
        target.onResourceReady(resource.get(), getTransition(dataSource));
    }
    // 通知请求管理器请求成功
    requestManager.onRequestSuccess(this);
}

在 onResourceReady 方法中,首先标记请求完成,然后记录加载结束的时间。接着遍历所有的请求监听器,调用它们的 onResourceReady 方法。如果有监听器处理了该资源,anyListenerHandledUpdatingTarget 会被设置为 true;否则,会调用 Target 对象的 onResourceReady 方法将资源显示到目标视图上。最后通知请求管理器请求成功。

3.2.4 加载取消回调

在图片加载被取消时,会调用 Target 对象的 onLoadCleared 方法。以下是 SingleRequest 类的 cancel 方法中触发加载取消回调的代码:

java

// SingleRequest 类的 cancel 方法,用于取消请求
@Override
public void cancel() {
    if (isCancelled()) {
        return;
    }
    // 标记请求被取消
    status = Status.CANCELLED;
    // 取消正在进行的加载操作
    clear();
    // 调用 Target 对象的 onLoadCleared 方法
    target.onLoadCleared(placeholderDrawable);
}

在 cancel 方法中,首先检查请求是否已经被取消,如果是则直接返回。然后标记请求被取消,取消正在进行的加载操作,并调用 Target 对象的 onLoadCleared 方法清除目标视图上的图片资源,显示占位图。

四、显示与回调模块的协作

4.1 协作流程概述

显示模块和回调模块在 Glide 中紧密协作,共同完成图片的加载和显示过程。整个协作流程可以分为以下几个阶段:

  1. 请求发起阶段:开发者通过 Glide 的 API 发起图片加载请求,并指定目标视图和回调监听器。此时,显示模块负责绑定目标视图,回调模块负责注册监听器。

  2. 加载开始阶段:请求开始执行,显示模块调用 Target 对象的 onLoadStarted 方法显示占位图,回调模块记录请求开始的状态。

  3. 加载过程阶段:Glide 进行图片的加载和解码操作,这个过程中可能会触发缓存命中、网络请求等事件。

  4. 加载结果处理阶段

    • 加载成功:回调模块调用 RequestListener 的 onResourceReady 方法和 Target 对象的 onResourceReady 方法,将解码后的图片资源传递给显示模块进行显示。显示模块根据是否设置了过渡动画,将图片资源显示在目标视图上。
    • 加载失败:回调模块调用 RequestListener 的 onLoadFailed 方法和 Target 对象的 onLoadFailed 方法,显示错误提示图。
    • 加载取消:回调模块调用 Target 对象的 onLoadCleared 方法,清除目标视图上的图片资源。

4.2 资源传递与状态同步

在显示与回调模块的协作过程中,资源的传递和状态的同步是非常重要的。Request 对象作为请求的核心,负责管理请求的状态和资源的传递。在请求的不同阶段,Request 对象会更新自身的状态,并将资源传递给相应的回调方法和 Target 对象。例如,在图片加载成功时,Request 对象会将解码后的 Resource 对象传递给 RequestListener 的 onResourceReady 方法和 Target 对象的 onResourceReady 方法,确保显示模块能够正确显示图片资源。同时,Request 对象的状态(如 Status.FAILEDStatus.COMPLETE 等)会被及时更新,以便回调模块和显示模块根据状态进行相应的处理。

4.2 资源传递与状态同步

4.2.1 Request 对象的状态管理

Request 接口定义了请求的生命周期方法,而 SingleRequest 是其主要的实现类,负责管理请求的各个阶段状态。以下是 SingleRequest 类中与状态管理相关的部分代码:

java

// SingleRequest 类实现了 Request 接口,管理图片加载请求的生命周期
public class SingleRequest<R> implements Request {
    // 定义请求的各种状态
    public enum Status {
        PENDING, // 待处理状态
        RUNNING, // 正在运行状态
        COMPLETE, // 完成状态
        FAILED, // 失败状态
        CANCELLED // 取消状态
    }

    private Status status; // 当前请求的状态
    private Resource<R> resource; // 加载的资源

    @Override
    public void begin() {
        status = Status.RUNNING; // 开始请求,将状态设置为运行
        // 开始加载资源的相关操作
        loadFromCache();
    }

    private void onResourceReady(@NonNull Resource<R> resource, @NonNull DataSource dataSource, boolean isFirstResource) {
        status = Status.COMPLETE; // 资源加载成功,将状态设置为完成
        this.resource = resource;
        // 处理资源准备好后的逻辑
        boolean anyListenerHandledUpdatingTarget = false;
        for (RequestListener<R> listener : requestListeners) {
            anyListenerHandledUpdatingTarget |= listener.onResourceReady(resource.get(), model, target, dataSource, isFirstResource);
        }
        if (!anyListenerHandledUpdatingTarget) {
            target.onResourceReady(resource.get(), getTransition(dataSource));
        }
        requestManager.onRequestSuccess(this);
    }

    private void onLoadFailed(@NonNull GlideException e, @NonNull DataSource dataSource, boolean isFirstResource) {
        status = Status.FAILED; // 资源加载失败,将状态设置为失败
        // 处理加载失败的逻辑
        Drawable errorDrawable = getErrorDrawable();
        boolean anyListenerHandledUpdatingTarget = false;
        for (RequestListener<R> listener : requestListeners) {
            anyListenerHandledUpdatingTarget |= listener.onLoadFailed(e, model, target, isFirstResource);
        }
        if (!anyListenerHandledUpdatingTarget) {
            target.onLoadFailed(errorDrawable);
        }
        requestManager.onRequestFailed(this);
    }

    @Override
    public void cancel() {
        if (isCancelled()) {
            return;
        }
        status = Status.CANCELLED; // 取消请求,将状态设置为取消
        clear();
        target.onLoadCleared(placeholderDrawable);
    }

    @Override
    public boolean isRunning() {
        return status == Status.RUNNING;
    }

    @Override
    public boolean isComplete() {
        return status == Status.COMPLETE;
    }

    @Override
    public boolean isCancelled() {
        return status == Status.CANCELLED;
    }

    @Override
    public boolean isFailed() {
        return status == Status.FAILED;
    }
}

在上述代码中,SingleRequest 类通过 Status 枚举来管理请求的不同状态。在请求开始时,状态被设置为 RUNNING;当资源加载成功时,状态变为 COMPLETE;加载失败则设置为 FAILED;请求被取消时,状态为 CANCELLED。这些状态的更新会影响到后续的回调处理和资源显示操作。例如,只有在请求处于运行状态时,才会继续进行资源加载和处理;而在完成状态下,才会将资源传递给 Target 对象进行显示。

4.2.2 资源传递机制

资源的传递主要通过 Request 对象在不同模块之间进行。当图片加载和解码完成后,SingleRequest 会将解码后的 Resource 对象传递给回调方法和 Target 对象。以下是相关的代码逻辑:

java

// 在 SingleRequest 的 onResourceReady 方法中传递资源
private void onResourceReady(@NonNull Resource<R> resource, @NonNull DataSource dataSource, boolean isFirstResource) {
    // ... 状态更新和其他逻辑 ...
    for (RequestListener<R> listener : requestListeners) {
        listener.onResourceReady(resource.get(), model, target, dataSource, isFirstResource);
    }
    if (!anyListenerHandledUpdatingTarget) {
        target.onResourceReady(resource.get(), getTransition(dataSource));
    }
    // ... 其他逻辑 ...
}

在 onResourceReady 方法中,首先将资源传递给所有注册的 RequestListener,调用它们的 onResourceReady 方法。如果没有监听器处理该资源,则将资源传递给 Target 对象的 onResourceReady 方法,以便进行显示操作。这种资源传递机制确保了回调模块和显示模块能够获取到最新的加载结果,并进行相应的处理。

4.3 生命周期管理与协作

Glide 的显示与回调模块与 Android 组件的生命周期紧密相关,以确保资源的正确管理和避免内存泄漏。RequestManager 负责管理请求的生命周期,根据 Activity 或 Fragment 的生命周期事件来控制请求的执行和取消。

4.3.1 RequestManager 与生命周期关联

RequestManager 通过 Lifecycle 接口与 Activity 或 Fragment 的生命周期进行关联。以下是 RequestManager 中与生命周期相关的部分代码:

java

// RequestManager 类管理图片加载请求的生命周期
public class RequestManager implements LifecycleListener {
    private final Lifecycle lifecycle;
    private final RequestTracker requestTracker;

    public RequestManager(Glide glide, Lifecycle lifecycle, RequestManagerTreeNode treeNode, Context context) {
        this.glide = glide;
        this.lifecycle = lifecycle;
        this.requestTracker = new RequestTracker();
        // 注册生命周期监听器
        lifecycle.addListener(this);
    }

    @Override
    public void onStart() {
        resumeRequests(); // 恢复请求
    }

    @Override
    public void onStop() {
        pauseRequests(); // 暂停请求
    }

    @Override
    public void onDestroy() {
        requestTracker.clearRequests(); // 清除所有请求
        lifecycle.removeListener(this); // 移除生命周期监听器
    }

    public void resumeRequests() {
        requestTracker.resumeRequests();
    }

    public void pauseRequests() {
        requestTracker.pauseRequests();
    }

    public void clear(@NonNull Target<?> target) {
        Request request = target.getRequest();
        if (request != null) {
            request.cancel();
            requestTracker.removeRequest(request);
            target.setRequest(null);
        }
    }

    public void track(@NonNull Target<?> target, @NonNull Request request) {
        target.setRequest(request);
        requestTracker.runRequest(request);
    }
}

RequestManager 实现了 LifecycleListener 接口,当 Activity 或 Fragment 进入 onStart 状态时,RequestManager 会调用 resumeRequests 方法恢复所有暂停的请求;当进入 onStop 状态时,调用 pauseRequests 方法暂停所有正在运行的请求;当进入 onDestroy 状态时,调用 clearRequests 方法清除所有请求,并移除生命周期监听器。这种生命周期管理机制确保了在 Activity 或 Fragment 销毁时,所有未完成的请求都会被取消,避免了内存泄漏。

4.3.2 显示与回调模块在生命周期中的协作

在不同的生命周期阶段,显示与回调模块会协同工作。例如,在 Activity 或 Fragment 暂停时,显示模块会停止正在进行的过渡动画和资源显示操作,回调模块也会暂停相关的回调处理;而在恢复时,会继续之前的操作。以下是 SingleRequest 中与生命周期相关的部分代码:

java

// SingleRequest 类中处理生命周期相关的逻辑
@Override
public void pause() {
    if (isRunning()) {
        // 暂停请求,停止正在进行的加载操作
        clear();
        status = Status.PENDING;
    }
}

@Override
public void resume() {
    if (status == Status.PENDING) {
        // 恢复请求,继续加载操作
        begin();
    }
}

当 RequestManager 调用 pauseRequests 方法时,SingleRequest 的 pause 方法会被调用,将请求状态设置为 PENDING,并停止正在进行的加载操作;当调用 resumeRequests 方法时,SingleRequest 的 resume 方法会被调用,如果请求处于 PENDING 状态,则重新开始请求。这样,显示模块和回调模块会根据生命周期的变化,协同完成请求的暂停和恢复操作。

五、自定义显示与回调

5.1 自定义 Target 对象

开发者可以根据自己的需求自定义 Target 对象,以实现特殊的图片显示逻辑。以下是一个自定义 Target 对象的示例:

java

// 自定义 Target 对象,用于处理图片加载结果
public class CustomTarget implements Target<Drawable> {
    private final ImageView imageView;
    private Request request;

    public CustomTarget(ImageView imageView) {
        this.imageView = imageView;
    }

    @Override
    public void onLoadStarted(@Nullable Drawable placeholder) {
        // 显示加载中的占位图
        imageView.setImageDrawable(placeholder);
        // 可以添加自定义的加载开始逻辑,如显示加载进度条
        Log.d("CustomTarget", "Load started");
    }

    @Override
    public void onLoadFailed(@Nullable Drawable errorDrawable) {
        // 显示错误提示图
        imageView.setImageDrawable(errorDrawable);
        // 可以添加自定义的加载失败逻辑,如记录错误日志
        Log.e("CustomTarget", "Load failed");
    }

    @Override
    public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
        if (transition == null || !transition.transition(resource, new ViewAdapter() {
            @Override
            public Drawable getCurrentDrawable() {
                return imageView.getDrawable();
            }

            @Override
            public void setDrawable(Drawable drawable) {
                imageView.setImageDrawable(drawable);
            }
        })) {
            // 如果没有过渡动画或者过渡动画执行失败,则直接设置资源
            imageView.setImageDrawable(resource);
        }
        // 可以添加自定义的加载成功逻辑,如对图片进行二次处理
        Log.d("CustomTarget", "Load success");
    }

    @Override
    public void onLoadCleared(@Nullable Drawable placeholder) {
        // 清除目标视图上的图片资源
        imageView.setImageDrawable(placeholder);
        // 可以添加自定义的加载取消逻辑,如释放相关资源
        Log.d("CustomTarget", "Load cleared");
    }

    @Override
    public int getWidth() {
        return imageView.getWidth();
    }

    @Override
    public int getHeight() {
        return imageView.getHeight();
    }

    @Override
    public void onStart() {
        // 目标视图开始时的处理逻辑,如恢复动画
        Log.d("CustomTarget", "Target started");
    }

    @Override
    public void onStop() {
        // 目标视图停止时的处理逻辑,如暂停动画
        Log.d("CustomTarget", "Target stopped");
    }

    @Override
    public void onDestroy() {
        // 目标视图销毁时的处理逻辑,如取消请求
        if (request != null) {
            request.cancel();
            request = null;
        }
        Log.d("CustomTarget", "Target destroyed");
    }

    @Override
    public void setRequest(@Nullable Request request) {
        this.request = request;
    }

    @Nullable
    @Override
    public Request getRequest() {
        return request;
    }
}

在上述代码中,CustomTarget 实现了 Target 接口的所有方法,并在每个方法中添加了自定义的日志记录逻辑。在 onLoadStarted 方法中,可以显示加载进度条;在 onLoadFailed 方法中,可以记录错误日志;在 onResourceReady 方法中,可以对图片进行二次处理;在 onLoadCleared 方法中,可以释放相关资源。同时,需要实现 setRequest 和 getRequest 方法,以便与请求管理器进行交互。

5.2 自定义 RequestListener 对象

开发者还可以自定义 RequestListener 对象,以实现特殊的回调逻辑。以下是一个自定义 RequestListener 对象的示例:

java

// 自定义 RequestListener 对象,用于监听图片加载状态
public class CustomRequestListener implements RequestListener<Drawable> {
    @Override
    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
        // 处理图片加载失败的情况
        Log.e("CustomRequestListener", "Image load failed: " + e.getMessage());
        // 可以添加自定义的错误处理逻辑,如重试机制
        if (shouldRetry(e)) {
            // 重试加载图片
            Glide.with(target.getView().getContext())
                   .load(model)
                   .addListener(this)
                   .into((ImageView) target.getView());
            return true;
        }
        return false;
    }

    @Override
    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
        // 处理图片加载成功的情况
        Log.d("CustomRequestListener", "Image load success");
        // 可以添加自定义的成功处理逻辑,如统计加载时间
        long loadTime = System.currentTimeMillis() - getStartTime(model);
        Log.d("CustomRequestListener", "Image load time: " + loadTime + "ms");
        return false;
    }

    private boolean shouldRetry(GlideException e) {
        // 判断是否需要重试,这里可以根据具体的错误类型进行判断
        return e.getRootCauses().size() > 0 && e.getRootCauses().get(0) instanceof IOException;
    }

    private long getStartTime(Object model) {
        // 获取图片开始加载的时间,这里可以使用一个 Map 来存储每个模型的开始时间
        // 为了简化示例,这里直接返回一个固定值
        return System.currentTimeMillis() - 1000;
    }
}

在 CustomRequestListener 中,onLoadFailed 方法会记录加载失败的错误信息,并根据错误类型判断是否需要重试加载;onResourceReady 方法会记录加载成功的信息,并统计加载时间。通过自定义 RequestListener 对象,开发者可以灵活地处理图片加载的不同状态。

5.3 自定义过渡动画

除了自定义 Target 和 RequestListener,还可以自定义过渡动画。要自定义过渡动画,需要实现 Transition 接口。以下是一个自定义过渡动画的示例:

java

// 自定义过渡动画类,实现淡入效果
public class CustomFadeTransition implements Transition<Drawable> {
    private static final int DURATION = 500; // 动画持续时间

    @Override
    public boolean transition(Drawable current, ViewAdapter adapter) {
        Drawable previous = adapter.getCurrentDrawable();
        if (previous == null) {
            // 如果之前没有 Drawable,直接设置当前 Drawable
            adapter.setDrawable(current);
            return true;
        }
        // 创建一个 AlphaAnimation 实现淡入效果
        AlphaAnimation animation = new AlphaAnimation(0f, 1f);
        animation.setDuration(DURATION);
        animation.setFillAfter(true);
        current.setAlpha(0);
        adapter.setDrawable(current);
        current.startAnimation(animation);
        return true;
    }
}

在 CustomFadeTransition 中,transition 方法会创建一个 AlphaAnimation 实现淡入效果。如果之前没有 Drawable,则直接设置当前 Drawable;否则,将当前 Drawable 的透明度从 0 渐变到 1。开发者可以根据自己的需求实现不同的过渡动画效果。

六、性能优化与注意事项

6.1 性能优化

6.1.1 合理使用缓存

Glide 提供了强大的缓存机制,包括内存缓存和磁盘缓存。合理使用缓存可以减少图片的重复加载,提高加载速度。可以通过 diskCacheStrategy() 方法来配置磁盘缓存策略,例如:

java

Glide.with(context)
     .load(imageUrl)
     .diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存所有版本的图片
     .into(imageView);

DiskCacheStrategy.ALL 会缓存原始图片和处理后的图片,下次加载时可以直接从缓存中获取,避免了重复的网络请求和解码操作。

6.1.2 控制图片尺寸

在加载图片时,要根据目标视图的大小来控制图片的尺寸,避免加载过大的图片导致内存占用过高。可以使用 override() 方法来指定图片的尺寸,例如:

java

Glide.with(context)
     .load(imageUrl)
     .override(200, 200) // 指定图片的宽度和高度
     .into(imageView);

这样 Glide 会在解码图片时将图片缩放至指定的尺寸,减少内存占用。

6.1.3 优化过渡动画

过渡动画可以增强用户体验,但如果使用不当,会影响性能。要选择简单且轻量级的动画效果,避免使用过于复杂的动画。同时,要合理设置动画的持续时间,避免过长的动画导致用户等待时间过长。例如,在使用 CrossFadeTransition 时,可以适当调整动画的持续时间:

java

Glide.with(context)
     .load(imageUrl)
     .transition(withCrossFade(200)) // 设置淡入淡出动画的持续时间为 200 毫秒
     .into(imageView);

6.1.4 及时取消请求

在目标视图(如 ActivityFragment)销毁时,要及时取消未完成的图片加载请求,避免内存泄漏和资源浪费。Glide 会自动处理 Activity 和 Fragment 的生命周期,但在自定义视图中,需要手动调用 Glide.with(context).clear(target) 方法取消请求。例如:

java

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    Glide.with(getContext()).clear(this);
}

6.2 注意事项

6.2.1 内存管理

在使用 Glide 时,要注意内存的使用情况,避免出现内存泄漏和 OOM(Out of Memory)错误。及时回收不再使用的 Bitmap 对象,使用 BitmapPool 进行 Bitmap 对象的复用。同时,要注意图片的尺寸和质量,避免加载过大的图片导致内存占用过高。

6.2.2 线程安全

Glide 的显示与回调模块涉及到多线程操作,因此要确保代码的线程安全。在回调方法中,要避免对共享资源进行并发访问,以免出现数据竞争和不一致的问题。如果需要对共享资源进行操作,可以使用同步机制(如 synchronized 关键字)来保证线程安全。

6.2.3 兼容性问题

不同的 Android 版本和设备可能对图片解码和显示的支持有所不同。在开发过程中,要进行充分的测试,确保在各种设备和版本上都能正常工作。同时,要注意处理一些特殊情况,如某些设备对特定图片格式的支持问题,或某些版本的 Android 系统对动画效果的兼容性问题。

6.2.4 网络请求管理

如果图片是从网络加载的,要注意网络请求的管理。可以使用 priority() 方法来设置请求的优先级,确保重要的图片优先加载。同时,要处理好网络异常情况,如网络连接中断、服务器错误等,避免应用崩溃。例如:

java

Glide.with(context)
     .load(imageUrl)
     .priority(Priority.HIGH) // 设置请求的优先级为高
     .error(R.drawable.error_image) // 设置错误图片
     .into(imageView);

七、总结

Glide 的显示与回调模块是其实现高效图片加载和显示的核心部分。显示模块通过目标视图绑定、图片加载与解码、资源显示和过渡动画执行等步骤,将图片资源准确无误地显示在目标视图上。回调模块则通过注册监听器和触发回调方法,让开发者能够实时掌握图片加载的状态,根据不同的状态进行相应的处理。

显示与回调模块之间紧密协作,通过 Request 对象进行资源传递和状态同步,同时与 Android 组件的生命周期进行关联,确保资源的正确管理和避免内存泄漏。开发者可以通过自定义 Target 对象、RequestListener 对象和过渡动画,实现个性化的显示和回调逻辑。

在使用 Glide 时,要注意性能优化和相关注意事项,如合理使用缓存、控制图片尺寸、优化过渡动画、及时取消请求等,以提高应用的性能和稳定性。通过深入理解 Glide 的显示与回调模块原理,开发者可以更好地利用 Glide 的功能,为用户提供更好的图片加载体验。

以上的分析涵盖了 Glide 显示与回调模块的各个方面,从源码级别深入剖析了其工作原理和实现细节,希望能帮助开发者更好地理解和使用 Glide。