Android开发Android技术知识程序员

Android Webview H5 秒开方案实现

2019-04-04  本文已影响8人  迷途小码农h

前言

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

问题原因

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

  1. js解析效率
    如果js文件较多、解析比较复杂, 就会导致渲染速度较慢。或者手机的硬件性能比较差的话, 也会导致渲染速度比较慢。

  2. 页面资源的下载
    一般加载一个H5页面, 都会产生较多的网络请求, 如图片、js文件、css文件等, 需要将这些资源都下载完成之后才能完成渲染, 这样也会导致页面渲染速度变慢

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

代码实现

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

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

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与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中缓存模式的解释:

所以我们一般设置为默认的缓存模式就可以了。关于缓存的配置, 主要还是靠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首屏秒开优化方案探讨、美团大众点评 Hybrid 化建设、《移动端本地 H5 秒开方案探索与实现》这几篇文章看看。

一些开源方案

CacheWebView
这个库的介绍链接在这里https://my.oschina.net/yale8848/blog/1544298, 据作者说主要是为了解决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的加载速度其实涉及到的方面很多, 需要注意的细节也很多, 没有办法一概而论。大家需要按照公司的业务需要量体裁衣, 按需配置。

上一篇下一篇

猜你喜欢

热点阅读