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性能、體驗分析與優化

歡迎關注個人微信公衆號「玉剛說」,接收第一手技術乾貨
相關文章
相關標籤/搜索