本文出自:【InTheWorld的博客】 (欢迎留言、交流)
Glide算是Android开发中比较常见的一个开源库了,它使用起来非常简单。说实话,图片加载我基本只用了Glide,因为我的许多看似复杂的需求,在Glide的框架下都能比较轻松的实现。因此,我根本没有心思去趟其他的坑,而且在实现的过程中也发现,Glide的设计是非常棒的,简单的API、很强的扩展性都让我非常满意。
写这篇blog的出发点不是Glide的基本教学,而是总结本人的经验性用法。因此,本文的逻辑和结构都会比较松散,还望看官海涵。
1. 自定义ModelLoader
在有些情况下,我们需要自定义ModelLoader,即模型加载器。这个ModelLoader的输入是图片资源的标识,输出则是一个InputStream。Glide则可以读这个InputStream完成图片的加载。比如,在自定义协议(非Http)下的图片加载。然而,自定义协议可能大家都不一样,所以实现了也没太多的参考价值。我就说说另外一个场景吧!
搞智能硬件的Android开发者可能知道,在Android的碎片化大背景下,不同Android系统的网络栈特性差异非常大。有相当一部分手机在连接WiFi之后是无法使用4g流量的,如果这类手机连接的WiFi是无法提供internet接入功能的智能硬件设备所发射的。那么恭喜你,你的App默认是用不了流量的。为了App能正常上网——能拉到图片,我们就需要对Glide进行改造了,也即自定义ModelLoader。
Android的不同NetworkInterface会对应一个Network,也这个Network可以提供一个SocketFactory。而OkHttp的构造过程中是可以设置SocketFactory的。换言之,只要我们把Glide的图片加载绑定到一个指定的OkHttpClient上,就可以实现图片加载在指定网卡上进行(4g或者Wi-Fi)。对于怎么请求Network和设置OkHttp的SocketFactory,这里就先不赘述了。这里我们假定已经可以得到定制化的OkHttpClient。那么接下来的步骤就是这样,真的很简单。我直接贴代码了。
public class OkHttpUrlLoader implements StreamModelLoader<String> { /** * The default factory for {@link OkHttpUrlLoader}s. */ public static class Factory implements ModelLoaderFactory<String, InputStream> { private static volatile OkHttpClient cellularClient; private OkHttpClient client; private static OkHttpClient getCellularClient() { if (cellularClient == null) { synchronized (Factory.class) { if (cellularClient == null) { cellularClient = OkHttpClientFactory.getOkHttpClient(true); } } } return cellularClient; } /** * Constructor for a new Factory that runs requests using a static singleton client. */ public Factory() { this(getCellularClient()); } /** * Constructor for a new Factory that runs requests using given client. */ public Factory(OkHttpClient client) { this.client = client; } @Override public ModelLoader<String, 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() { this(Factory.getCellularClient()); } public OkHttpUrlLoader(OkHttpClient client) { this.client = client; } @Override public DataFetcher<InputStream> getResourceFetcher(String model, int width, int height) { return new OkHttpStreamFetcher(client, model); } }
为了图省事,我并没有定义GlideModule,所以这个Factory看起来很没用。OkHttpClientFactory则是根据需求(是否使用流量)来构造OkHttpClient。这个ModelLoader的关键是这个getResourceFetcher函数,它返回了一个真正的加载过程。而这个OkHttpStreamFetcher也挺简单的,就是OkHttp下载文件的流程而已。然而,我还是把代码贴出来吧!看起来可能会清楚点。
public class OkHttpStreamFetcher implements DataFetcher<InputStream> { private final OkHttpClient client; private final String url; private InputStream stream; private ResponseBody responseBody; public OkHttpStreamFetcher(OkHttpClient client, String url) { this.client = client; this.url = url; } @Override public InputStream loadData(Priority priority) throws Exception { Request.Builder requestBuilder = new Request.Builder() .url(url); 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 (Exception e) { // Ignored. } } } @Override public String getId() { return url != null ? url : ""; } @Override public void cancel() { // TODO: } }
确实没啥复杂的东西,使用这个ModelLoader同样非常简单,就像下面这样既可:
Glide.with(mContext) .using(new OkHttpUrlLoader()) .load(url) .into(mImageView);
这个例子其实也不是很恰当了,因为实在有点简单了,和普通的Http图片下载没有什么区别。不过这确实是我遇到的一个实际问题,以及解决的方式。
2. 结合GPUImage进行图片处理
Glide本身就支持使用transform运算符实现图片的变换,Github上也有一个glide-transformations的库可以做各种图片处理。这个glide-transformations是依赖于GPUImage的,在写这篇博客之前我真不知道这事情。但是glide-transformations本身实现的变换种类有限,并不能满足我的要求,所以我就直接使用GPUImage来完成需求。
public class CustomTransform extends BitmapTransformation { private WeakReference<Context> contextReference; public CustomTransform(Context context) { super(context); this.contextReference = new WeakReference<>(context); } private Bitmap remap(BitmapPool pool, Bitmap source) { Logger.t("Transform").d("source = " + source); if (source == null || contextReference == null) { return null; } Context context = contextReference.get(); if (context == null) { return null; } GPUImage gpuImage = new GPUImage(context); gpuImage.setFilter(new CustomFilter()); Bitmap bitmap = gpuImage.getBitmapWithFilterApplied(source); return bitmap; } @Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { return remap(pool, toTransform); } @Override public String getId() { return getClass().getName(); } }
对于GPUImage而言,实现图像变换的主要逻辑就是在Filter了。而Filter本身就是一个OpenGL程序的封装,同时Filter也是GPUImage这个库的核心概念。对于GPUImage这个库的Android port而言,一切都是以filter为逻辑的。如果你需要个性化的变换,那么就去继承GPUImageFilter,然后实现你需要的OpenGL程序,然后设置给GPUImage即可。不过GPUImage有点大框架的风范了,所以使用它的transformer会有点慢。根据我的实际测试,一张1024 * 1024的图,一般需要几百毫秒才能转换成功。这个速度是远远低于OpenGL的实际处理速度,具体的瓶颈我还没有定位出来。当然,图片少的话,这几百毫秒也是可以接受的。
这篇博客就先水到这吧!这个十一月,我真的是累得不行了,一篇博客都没更新。。
(如果有人对GPUImage这个库感兴趣,我可以后边再写点东西介绍一下,个人觉得这个库的代码质量挺好的。)
疯狂打Call 😉
写得真是太好了,太感动了
我去,刚神你过分了!
OkHttpClientFactory是啥?