1.问题
我们知道Glide
默认使用的是HttpUrlConnection
的方式请求网络获取图片,具体实现参见HttpUrlFetcher 类。
而OkHttp
的出现就是用于替代HttpUrlConnection
和HttpClient
,它的高效与强大我就不多说了,官方文档如下:
OkHttp 是一个底层网络库(相较于 Cronet 或 Volley 而言),尽管它也包含了 SPDY 的支持。OkHttp 与 Glide 一起使用可以提供可靠的性能,并且在加载图片时通常比 Volley 产生的垃圾要少。对于那些想要使用比 Android 提供的 HttpUrlConnection 更 nice 的 API,或者想确保网络层代码不依赖于 app 安装的设备上 Android OS 版本的应用,OkHttp 是一个合理的选择。如果你已经在 app 中某个地方使用了 OkHttp ,这也是选择继续为 Glide 使用 OkHttp 的一个很好的理由,就像选择其他网络库一样。
当然替换起来很简单,添加一个对 OkHttp
集成库的依赖:
compile "com.github.bumptech.glide:okhttp3-integration:4.8.0"
添加 OkHttp
集成库的Gradle
依赖将使 Glide
自动开始使用 OkHttp
来加载所有来自 http
和 https URL
的图片。
okhttp3-integration
库中OkHttpUrlLoader
类源码如下,可以看到默认创建了一个OkHttpClient
。
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private final Call.Factory client;
// Public API.
@SuppressWarnings("WeakerAccess")
public OkHttpUrlLoader(@NonNull Call.Factory client) {
this.client = client;
}
@Override
public boolean handles(@NonNull GlideUrl url) {
return true;
}
@Override
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
return new LoadData<>(model, new OkHttpStreamFetcher(client, model));
}
/**
* The default factory for {@link OkHttpUrlLoader}s.
*/
// Public API.
@SuppressWarnings("WeakerAccess")
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile Call.Factory internalClient;
private final Call.Factory client;
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();// <--- 这里创建
}
}
}
return internalClient;
}
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*
* @param client this is typically an instance of {@code OkHttpClient}.
*/
public Factory(@NonNull Call.Factory client) {
this.client = client;
}
@NonNull
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new OkHttpUrlLoader(client);
}
@Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
}
到这里你以为就结束了吗?我只能说上面的只是正常操作,能不能优化一下?当然可以。首先就是默认创建的OkHttpClient
,我们完全可以使用我们请求接口所使用的单例OkHttpClient
。当然了如果你的项目对图片与接口有着不同的要求,比如连接超时时间,甚至图片加载需要监听加载进度,可能还是要创建两个OkHttpClient
。这时我希望你可以将线程池共用起来,避免不必要的资源消耗。
我们的自定义配置:
@GlideModule
public class MyGlideModule extends AppGlideModule {
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.dispatcher(new Dispatcher(executorService)) // <-线程池
.connectTimeout(15, TimeUnit.SECONDS)
.addInterceptor(new ProgressInterceptor())
.build();
// 替换
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
}
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
类似这种的配置,我相信许多人也不陌生,不过这里有个小问题。我们看一下生成的代码:
final class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {
private final MyGlideModule appGlideModule;
GeneratedAppGlideModuleImpl() {
appGlideModule = new MyGlideModule();
}
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
appGlideModule.applyOptions(context, builder);
}
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
@NonNull Registry registry) {
new OkHttpLibraryGlideModule().registerComponents(context, glide, registry);
appGlideModule.registerComponents(context, glide, registry);
}
@Override
public boolean isManifestParsingEnabled() {
return appGlideModule.isManifestParsingEnabled();
}
@Override
@NonNull
public Set<Class<?>> getExcludedModuleClasses() {
return Collections.emptySet();
}
@Override
@NonNull
GeneratedRequestManagerFactory getRequestManagerFactory() {
return new GeneratedRequestManagerFactory();
}
}
在上面的代码registerComponents
方法中,有调用OkHttpLibraryGlideModule
类的registerComponents
方法。这是干什么的?我们接着看:
@GlideModule
public final class OkHttpLibraryGlideModule extends LibraryGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
@NonNull Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
new OkHttpUrlLoader.Factory()
不是又多创建了一个我们上面说到的默认OkHttpClient
。只不过我们自定义的靠后执行,再次的替换了这个默认OkHttpClient
。这就是我说的小问题,不知道你有没有中枪,反正我是中了,毕竟网上大多数的文章都是上述操作。。。我也是前几天才意识到了这个问题。
怎么解决这个问题,我又仔细看了下官方文档,找到了办法。(还是要好好看文档啊)
是的,使用@Excludes
注解,排除掉OkHttpLibraryGlideModule
。
@Excludes(value = OkHttpLibraryGlideModule.class)
@GlideModule
public class MyGlideModule extends AppGlideModule {
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
...
}
}
这个小问题,出现的原因是你有多个@GlideModule
,却没有排出重复功能@GlideModule
导致的。
2.引申
在研究上述问题的过程中,我注意到了一段代码(glide版本4.3.1)okhttp3-integration
库中OkHttpStreamFetcher
类的loadData
实现:
@Override
public void loadData(Priority priority, final DataCallback<? super InputStream> callback) {
call = client.newCall(request);
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
call.enqueue(this);
} else {
try {
// Calling execute instead of enqueue is a workaround for #2355, where okhttp throws a
// ClassCastException on O.
onResponse(call, call.execute());
} catch (IOException e) {
onFailure(call, e);
} catch (ClassCastException e) {
// It's not clear that this catch is necessary, the error may only occur even on O if
// enqueue is used.
onFailure(call, new IOException("Workaround for framework bug on O", e));
}
}
}
问题Exception in OkHttp3 library on O,当时4.3.1的版本默认引用的OkHttp
版本是3.9.0,这时,在Android O的设备上,会错误出现ClassCastException
异常。
java.lang.ClassCastException: android.system.UnixSocketAddress cannot be cast to java.net.InetSocketAddress
这个属于O版本的bug,所以判断系统版本,将捕获的ClassCastException
异常处理为IOException
异常。当然,OkHttp
在3.9.1也做了同样的处理,修复了这个问题,所以Glide
后面的版本去除掉了这段处理。
这里主要是注意及时升级Glide
与OkHttp
版本,以免这类bug出现在你的项目中。同样的,多关注你项目中所使用依赖库的更新与变化,及时更新。可以避免一些同类问题,同时增长经验。
3.拓展
分享我前几天看到的一篇博客:pthread_create ——我与华为线程的争斗,希望给你带来一些思考。
最后,如果本文对你有所帮助,希望点赞支持!!