图片加载库Glide的“经验型”用法

本文出自:【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这个库感兴趣,我可以后边再写点东西介绍一下,个人觉得这个库的代码质量挺好的。)

已有4条评论 发表评论

  1. Rank /

    疯狂打Call 😉

  2. 1234 /

    写得真是太好了,太感动了

    1. swliu / 本文作者

      我去,刚神你过分了!

  3. vdo /

    OkHttpClientFactory是啥?

发表评论