记一次Glide的错误配置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://weilu.blog.csdn.net/article/details/83959518

1.问题

我们知道Glide默认使用的是HttpUrlConnection的方式请求网络获取图片,具体实现参见HttpUrlFetcher 类。

OkHttp的出现就是用于替代HttpUrlConnectionHttpClient,它的高效与强大我就不多说了,官方文档如下:

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 来加载所有来自 httphttps 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后面的版本去除掉了这段处理。

在这里插入图片描述

这里主要是注意及时升级GlideOkHttp版本,以免这类bug出现在你的项目中。同样的,多关注你项目中所使用依赖库的更新与变化,及时更新。可以避免一些同类问题,同时增长经验

3.拓展

分享我前几天看到的一篇博客:pthread_create ——我与华为线程的争斗,希望给你带来一些思考。

最后,如果本文对你有所帮助,希望点赞支持!!

展开阅读全文

没有更多推荐了,返回首页