Glide进阶详解(十)

来源:互联网 发布:英国的青年知乎 编辑:程序博客网 时间:2024/05/14 21:43

用 GlideModule 修改 Glide

在继续阅读前,请确保你已经阅读并理解了之前的博客 关于 GlideModule 的。我们不会在这个博客中继续说它的基础知识。相反,我们要跳过这个问题。所以确保你已经更新了你的 GlideModule 的基础知识。

你已经知道 GlideModule 提供给你两个方法去改变行为。上周,我们看了第一个方法 applyOptions()。这周我们会用另外一个方法 registerComponents(),去设置不同的网络库。默认情况下,Glide 内部使用了标准的 HTTPURLConnection 去下载图片。Glide 也提供了两个集合库。这三个都一个非常杨格的安全设置,这很好。唯一的缺点可能是当你的图片从服务端获取时,是使用 HTTPS,且是自签名的(self-signed)。这时 Glide 不会下载或显示图片了,因为自签名的证书被认为是一个安全的问题。

不安全的 OKHttpClient

因此,你需要去实现自己的网络栈,它接受自签名证书。幸运的是,我们之前已经实现了一个“不安全” 的 OKHttpClient。我们主要复制粘贴这个类。因为它给了我们一个常规的 OkHttpClient,我们这样子来集成:

public class UnsafeOkHttpClient {      public static OkHttpClient getUnsafeOkHttpClient() {        try {            // Create a trust manager that does not validate certificate chains            final TrustManager[] trustAllCerts = new TrustManager[]{                    new X509TrustManager() {                        @Override                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {                        }                        @Override                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {                        }                        @Override                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {                            return null;                        }                    }            };            // Install the all-trusting trust manager            final SSLContext sslContext = SSLContext.getInstance("SSL");            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());            // Create an ssl socket factory with our all-trusting manager            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();            OkHttpClient okHttpClient = new OkHttpClient();            okHttpClient.setSslSocketFactory(sslSocketFactory);            okHttpClient.setProtocols(Arrays.asList(Protocol.HTTP_1_1));            okHttpClient.setHostnameVerifier(new HostnameVerifier() {                @Override                public boolean verify(String hostname, SSLSession session) {                    return true;                }            });            return okHttpClient;        } catch (Exception e) {            throw new RuntimeException(e);        }    }}

创建 OkHttpClient 禁用掉所有的 SSL 证书检查。

整合到 Glide

我们的优势是,OkHttp 整合库为 Glide 做了几乎相同的事情,所以我们可以跟着他们走。首先,我们需要在 GlideModule 中声明我们的定制。正如你所期待的,我们要在 registerComponents() 方法中去做适配。我们可以调用 .register() 方法去改变 Glide 的基本部件。Glide 使用一个 GlideLoader 去链接数据模型到一个具体的数据类型。在我们的实例中,我们要去创建一个 ModeLoader,连接传入的 URL,通过 GlideUrl 类来代表一个 InputStream。Glide 要能创建一个我们的新的 ModeLoader,所以我们要在 .register() 方法中传递一个工厂。

public class UnsafeOkHttpGlideModule implements GlideModule {          @Override        public void applyOptions(Context context, GlideBuilder builder) {        }        @Override        public void registerComponents(Context context, Glide glide) {            glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());        }    }

前两个参数是 model 类,和连接的资源类。最后一个参数是 ModelLoaderFactory。因此,我们不能直接设置一个 UnsafeOkHttpClient 实例,我们需要去创建一个 ModelLoaderFactory,它用 UnsafeOkHttpClient 来提供了一个 URL 和输入流之前的连接。

再说一次,在 OkHttp 整合库 中给了我们一个很好的模板:

public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {    /**     * The default factory for {@link OkHttpUrlLoader}s.     */    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {        private static volatile OkHttpClient internalClient;        private OkHttpClient client;        private static OkHttpClient getInternalClient() {            if (internalClient == null) {                synchronized (Factory.class) {                    if (internalClient == null) {                        internalClient = UnsafeOkHttpClient.getUnsafeOkHttpClient();                    }                }            }            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.         */        public Factory(OkHttpClient client) {            this.client = client;        }        @Override        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {            return new OkHttpUrlLoader(client);        }        @Override        public void teardown() {            // Do nothing, this instance doesn't own the client.        }    }    private final OkHttpClient client;    public OkHttpUrlLoader(OkHttpClient client) {        this.client = client;    }    @Override    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {        return new OkHttpStreamFetcher(client, model);    }}

在这个类中,你可以看到 ModelLoaderFactory 的内部构造是怎样的。对我们来说,重要的代码是创建 internalClient 对象:internalClient = UnsafeOkHttpClient.getUnsafeOkHttpClient();

不幸的是,我们仍然需要用我们的不安全的 OKHttpClient 去连接 URL 激活输入流。因此,我们需要另外一个类去从一个 URL 中拉取返回的输入流:

public class OkHttpStreamFetcher implements DataFetcher<InputStream> {      private final OkHttpClient client;    private final GlideUrl url;    private InputStream stream;    private ResponseBody responseBody;    public OkHttpStreamFetcher(OkHttpClient client, GlideUrl url) {        this.client = client;        this.url = url;    }    @Override    public InputStream loadData(Priority priority) throws Exception {        Request.Builder requestBuilder = new Request.Builder()                .url(url.toStringUrl());        for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {            String key = headerEntry.getKey();            requestBuilder.addHeader(key, headerEntry.getValue());        }        Request request = requestBuilder.build();        Response response = client.newCall(request).execute();        responseBody = response.body();        if (!response.isSuccessful()) {            throw new IOException("Request failed with code: " + response.code());        }        long contentLength = responseBody.contentLength();        stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);        return stream;    }    @Override    public void cleanup() {        if (stream != null) {            try {                stream.close();            } catch (IOException e) {                // Ignored            }        }        if (responseBody != null) {            try {                responseBody.close();            } catch (IOException e) {                // Ignored.            }        }    }    @Override    public String getId() {        return url.getCacheKey();    }    @Override    public void cancel() {        // TODO: call cancel on the client when this method is called on a background thread. See #257    }}

不需要知道在这个类中所有的细节。然而,你应该对于这个系统有一个大概的理解,Glide 能去替换内部的工厂组件。

自定义内存缓存

希望你已经读了 caching basics 和 Glide modules 博客。否则,看下面的代码对你来说可能像魔术一般了。如果你读过了,那就继续读下去。

好吧,既然是定制 Glide,我们就需要创建一个新的 Glide module。就如你在以前博客中看到的那样,applyOptions 方法使我们获取了 GlideBuilder 对象。该 GlideBuilder 为我们提供了几个方法去定制 Glide 的缓存。首先,来看看内存缓存。

内存缓存是在设备的 RAM 中去维护图片的。这里没有 IO 行为,所以这个操作是很快的。另一方面是 RAM(内存) 的大小是非常有限的。寻找一个大内存缓存的平衡点(大量图像空间)与一个小内存缓存(最大限度减少我们 App 的资源消耗)并不容易。Glide 内部使用了 MemorySizeCalculator 类去决定内存缓存大小以及 bitmap 的缓存池。bitmap 池维护了你 App 的堆中的图像分配。正确的 bitmpa 池是非常必要的,因为它避免很多的图像重复回收,这样可以确保垃圾回收器的管理更加合理。

幸运的是,你已经得到了 Glide 的 MemorySizeCalculator 类以及默认的计算:

MemorySizeCalculator calculator = new MemorySizeCalculator(context);  int defaultMemoryCacheSize = calculator.getMemoryCacheSize();  int defaultBitmapPoolSize = calculator.getBitmapPoolSize();  

上面这段代码相当有用,如果我们想要用默认值作为基准,然后调整它。比如,如果你认为你的 app 需要 20% 大的缓存作为 Glide 的默认值,用我们上面的变量去计算他们:

int customMemoryCacheSize = (int) (1.2 * defaultMemoryCacheSize);  int customBitmapPoolSize = (int) (1.2 * defaultBitmapPoolSize);  

因为我们已经计算出了我们的内存缓存和 bitmap 池的大小,我们可以在我们的 Glide module 代码里去得到。在 applyOptions() 方法中,我们可以在 GlideBuilder 对象中调用相应的方法。

public class CustomCachingGlideModule implements GlideModule {      @Override public void applyOptions(Context context, GlideBuilder builder) {        MemorySizeCalculator calculator = new MemorySizeCalculator(context);        int defaultMemoryCacheSize = calculator.getMemoryCacheSize();        int defaultBitmapPoolSize = calculator.getBitmapPoolSize();        int customMemoryCacheSize = (int) (1.2 * defaultMemoryCacheSize);        int customBitmapPoolSize = (int) (1.2 * defaultBitmapPoolSize);        builder.setMemoryCache( new LruResourceCache( customMemoryCacheSize );        builder.setBitmapPool( new LruBitmapPool( customBitmapPoolSize );    }    @Override public void registerComponents(Context context, Glide glide) {        // nothing to do here    }}

正如你看到的,在 applyOptions() 方法的最后两行,我们不能直接设置大小。我们需要创建一个 LruResourceCache 和 LruBitmapPool 的实例。这两个都是 Glide 的默认实现。因此,如果你仅仅想要调整大小,就可以继续使用它们通过传两个不同的大小的值给构造函数。

自定义磁盘缓存

调整磁盘缓存和和刚才的很像,但是我们有一个更大的决定去做,磁盘缓存可以位于应用的私有目录(换句话说,除了它自己,没有别的应用可以访问)。否则,磁盘缓存也可以位于外部存储,公有目录(更多信息,请看 Storage Options)。不能一起设置这两个为之。Glide 为这两个选项都提供了它的实现:InternalCacheDiskCacheFactory 和 ExternalCacheDiskCacheFactory。就像内存缓存的构造函数一样,在它们的构造函数内都传一个磁盘缓存的工厂类:

public class CustomCachingGlideModule implements GlideModule {      @Override    public void applyOptions(Context context, GlideBuilder builder) {        // set size & external vs. internal        int cacheSize100MegaBytes = 104857600;        builder.setDiskCache(            new InternalCacheDiskCacheFactory(context, cacheSize100MegaBytes)        );        //builder.setDiskCache(        //new ExternalCacheDiskCacheFactory(context, cacheSize100MegaBytes));    }    @Override    public void registerComponents(Context context, Glide glide) {        // nothing to do here    }}

上面的代码将设置磁盘缓存到应用的内部目录,并且设置了最大的大小为 100M。下面注释的那行代码会设置磁盘缓存到外部存储(也设置了最大大小为 100M)。

这两个选项都不让你选一个特点的目录。如果你要让磁盘缓存到指定的目录,你要使用 DiskLruCacheFactory

// or any other pathString downloadDirectoryPath = Environment.getDownloadCacheDirectory().getPath(); builder.setDiskCache(          new DiskLruCacheFactory( downloadDirectoryPath, cacheSize100MegaBytes ));// In case you want to specify a cache sub folder (i.e. "glidecache")://builder.setDiskCache(//    new DiskLruCacheFactory( downloadDirectoryPath, "glidecache", cacheSize100MegaBytes ) //);

自定义缓存实现

目前为止,我们已经向你展示了如何去移动和设置缓存为确定的大小。然而,所有的调用都引用了缓存的原始实现。如果你有你自己的缓存实现呢?

嗯,你看到我们总是创建一个 Glide 的默认缓存的实现的新实例。你可以完成你自己的实现,创建和实例化它,并用上上面所有你看到的方法。你必须确保你的缓存代码实现了如下接口方法:

  • Memory cache needs to implement: MemoryCache
  • Bitmap pool needs to implement BitmapPool
  • Disk cache needs to implement: DiskCache

0 0
原创粉丝点击