一、引言
在当今的 Android 应用开发中,图片处理是一个至关重要的环节。从应用的图标展示到复杂的图片画廊,图片的加载和显示直接影响着用户体验。Glide 作为一款功能强大且广泛使用的图片加载库,凭借其高效的性能、丰富的功能和简洁的 API,成为了开发者的首选。其中,显示与回调模块更是 Glide 的核心部分,它负责将加载好的图片资源准确无误地显示在目标视图上,并在整个过程中提供各种回调机制,让开发者能够实时掌握图片加载的状态。
在接下来的内容中,我们将深入 Glide 的源码,详细剖析显示与回调模块的工作原理。从目标视图的绑定到图片的加载、解码,再到资源的显示和过渡动画的执行,每一个步骤都将进行细致的分析,并结合具体的代码片段进行解释。同时,我们也会探讨回调模块是如何工作的,包括加载开始、失败、成功和取消等不同状态下的回调处理。此外,还会介绍如何自定义显示和回调逻辑,以及一些性能优化和注意事项。通过对 Glide 显示与回调模块的深入理解,开发者可以更好地利用 Glide 的功能,优化应用的图片加载流程,提升用户体验。
二、Glide 显示模块详细分析
2.1 目标视图绑定流程
2.1.1 Glide.with()
方法
当我们开始使用 Glide 加载图片时,首先会调用 Glide.with()
方法。这个方法的主要作用是获取一个 RequestManager
对象,该对象负责管理图片加载请求的生命周期。以下是 Glide.with()
方法的几种常见重载形式:
java
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
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
会根据不同的上下文(如 Activity
、Fragment
)来管理图片加载请求的生命周期,确保在上下文销毁时自动取消未完成的请求,避免内存泄漏。
2.1.2 load()
方法
在获取到 RequestManager
对象后,我们会调用 load()
方法来指定要加载的图片资源。load()
方法有多种重载形式,支持加载不同类型的资源,如网络图片、本地文件、资源 ID 等。以下是一些常见的 load()
方法实现:
java
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 中紧密协作,共同完成图片的加载和显示过程。整个协作流程可以分为以下几个阶段:
-
请求发起阶段:开发者通过 Glide 的 API 发起图片加载请求,并指定目标视图和回调监听器。此时,显示模块负责绑定目标视图,回调模块负责注册监听器。
-
加载开始阶段 :请求开始执行,显示模块调用
Target
对象的onLoadStarted
方法显示占位图,回调模块记录请求开始的状态。 -
加载过程阶段:Glide 进行图片的加载和解码操作,这个过程中可能会触发缓存命中、网络请求等事件。
-
加载结果处理阶段:
- 加载成功 :回调模块调用
RequestListener
的onResourceReady
方法和Target
对象的onResourceReady
方法,将解码后的图片资源传递给显示模块进行显示。显示模块根据是否设置了过渡动画,将图片资源显示在目标视图上。 - 加载失败 :回调模块调用
RequestListener
的onLoadFailed
方法和Target
对象的onLoadFailed
方法,显示错误提示图。 - 加载取消 :回调模块调用
Target
对象的onLoadCleared
方法,清除目标视图上的图片资源。
- 加载成功 :回调模块调用
4.2 资源传递与状态同步
在显示与回调模块的协作过程中,资源的传递和状态的同步是非常重要的。Request
对象作为请求的核心,负责管理请求的状态和资源的传递。在请求的不同阶段,Request
对象会更新自身的状态,并将资源传递给相应的回调方法和 Target
对象。例如,在图片加载成功时,Request
对象会将解码后的 Resource
对象传递给 RequestListener
的 onResourceReady
方法和 Target
对象的 onResourceReady
方法,确保显示模块能够正确显示图片资源。同时,Request
对象的状态(如 Status.FAILED
、Status.COMPLETE
等)会被及时更新,以便回调模块和显示模块根据状态进行相应的处理。
4.2 资源传递与状态同步
4.2.1 Request
对象的状态管理
Request
接口定义了请求的生命周期方法,而 SingleRequest
是其主要的实现类,负责管理请求的各个阶段状态。以下是 SingleRequest
类中与状态管理相关的部分代码:
java
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
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
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
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
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
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
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
java
Glide.with(context)
.load(imageUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存所有版本的图片
.into(imageView);
DiskCacheStrategy.ALL
会缓存原始图片和处理后的图片,下次加载时可以直接从缓存中获取,避免了重复的网络请求和解码操作。
6.1.2 控制图片尺寸
在加载图片时,要根据目标视图的大小来控制图片的尺寸,避免加载过大的图片导致内存占用过高。可以使用 override()
方法来指定图片的尺寸,例如:
java
java
Glide.with(context)
.load(imageUrl)
.override(200, 200) // 指定图片的宽度和高度
.into(imageView);
这样 Glide 会在解码图片时将图片缩放至指定的尺寸,减少内存占用。
6.1.3 优化过渡动画
过渡动画可以增强用户体验,但如果使用不当,会影响性能。要选择简单且轻量级的动画效果,避免使用过于复杂的动画。同时,要合理设置动画的持续时间,避免过长的动画导致用户等待时间过长。例如,在使用 CrossFadeTransition
时,可以适当调整动画的持续时间:
java
java
Glide.with(context)
.load(imageUrl)
.transition(withCrossFade(200)) // 设置淡入淡出动画的持续时间为 200 毫秒
.into(imageView);
6.1.4 及时取消请求
在目标视图(如 Activity
、Fragment
)销毁时,要及时取消未完成的图片加载请求,避免内存泄漏和资源浪费。Glide 会自动处理 Activity
和 Fragment
的生命周期,但在自定义视图中,需要手动调用 Glide.with(context).clear(target)
方法取消请求。例如:
java
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
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。