Android Webview H5 秒开方案实现
本文首發于微信公眾號「玉剛說」
原文鏈接:Android Webview H5 秒開方案實現
前言
現在許多app都嵌入了H5頁面, 然而WebView加載速度慢這個問題卻一直影響著用戶的體驗, 所以本文就如何提高H5頁面的加載速度展開討論。
問題原因
首先我們需要知道為什么WebView的加載速度那么慢。H5頁面的渲染速度其實主要取決于兩個
如果js文件較多、解析比較復雜, 就會導致渲染速度較慢。或者手機的硬件性能比較差的話, 也會導致渲染速度比較慢。
一般加載一個H5頁面, 都會產生較多的網絡請求, 如圖片、js文件、css文件等, 需要將這些資源都下載完成之后才能完成渲染, 這樣也會導致頁面渲染速度變慢
對于上面的第一點, 其實主要是由前端代碼和手機硬件決定的, 因為我們這里討論的是對于app的性能優化, 暫時不考慮, 所以我們可以從第二點做文章, 主要思路就是一些資源文件都使用App本地資源, 而不需要從網絡下載, 從而提高頁面的打開速度。
代碼實現
以加載玉剛說的renyugang.io/post/75這個頁面為例。
首先將一些資源文件放在本地的assets目錄, 然后重寫WebViewClient的shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(WebView view, WebResourceRequest request)這兩個方法, 對訪問地址進行攔截, 當url地址命中本地配置的url時, 使用本地資源替代, 否則就使用網絡上的資源。
YuGangShuoWebActivity:
mWebview.setWebViewClient(new?WebViewClient()?{????//?設置不用系統瀏覽器打開,直接顯示在當前Webview
????
????public?boolean?shouldOverrideUrlLoading(WebView?view,?String?url)?{
??????view.loadUrl(url);
??????return?true;
????}
????
????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);
????}
????
????
????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-Modified、ETag、Expires、Cache-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)的問題, 代碼我簡單看了一下, 主要也是攔截這兩個方法:
????
????public?WebResourceResponse?interceptRequest(?WebResourceRequest?request)?{
????????if?(mInterceptor==null){
????????????return?null;
????????}
????????return?mInterceptor.interceptRequest(request);
????}
????
????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性能、體驗分析與優化
總結
以上是生活随笔為你收集整理的Android Webview H5 秒开方案实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ZooKeeper第三方客户端Curat
- 下一篇: 「每日分享」CPU Cache 与缓存行