在接下来的几篇文章中,咱们会对 Android 中经常使用的图片加载框架 Glide 进行分析。在本篇文章中,咱们先经过介绍 Glide 的几种经常使用的配置方式来了解 Glide 的部分源码。后续的文中,咱们会对 Glide 的源码进行更详尽的分析。java
对于 Glide,相信多数 Android 开发者并不陌生,在本文中,咱们不打算对其具体使用作介绍,你能够经过查看官方文档进行学习。Glide 的 API 设计很是人性化,上手也很容易。git
在这篇文中中咱们主要介绍两种经常使用的 Glide 的配置方式,并以此为基础来分析 Glide 的工做原理。在本文中咱们将会介绍的内容有:github
有时候,咱们须要对 Glide 进行配置来使其可以对特殊类型的图片进行加载和缓存。考虑这么一个场景:图片路径中带有时间戳。这种情形比较场景,即有时候咱们经过为图片设置时间戳来让图片连接在指定的时间事后失效,从而达到数据保护的目的。api
在这种状况下,咱们须要解决几个问题:1).须要配置缓存的 key,否则缓存没法命中,每次都须要从网络中进行获取;2).根据正确的连接,从网络中获取图片并展现。缓存
咱们可使用自定义配置 Glide 的方式来解决这个问题。网络
首先,按照下面的方式自定义 GlideModule
,架构
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
/** * 配置图片缓存的路径和缓存空间的大小 */
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, Constants.DISK_CACHE_DIR, 100 << 20));
}
/** * 注册指定类型的源数据,并指定它的图片加载所使用的 ModelLoader */
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
glide.getRegistry().append(CachedImage.class, InputStream.class, new ImageLoader.Factory());
}
/** * 是否启用基于 Manifest 的 GlideModule,若是没有在 Manifest 中声明 GlideModule,能够经过返回 false 禁用 */
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
复制代码
在上面的代码中,咱们经过覆写 registerComponents()
方法,并调用 Glide 的 Registry
的 append()
方法来向 Glide 增长咱们的自定义图片类型的加载方式。(若是替换某种资源加载方式则须要使用 replace()
方法,此外 Registry
还有其余的方法,能够经过查看源码进行了解。)app
在上面的方法中,咱们新定义了两个类,分别是 CachedImage
和 ImageLoader
。CachedImage
就是咱们的自定义资源类型,ImageLoader
是该资源类型的加载方式。当进行图片加载的时候,会根据资源的类型找到该图片加载方式,而后使用它来进行图片加载。框架
咱们经过该类的构造方法将原始的图片的连接传入,并经过该类的 getImageId()
方法来返回图片缓存的键,在该方法中咱们从图片连接中过滤掉时间戳:ide
public class CachedImage {
private final String imageUrl;
public CachedImage(String imageUrl) {
this.imageUrl = imageUrl;
}
/** * 原始的图片的 url,用来从网络中加载图片 */
public String getImageUrl() {
return imageUrl;
}
/** * 提取时间戳以前的部分做为图片的 key,这个 key 将会被用做缓存的 key,并用来从缓存中找缓存数据 */
public String getImageId() {
if (imageUrl.contains("?")) {
return imageUrl.substring(0, imageUrl.lastIndexOf("?"));
} else {
return imageUrl;
}
}
}
复制代码
CachedImage
的加载经过 ImageLoader
实现。正如上面所说的,咱们将 CachedImage
的 getImageId()
方法获得的字符串做为缓存的键,而后使用默认的 HttpUrlFetcher
做为图片的加载方式。
public class ImageLoader implements ModelLoader<CachedImage, InputStream> {
/** * 在这个方法中,咱们使用 ObjectKey 来设置图片的缓存的键 */
@Override
public LoadData<InputStream> buildLoadData(CachedImage cachedImage, int width, int height, Options options) {
return new LoadData<>(new ObjectKey(cachedImage.getImageId()),
new HttpUrlFetcher(new GlideUrl(cachedImage.getImageUrl()), 15000));
}
@Override
public boolean handles(CachedImage cachedImage) {
return true;
}
public static class Factory implements ModelLoaderFactory<CachedImage, InputStream> {
@Override
public ModelLoader<CachedImage, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new ImageLoader();
}
@Override
public void teardown() { /* no op */ }
}
}
复制代码
当咱们按照上面的方式配置完毕以后就能够在项目中使用 CachedImage
来加载图片了:
GlideApp.with(getContext())
.load(new CachedImage(user.getAvatarUrl()))
.into(getBinding().ivAccount);
复制代码
这里,当有加载图片需求的时候,都会把原始的图片连接使用 CachedImage
包装一层以后再进行加载,其余的步骤与 Glide 的基本使用方式一致。
当咱们启用了 @GlideModule
注解以后会在编译期间生成 GeneratedAppGlideModuleImpl
。从下面的代码中能够看出,它实际上就是对咱们自定义的 MyAppGlideModule
作了一层包装。这么去作的目的就是它能够经过反射来寻找 GeneratedAppGlideModuleImpl
,并经过调用 GeneratedAppGlideModuleImpl
的方法来间接调用咱们的 MyAppGlideModule
。本质上是一种代理模式的应用:
final class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {
private final MyAppGlideModule appGlideModule;
GeneratedAppGlideModuleImpl() {
appGlideModule = new MyAppGlideModule();
}
@Override
public void applyOptions(Context context, GlideBuilder builder) {
appGlideModule.applyOptions(context, builder);
}
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
appGlideModule.registerComponents(context, glide, registry);
}
@Override
public boolean isManifestParsingEnabled() {
return appGlideModule.isManifestParsingEnabled();
}
@Override
public Set<Class<?>> getExcludedModuleClasses() {
return Collections.emptySet();
}
@Override
GeneratedRequestManagerFactory getRequestManagerFactory() {
return new GeneratedRequestManagerFactory();
}
}
复制代码
下面就是 GeneratedAppGlideModuleImpl
被用到的地方:
当咱们实例化单例的 Glide 的时候,会调用下面的方法来经过反射获取该实现类(因此对生成类的混淆就是必不可少的):
Class<GeneratedAppGlideModule> clazz = (Class<GeneratedAppGlideModule>)
Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
复制代码
当获得了以后会调用 GeneratedAppGlideModule
的各个方法。这样咱们的自定义 GlideModule
的方法就被触发了。(下面的方法比较重要,咱们自定义 Glide 的时候许多的配置都可以从下面的源码中寻找到答案,后文中咱们仍然会提到这个方法)
private static void initializeGlide(Context context, GlideBuilder builder) {
Context applicationContext = context.getApplicationContext();
// 利用反射获取 GeneratedAppGlideModuleImpl
GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();
// 从 Manifest 中获取 GlideModule
List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
manifestModules = new ManifestParser(applicationContext).parse();
}
// 获取被排除掉的 GlideModule
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
Set<Class<?>> excludedModuleClasses = annotationGeneratedModule.getExcludedModuleClasses();
Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
while (iterator.hasNext()) {
com.bumptech.glide.module.GlideModule current = iterator.next();
if (!excludedModuleClasses.contains(current.getClass())) {
continue;
}
iterator.remove();
}
}
// 应用 GlideModule,咱们自定义 GlideModuel 的方法会在这里被调用
RequestManagerRetriever.RequestManagerFactory factory = annotationGeneratedModule != null
? annotationGeneratedModule.getRequestManagerFactory() : null;
builder.setRequestManagerFactory(factory);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.applyOptions(applicationContext, builder);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.applyOptions(applicationContext, builder);
}
// 构建 Glide 对象
Glide glide = builder.build(applicationContext);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.registerComponents(applicationContext, glide, glide.registry);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
}
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;
}
复制代码
再回到以前的自定义 GlideModule 部分代码中:
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, Constants.DISK_CACHE_DIR, 100 << 20));
}
复制代码
这里的 applyOptions()
方法容许咱们对 Glide 进行自定义。从 initializeGlide()
方法中,咱们也看出,这里的 GlideBuilder
也就是 initializeGlide()
方法中传入的 GlideBuilder
。这里使用了构建者模式,GlideBuilder
是构建者的实例。因此,咱们能够经过调用 GlideBuilder
的方法来对 Glide 进行自定义。
在上面的自定义 GlideModule 中,咱们经过构建者来指定了 Glide 的缓存大小和缓存路径。 GlideBuilder
还提供了一些其余的方法,咱们能够经过查看源码了解,并调用这些方法来自定义 Glide.
Glide 默认使用 HttpURLConnection
实现网络当中的图片的加载。咱们能够经过对 Glide 进行配置来使用 OkHttp 进行网络图片加载。
首先,咱们须要引用以下依赖:
api ('com.github.bumptech.glide:okhttp3-integration:4.8.0') {
transitive = false
}
复制代码
该类库中提供了基于 OkHttp 的 ModelLoader
和 DataFetcher
实现。它们是 Glide 图片加载环节中的重要组成部分,咱们会在后面介绍源码和 Glide 的架构的时候介绍它们被设计的意图及其做用。
而后,咱们须要在自定义的 GlideModule
中注册网络图片加载须要的组件,即在 registerComponents()
方法中替换 GlideUrl
的加载的默认实现:
@GlideModule
@Excludes(value = {com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule.class})
public class MyAppGlideModule extends AppGlideModule {
private static final String DISK_CACHE_DIR = "Glide_cache";
private static final long DISK_CACHE_SIZE = 100 << 20; // 100M
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, DISK_CACHE_DIR, DISK_CACHE_SIZE));
}
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.eventListener(new EventListener() {
@Override
public void callStart(Call call) {
// 输出日志,用于确认使用了咱们配置的 OkHttp 进行网络请求
LogUtils.d(call.request().url().toString());
}
})
.build();
registry.replace(GlideUrl.class, InputStream.class, new Factory(okHttpClient));
}
@Override
public boolean isManifestParsingEnabled() {
// 不使用 Manifest 中的 GlideModule
return false;
}
}
复制代码
这样咱们经过本身的配置指定网络中图片加载须要使用 OkHttp. 而且自定义了 OkHttp 的超时时间等参数。按照上面的方式咱们能够在 Glide 中使用 OkHttp 来加载网络中的图片了。
不过,当咱们在项目中引用了 okhttp3-integration
的依赖以后,不进行上述配置同样可使用 OkHttp 来进行网络图片加载的。这是由于上述依赖的包中已经提供了一个自定义的 GlideModule,即 OkHttpLibraryGlideModule
。该类使用了 @GlideModule
注解,而且已经指定了网络图片加载使用 OkHttp。因此,当咱们不自定义 GlideModule 的时候,只使用它同样能够在 Glide 中使用 OkHttp.
若是咱们使用了自定义的 GlideModule,当咱们编译的时候会看到 GeneratedAppGlideModuleImpl
中的 registerComponents()
方法定义以下:
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
new OkHttpLibraryGlideModule().registerComponents(context, glide, registry);
appGlideModule.registerComponents(context, glide, registry);
}
复制代码
这里先调用了 OkHttpLibraryGlideModule
的 registerComponents()
方法,而后调用了咱们自定义的 GlideModule 的 registerComponents()
方法,只是,咱们的 GlideModule 的 registerComponents()
方法会覆盖掉 OkHttpLibraryGlideModule
中的实现。(由于咱们的 GlideModule 的 registerComponents()
方法中调用的是 Registry
的 replace()
方法,会替换以前的效果。)
若是不但愿画蛇添足,咱们能够直接在自定义的 GlideModule 中使用 @Excludes
注解,并指定 OkHttpLibraryGlideModule
来直接排除该类。这样 GeneratedAppGlideModuleImpl
中的 registerComponents()
方法将只使用咱们自定义的 GlideModule. 如下是排除以后生成的类中 registerComponents()
方法的实现:
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
appGlideModule.registerComponents(context, glide, registry);
}
复制代码
在本文中,咱们经过介绍 Glide 的两种常见的配置方式来分析了 Glide 的部分源码实现。在这部分中,咱们重点介绍了初始化 Glide 的并获取 GlideModule
的过程,以及与图片资源的时候相关的 ModelLoader
等的源码。了解这部份内容是比较重要的,由于它们是暴露给用户的 API 接口,比较经常使用;而且对这些类简单了解以后可以不至于在随后分析 Glide 整个加载流程的时候迷路。
这里咱们对上面两种配置方式中涉及到的类进行一个分析。以下图所示
当咱们初始化 Glide 的时候会使用 Registry
的 append()
等一系列的方法构建资源类型-加载方式-输出类型
的一个映射,而后当咱们使用 Glide 进行记载的时候,会先根据资源类型找到对应的加载方式,而后使用该加载方式从指定的数据源中加载数据,并将其转换成指定的输出类型。
以上面咱们自定义图片加载方式的过程为例,这里咱们自定义了一个资源类型 CacheImage
,并经过自定义 GlideModule 指定了它的加载实现是咱们自定义的 ImageLoader
类。而后,在咱们自定义的 ImageLoader 中,咱们指定了获取该资源的缓存的键的方式和从数据源中记载数据的具体实现 HttpUrlFetcher
。这样,当 Glide 要加载某个 CacheImage 的时候,会先使用该缓存的键尝试从缓存中获取,拿不到结果以后使用 HttpUrlFetcher
从网络当中获取数据。从网络中获取数据的时候会获得 InputStream,最后,再调用一个回调类,使用 BitmapFactory 从 InputStream 中获取 Bitmap 并将其显示到 ImageView 上面,这样就完成了整个图片加载的流程。
从上文的分析中,咱们能够总结出 Glide 的几个设计人性的地方:
资源类型-加载方式-输出类型
映射的时候使用工厂方法而不是经过某个类创建一对一映射。上面咱们经过 Glide 的几种配置方式简单介绍了 Glide 的图片加载流程。其实际的执行过程远比咱们上述过程更加复杂。在下文中咱们会对 Glide 的图片加载的主流程进行分析。欢迎继续关注和阅读!
若是您喜欢个人文章,能够在如下平台关注我:
更多文章:Gihub: Android-notes