最近把Webview優化速度,好好看了看,發現在webview啟動的時候特別耗時
性能
對于WebView的性能,給人最直觀的莫過于:打開速度比native慢。
是的,當我們打開一個WebView頁面,頁面往往會慢吞吞的loading很久,若干秒后才出現你所需要看到的頁面。
這是為什么呢?
對于一個普通用戶來講,打開一個WebView通常會經歷以下幾個階段:
交互無反饋
到達新的頁面,頁面白屏
頁面基本框架出現,但是沒有數據;頁面處于loading狀態
出現所需的數據
如果從程序上觀察,WebView啟動過程大概分為以下幾個階段:
如何縮短這些過程的時間,就成了優化WebView性能的關鍵。
接下來我們逐一分析各個階段的耗時情況,以及需要注意的優化點。
WebView初始化
當App首次打開時,默認是并不初始化瀏覽器內核的;只有當創建WebView實例的時候,才會創建WebView的基礎框架。
所以與瀏覽器不同,App中打開WebView的第一步并不是建立連接,而是啟動瀏覽器內核。
我們來分析一下這段耗時到底需要多久。
分析
針對WebView的初始化時間,我們可以定義兩個指標:
首次初始化時間:客戶端冷啟動后,第一次打開WebView,從開始創建WebView到開始建立網絡連接之間的時間。
二次初始化時間:在打開過WebView后,退出WebView,再重新打開WebView,從開始創建WebView到開始建立網絡連接之間的時間。
測試數據:
測試系統1: iOS模擬器,Titans 10.0.7
測試系統2: OPPO R829T Android 4.2.2
測試方式:測試10次取平均值
測試App:美團外賣
單位:ms
首次初始化時間二次初始化時間
iOS(UIWebView)306.5676.43
iOS(WKWebView)763.26457.25
Android192.79 *142.53
*Android外賣客戶端啟動后會在后臺開啟WebView進程,故并不是完全新建WebView時間。
這意味著什么呢?
作為前端工程師,統計了無數次的頁面打開時間,都是以網絡連接開始作為起點的。
很遺憾的通知您:WebView中用戶體驗到的打開時間需要再增加70~700ms。
于是我們找到了“為什么WebView總是很慢”的原因之一:
在瀏覽器中,我們輸入地址時(甚至在之前),瀏覽器就可以開始加載頁面。
而在客戶端中,客戶端需要先花費時間初始化WebView完成后,才開始加載。
而這段時間,由于WebView還不存在,所有后續的過程是完全阻塞的。可以這樣形容WebView初始化過程:
那么有哪些解決辦法呢?
怎么優化
由于這段過程發生在native的代碼中,單純靠前端代碼是無法優化的;大部分的方案都是前端和客戶端協作完成,以下是幾個業界采用過的方案。
全局WebView
方法:
在客戶端剛啟動時,就初始化一個全局的WebView待用,并隱藏;
當用戶訪問了WebView時,直接使用這個WebView加載對應網頁,并展示。
這種方法可以比較有效的減少WebView在App中的首次打開時間。當用戶訪問頁面時,不需要初始化WebView的時間。
當然這也帶來了一些問題,包括:
額外的內存消耗。
頁面間跳轉需要清空上一個頁面的痕跡,更容易內存泄露。
【參考東軟專利 - 加載網頁的方法及裝置CN106250434A】
于是乎,準備自己寫一個全局的webview,也就是webview池
public class WebViewPool {
private static final String DEMO_URL = "http://mc.vip.qq.com/demo/indexv3";
private static List<WebView> available = new ArrayList<WebView>();
private static List<WebView> inUse = new ArrayList<WebView>();
private static final byte[] lock = new byte[]{};
private static int maxSize = 3;
private int currentSize = 0;
private WebViewPool() {
available = new ArrayList<WebView>();
inUse = new ArrayList<WebView>();
}
private static volatile WebViewPool instance = null;
public static WebViewPool getInstance() {
if (instance == null) {
synchronized (WebViewPool.class) {
if (instance == null) {
instance = new WebViewPool();
}
}
}
return instance;
}
/**
* Webview 初始化
* 最好放在application oncreate里
* */
public static void init() {
for (int i = 0; i < maxSize; i++) {
WebView webView = new WebView(BaseApplication.getInstance());
ViewGroup.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
webView.setLayoutParams(params);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); //設置 緩存模式(true);
webView.getSettings().setAppCacheEnabled(false);
webView.getSettings().setSupportZoom(false);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
webView.getSettings().setDomStorageEnabled(true);
webView.loadUrl(DEMO_URL);
available.add(webView);
}
}
/**
* 獲取webview
*
* */
public WebView getWebView() {
synchronized (lock) {
WebView webView;
if (available.size() > 0) {
webView = available.get(0);
available.remove(0);
currentSize++;
inUse.add(webView);
} else {
webView = new WebView(BaseApplication.getInstance());
inUse.add(webView);
currentSize++;
}
ViewGroup.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
webView.setLayoutParams(params);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); //設置 緩存模式(true);
webView.getSettings().setAppCacheEnabled(false);
webView.getSettings().setSupportZoom(false);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
webView.getSettings().setDomStorageEnabled(true);
webView.loadUrl("about:blank");
return webView;
}
}
/**
* 回收webview ,不解綁
*@param webView 需要被回收的webview
*
* */
public void removeWebView(WebView webView) {
webView.loadUrl("");
webView.stopLoading();
webView.setWebChromeClient(null);
webView.setWebViewClient(null);
webView.clearCache(true);
webView.clearHistory();
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); //設置 緩存模式(true);
webView.getSettings().setAppCacheEnabled(false);
webView.getSettings().setSupportZoom(false);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setBuiltInZoomControls(false);
webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
webView.getSettings().setLoadWithOverviewMode(false);
webView.getSettings().setUserAgentString("android_client");
webView.getSettings().setDefaultTextEncodingName("UTF-8");
webView.getSettings().setDefaultFontSize(16);
synchronized (lock) {
inUse.remove(webView);
if (available.size() < maxSize) {
available.add(webView);
} else {
webView = null;
}
currentSize--;
}
}
/**
* 回收webview ,解綁
*@param webView 需要被回收的webview
*
* */
public void removeWebView(ViewGroup view, WebView webView) {
view.removeView(webView);
webView.loadUrl("");
webView.stopLoading();
webView.setWebChromeClient(null);
webView.setWebViewClient(null);
webView.clearCache(true);
webView.clearHistory();
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); //設置 緩存模式(true);
webView.getSettings().setAppCacheEnabled(false);
webView.getSettings().setSupportZoom(false);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setBuiltInZoomControls(false);
webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
webView.getSettings().setLoadWithOverviewMode(false);
webView.getSettings().setUserAgentString("android_client");
webView.getSettings().setDefaultTextEncodingName("UTF-8");
webView.getSettings().setDefaultFontSize(16);
synchronized (lock) {
inUse.remove(webView);
if (available.size() < maxSize) {
available.add(webView);
} else {
webView = null;
}
currentSize--;
}
}
/**
* 設置webview池個數
* @param size webview池個數
* */
public void setMaxPoolSize(int size) {
synchronized (lock) {
maxSize = size;
}
}
}
附上項目地址
十分感謝以下博客的分享: