diycode 开发日志 (二) 缓存网页中的图片

Android · sloop · 于 发布 · 最后由 sloop回复 · 621 次阅读
1644

在开发 diycode 客户端过程中,为了节省流量,我在很多地方都使用了缓存,请求的 json 数据可以缓存,用户头像可以缓存,文章当然也是可以缓存的。

唯一有问题的一点是文章中的图片,文章是使用 WebView 进行加载的,所有图片都是是超链接,加载时会从网络上请求数据,而图片却是整个过程中最耗费流量的,一张大图的耗费的流量都够加载好几篇文章了。

其余地方的图片倒是好处理,使用 Glide 加载就行了,Glide自己有缓存策略,只需要配置一下就行了,但是遇到 WebView 时 Glide 就没用了,虽然 WebView 有缓存策略,但如果统一设置优先使用缓存可能会导致一些数据的不同步,使用默认缓存的话,每次退出后再进来就要重新请求图片资源,会浪费掉很多的流量。

于是我想,有没有办法让图片第一次加载时缓存下来,后续用到的时候就不用再去网络请求了。这里涉及到两个主要的问题:

1. 如何获取到 WebView 所加载的图片

2. 如何使用本地图片替换 WebView 中需要从网络上请求的图片。

关于第一个比较好解决,我们知道 WebView 有一个 WebViewClient, 其中有一个 onLoadResource 方法可以监听 WebView 加载了哪些资源。我的思路非常简单,伪代码如下:

@Override
public void onLoadResource(WebView view, final String url) {
    if(url是图片类型){
      图像 = 下载(url);
      存储(图像);
    }
}

使用这种方式实际上是请求了两次数据,WebView 下载了 1 次,我们也下载了 1 次,所以在首次加载的时候重复下载了 2 次图片。有点浪费,不过相对于每次都重新获取要好一点。

其实还有另一种方案,就是在 WebView 实际加载资源的时候,先拦截下来,我们先下载好图片,之后再将结果给 WebView 这样最终就只请求了一次,但是由于这种方式会明显延长 WebView 的加载时间,用户体验会变差,而且数据的来回转换也比较难以控制,如果以后找到解决方案再来更新。

我们将图片缓存下来之后,下一步就是让下一次加载数据的时候不去网上请求图片,而是使用本地内容,关于这个我想到了两种方案。

第一种,直接用 base64 数据替换

这一种方法简单粗暴,下载完图片之后,将图片转化为 base64 格式缓存起来,使用图片等 url 作为索引,当下一次设置加载文章内容之前,先对文章扫描一遍,如果扫描到图片的 url 在本地有缓存数据,就用 base64 格式的数据直接替换掉原文链接。像下面这样:

https://diycode.b0.upaiyun.com/photo/2017/73a8485c12b51e7a9e61da4fdab94feb.png

将上面的链接替换成为下面的数据(数据只截取了一段,作为示例,里面省略了很长,很长,很长 的内容)。

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAAJACAIAAABdeVxCAAAAA3NCSVQFBgUzC42AAAAgAElEQVR4nOy9X4hkWZrY95vcE73f3Y2UzmWzRFxRbTqkadgc3IurYYy68T7MmDXsGBm8+6Z98IPBD5bRkx/8LIzxw4KxsWSEWBmtGAvt0jOww1Q/NKoGtZ0p6CFjvDVUtLYbReFOKwJXsveweZn7qeMo8MO5N+JGZlZ19XRlZmTE96OpjrxxI+ReF......

这样方法简单粗暴,也能实现我想要的效果,但是缺陷也很大。

  1. 耗费内存,需要将文章数据+图片数据一次性全部加载进内存,如果遇上一张大图片,很可能就直接 OOM 异常了。
  2. 耗费时间,数据转换时间太长,使用了这种缓存机制后,明显感觉文章加载速度变慢了,比直接使用网络请求数据还慢。

第二种,拦截资源请求

这一种方案比较靠谱,具体思路是监听 WebView 的网络请求,当发现请求是图片链接的话,拦截该请求,检查本地是否有缓存文件,如果有的话,就创建一个资源指向缓存文件,并直接返回该资源,不再去请求。如果没有的话,就执行默认设置,去请求网络资源。示例代码是这样:

setWebViewClient(new WebViewClient() {
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        Logger.e("拦截请求");
        try {
            // 如果是图片且本地有缓存
            if (isImageSuffixCheck(url) || isGifSuffixCheck(url)) {
                File file = new File("/sdcard/a.jpeg");
                InputStream inputStream = new FileInputStream(file);
                WebResourceResponse response = new WebResourceResponse("image/jpeg", "base64", inputStream);
                return response;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        Logger.e("获取默认");
        return super.shouldInterceptRequest(view, url);
    }
});

结果像这样:

下图对应的文章地址

tiny_Screenshot_2017-03-11-06-50-05

可以看到,图片被成功的替换掉了。使用这种方式所耗费的资源要比上一种方式少很多,目前来说,是一种不错的方法。

本次开发日志就到这里了,以后如果有新奇好玩的东西继续分享给大家。

如果想要了解我的话,可以关注我的微博和网站,详情见下方。

微博: http://weibo.com/GcsSloop
网站: http://www.gcssloop.com

共收到 3 条回复
96
zhangluya · #1 ·

最近我也在做客户端,学习了

96
wxf · #2 ·

赞👍

1644

代码示例可以看这里: GcsWebViewClient

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册