Android Webview H5 秒开方案实现

本文首发于微信公众号「玉刚说」css

原文连接:Android Webview H5 秒开方案实现html

前言

如今许多app都嵌入了H5页面, 然而WebView加载速度慢这个问题却一直影响着用户的体验, 因此本文就如何提升H5页面的加载速度展开讨论。前端

问题缘由

首先咱们须要知道为何WebView的加载速度那么慢。H5页面的渲染速度其实主要取决于两个git

  1. js解析效率
    若是js文件较多、解析比较复杂, 就会致使渲染速度较慢。或者手机的硬件性能比较差的话, 也会致使渲染速度比较慢。
  2. 页面资源的下载
    通常加载一个H5页面, 都会产生较多的网络请求, 如图片、js文件、css文件等, 须要将这些资源都下载完成以后才能完成渲染, 这样也会致使页面渲染速度变慢

对于上面的第一点, 其实主要是由前端代码和手机硬件决定的, 由于咱们这里讨论的是对于app的性能优化, 暂时不考虑, 因此咱们能够从第二点作文章, 主要思路就是一些资源文件都使用App本地资源, 而不须要从网络下载, 从而提升页面的打开速度。github

代码实现

以加载玉刚说的renyugang.io/post/75这个页面为例。web

首先将一些资源文件放在本地的assets目录, 而后重写WebViewClient的shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(WebView view, WebResourceRequest request)这两个方法, 对访问地址进行拦截, 当url地址命中本地配置的url时, 使用本地资源替代, 不然就使用网络上的资源。segmentfault

YuGangShuoWebActivity:后端

mWebview.setWebViewClient(new WebViewClient() {
    // 设置不用系统浏览器打开,直接显示在当前Webview
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
      view.loadUrl(url);
      return true;
    }

    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
      // 若是命中本地资源, 使用本地资源替代
      if (mDataHelper.hasLocalResource(url)) {
          WebResourceResponse response =
                  mDataHelper.getReplacedWebResourceResponse(getApplicationContext(),
                          url);
          if (response != null) {
              return response;
          }
      }
      return super.shouldInterceptRequest(view, url);
    }

    @TargetApi(VERSION_CODES.LOLLIPOP)
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view,
          WebResourceRequest request) {
      String url = request.getUrl().toString();
      if (mDataHelper.hasLocalResource(url)) {
          WebResourceResponse response =
                  mDataHelper.getReplacedWebResourceResponse(getApplicationContext(),
                          url);
          if (response != null) {
              return response;
          }
      }
      return super.shouldInterceptRequest(view, request);
    }

}); 
复制代码

DataHelper是一个工具类, 代码以下:浏览器

public class DataHelper {

    private Map<String, String> mMap;

    public DataHelper({
        mMap = new HashMap<>();
        initData();
    }

    private void initData({
        String imageDir = "images/";
        String pngSuffix = ".png";
        mMap.put("http://renyugang.io/wp-content/themes/twentyseventeen/style.css?ver=4.9.8",
                "css/style.css");
        mMap.put("http://renyugang.io/wp-content/uploads/2018/06/cropped-ryg.png",
                imageDir + "cropped-ryg.png");
        ...
    }

    public boolean hasLocalResource(String url{
        return mMap.containsKey(url);
    }

    public WebResourceResponse getReplacedWebResourceResponse(Context context, String url{
        String localResourcePath = mMap.get(url);
        if (TextUtils.isEmpty(localResourcePath)) {
            return null;
        }
        InputStream is = null;
        try {
            is = context.getApplicationContext().getAssets().open(localResourcePath);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        String mimeType;
        if (url.contains("css")) {
            mimeType = "text/css";
        } else if (url.contains("jpg")) {
            mimeType = "image/jpeg";
        } else {
            mimeType = "image/png";
        }
        WebResourceResponse response = new WebResourceResponse(mimeType, "utf-8"is);
        return response;
    }


}
复制代码

咱们抓包看一下修改先后的网络请求的对比。缓存

优化前, 有n个实际发出的网络请求:

优化后, 只有一个实际发出的网络请求。而且为了和网络的资源图片作区分, 我在两张本地图片中加了“本地”的水印, 能明显看到这时候加载的是本地图片:

另外再提一点, 对于WebViewClient的shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(WebView view, WebResourceRequest request)这两个方法, 经本人亲测, 重写其中的任何一个都能生效, 后面一个shouldInterceptRequest(WebView view, WebResourceRequest request)通常是5.0以上的系统使用。我我的的建议是把这两个方法都重写了。

关于WebView的缓存

咱们再看一个有意思的现象, 在不配置本地资源的时候, 咱们第一次打开页面, 产生了n多个请求。可是当咱们退出后再次打开这个页面(没有设置加载本地资源)的时候, 竟然只发生了一次请求, 这现象与加载本地资源十分类似。

这是为何呢?
咱们卸载app, 抓包, 再次打开页面, 以banner图片请求的举例。

咱们观察这个请求的response的headers中的参数, 注意到这么几个字段:
Last-ModifiedETagExpiresCache-Control

  • Cache-Control
    例如Cache-Control:max-age=2592000, 表示缓存时长为2592000秒, 也就是一个月30天的时间。若是30天内须要再次请求这个文件,那么浏览器不会发出请求,直接使用本地的缓存的文件。这是HTTP/1.1标准中的字段。

  • Expires
    例如Expires:Tue,25 Sep 2018 07:17:34 GMT, 这表示这个文件的过时时间是格林尼治时间2018年9月25日7点17分。由于我是北京时间2018年8月26日15点请求的, 因此能够看出也是差很少一个月有效期。在这个时间以前浏览器都不会再次发出请求去获取这个文件。Expires是HTTP/1.0中的字段,若是客户端和服务器时间不一样步会致使缓存出现问题,所以才有了上面的Cache-Control。当它们同时出现时,Cache-Control优先级更高。

  • Last-Modified
    标识文件在服务器上的最新更新时间, 下次请求时,若是文件缓存过时,浏览器经过If-Modified-Since字段带上这个时间,发送给服务器,由服务器比较时间戳来判断文件是否有修改。若是没有修改,服务器返回304(未修改)告诉浏览器继续使用缓存;若是有修改,则返回200,同时返回最新的文件。

  • Etag
    Etag的取值是一个对文件进行标识的特征字串, 在向服务器查询文件是否有更新时,浏览器经过If-None-Match字段把特征字串发送给服务器,由服务器和文件最新特征字串进行匹配,来判断文件是否有更新:没有更新回包304,有更新回包200。Etag和Last-Modified可根据需求使用一个或两个同时使用。两个同时使用时,只要知足基中一个条件,就认为文件没有更新。

常见用法是Cache-Control与Last-Modified一块儿使用, Expires与 Etag一块儿使用。

可是实际状况可能并非这样。

如今过了5分钟, 咱们再次打开页面, 观察请求。

在上面这个请求中, 咱们在request中没有看到If-None-Match字段, 说明Etag这个字段没有用到。可是在request中有If-Modified-Since这个字段, 表示缓存文件的上次的修改日期, 是1984年, 表示当时从服务器请求下来的文件最后一次的修改时间是1984年, 而咱们在response中看到Last-Modified字段仍是那个时间, 说明服务器上的文件没有修改过, 因此返回了304(未修改), 而Cache-Control在这里是300秒, 表示5分钟就会过时, 而Expires在这里虽然也出现了, 可是咱们上面说过, 当Cache-Control和Expires同时出现时, Cache-Control的优先级较高。

因此说, 大部分状况下, 咱们其实看Cache-Control和Last-Modified字段足矣。

好了, 话说回来, 如今咱们知道为何会有以前提到的现象了, 是由于WebView的缓存。

那么如何才能使WebView支持这些缓存协议呢?答案是不配置(使用默认的CacheMode), 或者手动设置

WebSettings webSettings = webView.getSettings();
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
复制代码

下面是5中缓存模式的解释:

  • LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据。
  • LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。
  • LOAD_CACHE_NORMAL: API level 17中已经废弃,从API level 11开始做用同LOAD_DEFAULT模式
  • LOAD_NO_CACHE: 不使用缓存,只从网络获取数据。
  • LOAD_CACHE_ELSE_NETWORK,只要本地有,不管是否过时,或者no-cache,都使用缓存中的数据。本地没有缓存时才从网络上获取。

因此咱们通常设置为默认的缓存模式就能够了。关于缓存的配置, 主要仍是靠web前端和后台设置。

除了WebView自带的缓存, 还有Application Cache缓存, Dom Storage缓存, Web SQL Database缓存, IndexedDB缓存。可是剩下的几种缓存, 根据官方文档, AppCache已经不推荐使用了, 标准也不会再支持。而其余的几种也不是文件缓存, 和咱们今天讨论的主题不符, 因此我也再也不介绍了。有兴趣能够看H5 缓存机制浅析 移动端 Web 加载性能优化Android:手把手教你构建 全面的WebView 缓存机制 & 资源加载方案

其余提高WebView速度的方案

WebView的初始化

本地Webview初始化都要很多时间, 首次初始化webview与第二次初始化不一样,首次会比第二次慢不少。缘由预计是webview首次初始化后,即便 webview 已经释放,但一些webview 共用的全局服务或资源对象仍没有释放,第二次初始化时不须要再生成这些对象从而变快。咱们能够在Application预先初始化好WebView, 当第二次初始化WebView的时候速度就快多了, 或者直接将其拿来使用。

预加载数据

预加载数据就是在客户端初始化WebView的同时,直接由native开始网络请求数据, 当页面初始化完成后,向native获取其代理请求的数据, 数据请求和WebView初始化能够并行进行,缩短整体的页面加载时间。简单来讲就是配置一个预加载列表,在APP启动或某些时机时提早去请求,这个预加载列表须要包含所需H5模块的页面和资源, 客户端能够接管全部请求的缓存,不走webview默认缓存逻辑, 自行实现缓存机制, 原理其实就是拦截WebViewClient的那两个shouldInterceptRequest方法。

离线包

离线包的意思就是将H5的页面和资源进行打包后下发到客户端,并由客户端直接解压到本地储存中。优势是因为其本地化,首屏加载速度快,用户体验更为接近原生, 能够不依赖网络,离线运行, 缺点就是开发流程/更新机制复杂化, 须要客户端、甚至服务端的共同协做。这里我以Hybrid App技术解析 -- 实战篇中提到的思路为例子供你们参考。

资源:

  • H5: 每一个代码包都有一个惟一且递增的版本号;
  • Native: 提供包下载且解压资源文件到对应目录
  • 服务端: 提供一个接口,能够获取线上最新代码包的版本号和下载地址。

流程:

  • 前端更新代码打包后按版本号上传至指定的服务器上;
  • 每次打开页面时,H5请求接口获取线上最新代码包版本号,并与本地包进行版本号比对,当线上的版本号大于本地包版本号时,调用原生下载离线包
  • 客户端直接去线上地址下载最新的代码包,并解压替换到当前目录文件。

关于离线包的机制须要注意的问题还不少, 本文确定没法照顾彻底, 你们能够参考移动H5首屏秒开优化方案探讨美团大众点评 Hybrid 化建设《移动端本地 H5 秒开方案探索与实现》这几篇文章看看。

一些开源方案

CacheWebView
这个库的介绍连接在这里my.oschina.net/yale8848/bl…, 据做者说主要是为了解决Android自身缓存空间过小(12M)的问题, 代码我简单看了一下, 主要也是拦截这两个方法:

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public WebResourceResponse interceptRequest( WebResourceRequest request) {
        if (mInterceptor==null){
            return null;
        }
        return mInterceptor.interceptRequest(request);
    }

    @Override
    public WebResourceResponse interceptRequest(String url) {
        if (mInterceptor==null){
            return null;
        }
        return mInterceptor.interceptRequest(url);
    }
复制代码

而后使用Okhttp去下载资源, 同时给OkHttpClient配置了缓存拦截器, 由于OkHttp可以很好的支持缓存, 这样就突破了WebView缓存空间过小和缓存不可控的问题。

VasSonic
腾讯出品的一个轻量级的高性能的Hybrid框架,专一于提高页面首屏加载速度,完美支持静态直出页面和动态直出页面,兼容离线包等方案。优势是性能好, 速度快, 大厂出品, 缺点是配置复杂, 同时须要先后端接入。VasSonic的代码我没有看, 感兴趣的能够看他们的VasSonic/wiki腾讯祭出大招VasSonic,让你的H5页面首屏秒开!

总结

怎样提升WebView的加载速度其实涉及到的方面不少, 须要注意的细节也不少, 没有办法一律而论。你们须要按照公司的业务须要量体裁衣, 按需配置。

本文Demo
github.com/mundane7996…

参考:

Android:手把手教你构建 全面的WebView 缓存机制 & 资源加载方案
WebView缓存原理分析和应用
H5 和移动端 WebView 缓存机制解析与实战
腾讯祭出大招VasSonic,让你的H5页面首屏秒开!
《移动端本地 H5 秒开方案探索与实现》
移动 H5 首屏秒开优化方案探讨
美团大众点评 Hybrid 化建设
H5 缓存机制浅析 移动端 Web 加载性能优化
QQ会员基于 Hybrid 的高质量 H5 架构实践
从WebView缓存聊到Http 的缓存机制 | 掘金技术征文
美团: WebView性能、体验分析与优化

欢迎关注个人微信公众号「玉刚说」,接收第一手技术干货
相关文章
相关标签/搜索