正文
Post Views
= 5
本文出自:【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的实际处理速度,具体的瓶颈我还没有定位出来。当然,图片少的话,这几百毫秒也是可以接受的。