[TOC]
概述
Webkit是一個開源瀏覽器項目,其中,對Android開發(fā)者來說,或多或少的都有些接觸。 在應用層來看,最經常使用無非這么幾個類:WebView(Android中最為復雜,也是最為簡單的一個View,繼承自AbsoluteLayout),WebViewClient、WebChromeClient(作為回調控制類)、WebSettings(進行設置項的配置)等;Webkit內部包含了網絡請求、頁面渲染、Js引擎等等。在Android4.4之前的版本中,系統(tǒng)使用的是Webkit內核,其后,切換到Google的Chromium內核。本文主要介紹的是在Android中,如何使用Webkit進行H5頁面的展現,以及常見問題的分析手段。
內核簡介
下面的內容抄自百度百科 & 亂七八糟的地方,簡單了解一下。
Webkit內核
WebKit 是一個開源的瀏覽器引擎,與之相對應的引擎有Gecko(Mozilla Firefox 等使用)和Trident(也稱MSHTML,IE 使用)。
同時WebKit 也是蘋果Mac OS X 系統(tǒng)引擎框架版本的名稱,主要用于Safari,Dashboard,Mail 和其他一些Mac OS X 程序。WebKit 前身是 KDE 小組的 KHTML,WebKit 所包含的 WebCore 排版引擎和 JSCore 引擎來自于 KDE 的 KHTML 和 KJS,當年蘋果比較了 Gecko 和 KHTML 后,仍然選擇了后者,就因為它擁有清晰的源碼結構、極快的渲染速度。Apple將 KHTML 發(fā)揚光大,推出了裝備 KHTML 改進型 WebKit 引擎的瀏覽器 Safari。
WebKit 所包含的 WebCore排版引擎和 JSCore 引擎,均是從KDE的KHTML及KJS引擎衍生而來。它們都是自由軟件,在GPL條約下授權,同時支持BSD系統(tǒng)的開發(fā)。所以Webkit也是自由軟件,同時開放源代碼。
WebKit的優(yōu)勢在于高效穩(wěn)定,兼容性好,且源碼結構清晰,易于維護。
Chrominum內核
Chromium 是 Google 的chrome瀏覽器背后的引擎,其目的是為了創(chuàng)建一個安全、穩(wěn)定和快速的通用瀏覽器。
Chromium是一個由Google主導開發(fā)的網頁瀏覽器。以BSD許可證等多重自由版權發(fā)行并開放源代碼。Chromium的開發(fā)可能早自2006年即開始,設計思想基于簡單、高速、穩(wěn)定、安全等理念,在架構上使用了Apple發(fā)展出來的WebKit排版引擎、Safari的部份源代碼與Firefox的成果,并采用Google獨家開發(fā)出的V8引擎以提升解譯JavaScript的效率,而且設計了“沙盒”、“黑名單”、“無痕瀏覽”等功能來實現穩(wěn)定與安全的網頁瀏覽環(huán)境。Chromium是Google為發(fā)展自家的瀏覽器Google Chrome(以下簡稱Chrome)而開啟的計劃,所以Chromium相當于Chrome的工程版或稱實驗版(盡管Chrome自身也有β版階段),新功能會率先在Chromium上實現,待驗證后才會應用在Chrome上,故Chrome的功能會相對落后但較穩(wěn)定。Chromium的更新速度很快,每隔數小時即有新的開發(fā)版本發(fā)布,而且可以免安裝,下載zip封裝版后解壓縮即可使用(Windows下也有安裝版)。Chrome雖然理論上也可以免安裝,但Google僅提供安裝版。
Chromium和Chrome所使用的webkit內核是目前公認的最快的網頁瀏覽方式。
使用Chromium開源代碼(基于webkit內核)的瀏覽器有360極速瀏覽器、楓樹瀏覽器、太陽花瀏覽器、世界之窗極速版、傲游瀏覽器和UC瀏覽器電腦版等。搜狗高速瀏覽器和qq瀏覽器官網未提及Chromium,只是說采用webkit內核,經網友測試這兩款瀏覽器極有可能也是使用的Chromium,只是官方不承認而已。
Blink內核
Blink 是由 WebKit 內核衍生出來的,是由 Chromium 開發(fā)維護的新項目。在官方的博客上有詳細的說明:Blink: A rendering engine for the Chromium project。關于這個瀏覽器的更加詳細的特性和目標,可以看這里:http://www.chromium.org/blink。大體就是一大堆的優(yōu)化,提高速度,各種支持。
之前的 WebKit 內核是由 谷歌 chrome 和 蘋果 safari 共同開發(fā),谷歌不干了,從 WebKit 中衍生分離出 Blink 不知道是何居心,不過瀏覽器渲染內核的多樣性是肯定有了,以谷歌的技術,這種多樣性應該會促進 Web 的發(fā)展。
對于國內來說,Blink 是開源引擎,X60 、X狗 之類的瀏覽器,又可以再增加一個核了,因為 Blink 對速度做了優(yōu)化,應該會比 WebKit 還要快,所以可能會被命名為超急速三核切換功能。總之,媽媽再也不用擔心我的瀏覽器核心不夠用了。
<b><i>前面都是吹牛逼的信息,如何使用Webkit來更好的搬磚? 且聽如下分解</i></b>
如何使用
最基本的使用
XML布局中丟一個<WebView>
標簽,然后再Activity
或者Fragment
中findViewById
,進而loadUrl
,一般也沒人這么簡單的用,除非寫Demo。很簡單,它就是一個Layout,提供了一個調用加載頁面的接口,不寫范例了,能看到這篇文章的都看過Google的API說明。
對WebView的行為進行控制
對WebView進行設置
主要涉及到WebView和WebSettings兩個類。
視覺方面
例如:
WebView.setHorizontalScrollBarEnabled(false);
WebView.setBackgroundColor(resId);
其實就是WebView的父類ViewGroup和View的方法,不多說了。不過需要注意的是,不是所有的View或ViewGroup的方法對WebView都生效。
常用屬性設置
列舉幾類常用的,幾乎所有App的WebView
都會設置的屬性:
//設置Js開啟(不開啟,你玩?zhèn)€毛線。實際場景中一般用于定位問題)
WebView.getSettings().setJavaScriptEnabled(true);
//緩存相關
WebView.getSettings().setAppCacheEnabled(true);
WebView.getSettings().setDatabaseEnabled(true);
WebView.getSettings().setDomStorageEnabled(true);
// 設置Client實現類,對于一個追求上進的App來說,自己實現一下是非常有必要的,因為不是所有的Rom都做了默認行為的實現(例如Google大爺),并且默認實現不一定滿足業(yè)務需求。
WebView.setWebChromeClient(new WebChromeClient());
WebView.setWebViewClient(new WebViewClient());
// 設置下載監(jiān)聽,注意,這里是跟隨WebView實現的,一般情況下,都會嘗試打開此鏈接,出現一個空白加載頁,然后Webkit才會判斷出此鏈接是一個下載鏈接,觸發(fā)DownloadListener回調。
WebView.setDownloadListener(new DownloadListener());
//User-agent設置,標示由誰請求
String ua = WebView.getSettings().getUserAgentString()
WebView.getSettings().setUserAgentString(ua);
其他設置項:
//Api >=19 時,支持Web內容調試,FE同學會比較依賴于此:
WebView.setWebContentsDebuggingEnabled(true);
頁面顯示:
//概覽模式進行網頁瀏覽
WebSettings.setLoadWithOverviewMode(boolean overview);
WebSettings.setUseWideViewPort(boolean use)
</br>
處理頁面&數據交互
如何處理頁面跳轉以及特殊Scheme
public boolean shouldOverrideUrlLoading(WebView view, String url)
這個回調可以說是最容易出問題的一個回調,表示什么? 字面意思,讓你重寫這個URL 的loading,比如點擊html打電話的一個<a href=“tel:110”>
標簽,作為一個有節(jié)操、有責任心的瀏覽器,你需要處理 H5常用的幾個Scheme :
- sms 發(fā)送短信
- mailto 發(fā)送郵件
- geo 查看定位信息
- tel 撥打電話
除此之外,還有各個應用自定義的scheme ,舉個例子,支付寶的支付Scheme :alipay:
。 這里的返回值,就代表你有沒有能力處理這個url,沒有的話Webkit就默認處理了。
需要注意的是,這個回調的觸發(fā)的絕大多數情況是點擊頁面的<a href="xxxx">
a標簽,在Android中loadUrl("http://www.baidu.com")
,是不會回調的,為什么不會回調,各位自行理解吧。
超鏈接<a>
標簽怎么寫:點我
特別說下窗口常見的兩種打開方式:
-
<a target="_self">
<i>不寫target時,默認為self,當前窗口打開連接 </i> <a target="_blank">
針對單頁模式的WebView框架(所有的html窗口均使用同一個WebView實例),不需要關注target的。
如果作為一個成熟的瀏覽器框架的話,是需要支持Html、JavaScript使用新窗口打開頁面,需要實現如下回調:
boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg)
還有一個相關設置項:WebSettings.setJavaScriptCanOpenWindowsAutomatically
此時,系統(tǒng)將不會再回調shouldOverrideUrlLoading
。新窗口邏輯的具體實現機制,可以參考系統(tǒng)browser實現邏輯。
<b> 這里有個坑 </b>
Android 4.4版本 ,如果實現了onCreateWindow,也就是說頁面<a>
標簽是這么寫的:<a target="_blank">
,點擊此鏈接打開的新WebView窗口,此窗口中的url點擊,是不會觸發(fā)shouldOverrideUrlLoading
。 這是剛替換成Chrominum內核出的一個bug。本人并沒在新版本上驗證是否已經修復。
另外,根據不同的Rom,底層實現是不一樣的,有的ROM會幫你處理各種調起scheme,也就是startActivity,有的ROM點一個url,就會拋一個intent出來,讓用戶選擇系統(tǒng)瀏覽器進行加載。
Js 與 Native進行通訊
系統(tǒng)默認,提供了一個接口:
public void addJavascriptInterface(Object object, String name)
有什么安全隱患呢?
戳這里
Js三種窗口
如果不知道Js怎么寫,請戳我
- Alert
public boolean onJsAlert(WebView view, String url, String message, final JsResult result)
- Confirm
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result)
- Prompt
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result)
用PC的截圖意思一下,看出區(qū)別了吧。 這里確定、取消點擊以后就得調用 JsResult、JsPromptResult 的 confirm或者cancel。
因為安全問題,大一些的App Native與Js通信都不再用WebView.addJavascriptInterface(Object)
了,都改用JsPrompt,因為JsPrompt中有message、有JsPromptResult可以返回給Js一些信息,所以橋選中了JsPrompt,另一個備選方案是JsConsole。
客戶端與Web進行數據傳輸
大體有這么幾種方式進行傳遞
- User-agent
適用場景,非常通用的數據可以通過設置Ua進行傳遞,類似于標示客戶端平臺類型、版本等,一般,應用內的瀏覽框架的Ua是統(tǒng)一的。 - Header
適用場景:特定的頁面,傳遞少量key-value數據,出現在Request的Header中。 對于WebView來說,就是通過
public void loadUrl(String url, Map<String, String> additionalHttpHeaders)
有沒有人想過,對于Http Request和Response ,Header有什么區(qū)別? 反正我是知道Response中,Header的Key是可以重復的,比如 “Set-Cookie”,這里用的是Map,Request的Header的Key是不是永遠不會重復? - Url parameters
適用場景:Web頁一般為GET請求,Url的query部分添加參數,比如這么一個Uri : https://www.baidu.com/s?wd=小豬佩奇 ,適用于少量key-value數據,單個頁面。 - Cookie
適用場景:針對域的數據存儲與傳輸,并且,客戶端的Cookie是App全局的,各個界面中的WebView均可以讀取,并且所有的請求會自動帶上請求域的Cookie數據。
從客戶端的角度來說,Cookie又分為Session Cookie和全局Cookie,默認情況下(不設置超時時間),為Session Cookie,生命周期為App啟動-結束。 一般,應用啟動時,會進行一次CookieManager.removeSessionCookie
操作。
對于FE來說,Session和Cookie是不同的概念,這點需要注意。 - JsObjectInterface & 橋
適用場景:更偏向于業(yè)務的一種方式,并且,執(zhí)行時機取決于橋的實現機制,且一般為異步操作,數據方面更偏向于需要客戶端進行界面操作或邏輯處理,而前幾種方式,客戶端在加載Web頁面前已可以準確獲知數據。
具體方案實現時,多方面考慮使用何種方式。
頁面加載相關(歷史&前進&后退)
// 需要特殊說明的是,這個方法不僅可以load網絡uri,也可以load本地靜態(tài)html文件
loadUrl(String url)
// 需要添加自定義Http Header時使用
loadUrl(String url, Map<String, String> additionalHttpHeaders)
// 刷新
reload()
// 停止加載(異步)
stopLoading()
// Post請求
postUrl(String url, byte[] postData)
// load本地靜態(tài)html代碼時使用,注意是html代碼。data = "<html><body></body></html>
loadData(String data, String mimeType, String encoding) "
// baseUrl可以指定基準url,所以這個方法可以load本地與網絡混合html,最常用解決的問題是html中的css、js資源的相對路徑問題
loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)
// 前進
goForward()
canGoForward()
// 后退
goBack()
canGoBack()
還有一個比較牛逼的
//一次前進后退多個頁面
canGoBackOrForward(int steps)
goBackOrForward(int steps)
系統(tǒng)源碼中均有方法注釋,怎么用自己看吧。
那么問題來了
WebView中的歷史記錄有哪些操作呢,又怎樣調試?
查了下,只有這兩個相關的:
WebBackForwardList copyBackForwardList()
void clearHistory()
系統(tǒng)提供的關于歷史記錄的操作并不多,因為,不支持單條刪除啊,啊啊啊!
WebViewClient中,還有一個相關callback,當系統(tǒng)更新歷史記錄時回調:
void doUpdateVisitedHistory(WebView view, String url, boolean isReload)
<b>相關問題分析法:歷史棧回退錯誤的定位</b>
絕大多數回退錯誤是由于接口調用、回調中邏輯執(zhí)行時序錯誤。
定位方法:利用copyBackForwardList
,doUpdateVisitedHistory
兩個接口在loadUrl、onPageStart、onPageFinish
以及邏輯相關的地方調用,打log,查看歷史棧,這里注意下由于loadurl是異步的,需要考慮是否加延遲等等保證調用時機的準確。
本人曾經遇到一個問題:在WebChromeClient中的 JsPrompt回調中,直接進行WebView.goBack操作,結果發(fā)現WebView確實回退到上一個頁面,但是BackFowardList當前頁面的index未更新的問題,具體見另一個篇blog。
銷毀
網上有很多關于WebView內存泄露的討論,據傳,老版本的WebView在展示大量圖片的時候,即使WebView.destory() WebView=null
,也不會銷毀。
在新版本上,實際測試結果:compileSDKVersion 23 不會泄露。
一般,我們如何銷毀WebView比較保險?
@Override
protected void onDestroy() {
final WebView tempWebView = mWebView;
mWebView = null;
if (tempWebView.getParent() != null
&& tempWebView.getParent() instanceof ViewGroup) {
((ViewGroup) tempWebview.getParent()).removeView(mWebView);
}
tempWebView.destroy();
}
緩存
這個問題好大。。。
暫時不介紹,另起blog進行說明。
安全相關
源生JSInterface漏洞
Js安全漏洞的說明:http://blog.csdn.net/leehong2005/article/details/11808557Android WebView File域攻擊
漏洞說明:http://drops.wooyun.org/mobile/11263
解決方法:
- 將不必要導出的組件設置為不導出 android:exported=false;
- 如果需要導出組件,禁止使用File域 websettings.setAllowFileAccess(false);
- 如果需要使用File協(xié)議,禁止File協(xié)議調用JavaScript:
WebSettings.setJavaScriptEnabled(false);
- http401認證:
實現WebViewClient.onReceivedHttpAuthRequest
回調,如何實現,參考系統(tǒng)browser源碼。 - SSLError
當網站https證書出現問題時,所有的瀏覽器有義務提示用戶該網站訪問有風險,
比如我們的鐵老大的網站
解決方案:
實現回調void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
需要注意的幾個問題
首先,提幾個需要注意的點:
- WebView所有的調用,都需要在UI線程
請看源碼,隨便找個方法。 基本上,每個方法,都會首先調用checkThread();
/**
* Loads the given URL.
*
* @param url the URL of the resource to load
*/
public void loadUrl(String url) {
checkThread();
mProvider.loadUrl(url);
}
不要阻塞Js調用和返回
比如,Js在調用Prompt時,客戶端沒有給返回值(JsPromptResult.confirm或cancel)就進行WebView goBack或者其他操作。 會怎樣? boom!
再比如,頁面中的一段Js跑了一個死循環(huán),會怎樣? 不殺進程,整個應用休想再使用WebView展示Web頁。-
HTTPS與HTTP混合頁面時
Android L開始,WebView默認情況下,不支持HTTPS HTTP混合型內容的展示,會對渲染進行阻塞。
https://datatheorem.github.io/android/2014/12/20/webviews-andorid-lollipop/解決方法:
if (Build.VERSION.SDK_INT >= 21) {
WebSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
問題排查方法論
個人歸納總結幾點:
- 別亂設置屬性,使用WebViewActivity基類時,了解WebView的Settings設置情況。
- Web頁面是否有非常規(guī)的Js或者html屬性調用。
- 查Log,主要的Log涉及幾方面:
- Webkit、Chrominue的Java層拋warning 或 exception
<i> 沒什么好說的,基本上就是代碼調用有問題。</i> - 內核的C層拋出Native crash
<i>可能是Web頁的適配(html & Js)適配有問題,或者是客戶端調用有問題,這個如果是客戶端問題,比較難查,靠使用經驗居多。</i> - console Web頁面拋出的信息。 (特別注意,查log時,不要限定Application Filter)
<i> 找FE了解相關情況吧,或者Google。 基本是web上的一些元素錯誤,比如:Js對象找不著,跟FE溝通吧</i>
- 笨方法,也是最有效的方法:對比測試,利用Demo、測試Html頁面<b>單一變量法</b>進行驗證。
奇巧淫技
- 假如總是有PM問你,怎么知道某個App的某一個界面是Native的,還是H5的,你可以把這一段截圖丟他/她臉上。
step1 進入開發(fā)者模式,勾選“顯示布局邊界”;
step 2,回到你想查看的界面; step 3 假如內容區(qū)只有一層基本就是H5 WebView的,多個層級,就是Native。
看到左右圖的差異了吧。
還有另一種方法,RD屌絲們看這里,特別說明,這種方法不太適合瀏覽器。 (自有內核,可能會不準確)
好了,就介紹到這里,零零散散的幾年前寫的文章,第一篇簡書blog,如有不對的地方,還懇請大家指正。