WebView交互架構(gòu)項(xiàng)目實(shí)戰(zhàn)(三):多進(jìn)程WebView使用實(shí)踐

*本文介紹自己在使用WebView的過(guò)程中遇到的一些問(wèn)題的解決方法和對(duì)WebView的一些優(yōu)化實(shí)踐*

*瀏覽器緩存知識(shí)介紹:*

瀏覽器緩存之 Expires , max-age, Etag , Last-Modified (其中Expires,max-age是客戶端在這個(gè)時(shí)間之前不去向服務(wù)器端發(fā)送請(qǐng)求驗(yàn)證資源是否有更新,
Etag, Last-Modified是服務(wù)器決定是否需要返回資源,未更新的資源不需要返回)

Expires
  http/1.0中定義的header,是最基礎(chǔ)的瀏覽器緩存處理,表示資源在一定時(shí)間內(nèi)從瀏覽器的緩存中獲取資源,不需要請(qǐng)求服務(wù)器驗(yàn)證獲取資源,從而達(dá)到快速獲取資源,緩解服務(wù)器壓力的目的。
  在response的header中的格式為:Expires: Thu, 01 Dec 1994 16:00:00 GMT (必須是GMT格式)
應(yīng)用:
  1、可以在html頁(yè)面中添加<meta http-equiv="Expires" content="Thu, 01 Dec 1994 16:00:00"/> 來(lái)給頁(yè)面設(shè)置緩存時(shí)間。
  2、對(duì)于圖片、css等文件則需要在IIS或者apache等運(yùn)行容器中進(jìn)行規(guī)則配置來(lái)讓容器在請(qǐng)求資源的時(shí)候添加在responese的header中。

max-age
Cache-Control中設(shè)置資源在本地緩存時(shí)間的一個(gè)值,單位為:秒(s),其他值還有private、no-cache、must-revalidate等
Last-modified

望文知義,根據(jù)這個(gè)詞條的直譯應(yīng)該是上次修改(時(shí)間),通過(guò)修改服務(wù)器端的文件后再請(qǐng)求,發(fā)現(xiàn)response的header中的Last-modified改變了
更新原理:
  1、在瀏覽器首次請(qǐng)求某個(gè)資源時(shí),服務(wù)器端返回的狀態(tài)碼是200 (ok),內(nèi)容是你請(qǐng)求的資源,同時(shí)有一個(gè)Last-Modified的屬性標(biāo)記(Reponse Header),標(biāo)識(shí)此文件在服務(wù)期端最后被修改的時(shí)間,格式:Last-Modified:Tue, 24 Feb 2009 08:01:04 GMT
  2、瀏覽器第二次請(qǐng)求該資源時(shí),根據(jù)HTTP協(xié)議的規(guī)定,瀏覽器會(huì)向服務(wù)器傳送If-Modified-Since報(bào)頭(Request Header),詢問(wèn)該文件是否在指定時(shí)間之后有被修改過(guò),格式為:If-Modified-Since:Tue, 24 Feb 2009 08:01:04 GMT
  3、如果服務(wù)器端的資源沒(méi)有變化,則服務(wù)器返回304狀態(tài)碼(Not Modified),內(nèi)容為空,這樣就節(jié)省了傳輸數(shù)據(jù)量。當(dāng)服務(wù)器端代碼發(fā)生改變,則服務(wù)器返回200狀態(tài)碼(ok),內(nèi)容為請(qǐng)求的資源,和第一次請(qǐng)求資源時(shí)類似。從而保證在資源沒(méi)有修改時(shí)不向客戶端重復(fù)發(fā)出資源,也保證當(dāng)服務(wù)器有變化時(shí),客戶端能夠及時(shí)得到最新的資源。

注:如果If-Modified-Since的時(shí)間比服務(wù)器當(dāng)前時(shí)間(當(dāng)前的請(qǐng)求時(shí)間request_time)還晚,會(huì)認(rèn)為是個(gè)非法請(qǐng)求
ETag

http/1.1 中增加的header,HTTP協(xié)議規(guī)格說(shuō)明定義ETag為“被請(qǐng)求變量的實(shí)體值” 。另一種說(shuō)法是,ETag是一個(gè)可以與Web資源關(guān)聯(lián)的記號(hào)(token)。典型的Web資源可以一個(gè)Web頁(yè),但也可能是JSON或XML文檔。服務(wù)器單獨(dú)負(fù)責(zé)判斷記號(hào)是什么及其含義,并在HTTP響應(yīng)頭中將其傳送到客戶端。
ETag的格式
不同類型的Web服務(wù)器生成ETag的策略以及生成的格式是不同的:
1、apache1.3和2.x的Etag格式是:inode-size-timestamp。
2、IIS5.0和6.0的Etag格式為Filetimestamp:Changenumber。
更新原理:
1、當(dāng)瀏覽器首次請(qǐng)求資源的時(shí)候,服務(wù)器會(huì)返回200的狀態(tài)碼(ok),內(nèi)容為請(qǐng)求的資源,同時(shí)response header會(huì)有一個(gè)ETag標(biāo)記,該標(biāo)記是服務(wù)器端根據(jù)容器(IIS或者Apache等等)中配置的ETag生成策略生成的一串唯一標(biāo)識(shí)資源的字符串,ETag格式為 ETag:"856247206"
2、當(dāng)瀏覽器第2次請(qǐng)求該資源時(shí),瀏覽器會(huì)在傳遞給服務(wù)器的request中添加If-None-Match報(bào)頭,詢問(wèn)服務(wù)器改文件在上次獲取后是否修改了,報(bào)頭格式:If-None-Match:"856246825"
3、服務(wù)器在獲取到瀏覽器的請(qǐng)求后,會(huì)根據(jù)請(qǐng)求的資源查找對(duì)應(yīng)的ETag,將當(dāng)前服務(wù)器端指定資源對(duì)應(yīng)的Etag與request中的If-None-Match進(jìn)行對(duì)比,如果相同,說(shuō)明資源沒(méi)有修改,服務(wù)器返回304狀態(tài)碼(Not Modified),內(nèi)容為空;如果對(duì)比發(fā)現(xiàn)不相同,則返回200狀態(tài)碼,同時(shí)將新的Etag添加到返回瀏覽器的response中。

幾者之間的關(guān)系

Expires 與max-age
Expires存在HTTP 1.0 版本, 標(biāo)識(shí)本地緩存的截止時(shí)間,允許客戶端在這個(gè)時(shí)間之前不去向服務(wù)器端發(fā)送請(qǐng)求驗(yàn)證資源是否有更新
max-age是HTTP 1.1版本新增的, 標(biāo)識(shí)資源可以在本地緩存多少秒,存儲(chǔ)的是更新間隔。
Expires 的一個(gè)缺點(diǎn)就是,返回的到期時(shí)間是服務(wù)器端的時(shí)間,這樣存在一個(gè)問(wèn)題,如果瀏覽器所在機(jī)器的時(shí)間與服務(wù)器的時(shí)間相差很大,那么誤差就很大,所以在HTTP 1.1版開(kāi)始,使用Cache-Control: max-age替代。
注: 如果max-age和Expires同時(shí)存在,則被Cache-Control的max-age覆蓋。
Expires =max-age + “每次下載時(shí)的當(dāng)前的request時(shí)間”
所以一旦重新下載的頁(yè)面后,expires就重新計(jì)算一次,但last-modified不會(huì)變化
Last-Modified和Expires
  使用Last-Modified標(biāo)識(shí)在資源未修改時(shí)返回的response內(nèi)容為空,可以節(jié)省一點(diǎn)帶寬,但是還是逃不掉發(fā)一個(gè)HTTP請(qǐng)求出去,需要瀏覽器連接一次服務(wù)器端。
而Expires標(biāo)識(shí)卻使得瀏覽器干脆連HTTP請(qǐng)求都不用發(fā),但是當(dāng)用戶強(qiáng)制刷新的時(shí)候,就算URI設(shè)置了Expires,瀏覽器也會(huì)發(fā)一個(gè)HTTP請(qǐng)求給服務(wù)器端驗(yàn)證資源的更新,所以,Last-Modified還是要用的,而且要和Expires一起用。

Etag和Expires
和 Last-Modified和Expires的情況類似,需要Expires控制請(qǐng)求的頻率,Etag在強(qiáng)制刷新時(shí)作為驗(yàn)證資源是否更新

Last-Modified和Etag
分布式系統(tǒng)里多臺(tái)機(jī)器間文件的last-modified必須保持一致,以免負(fù)載均衡到不同機(jī)器導(dǎo)致比對(duì)失敗
分布式系統(tǒng)盡量關(guān)閉掉Etag(每臺(tái)機(jī)器生成的etag都會(huì)不一樣)
Last-Modified和ETags請(qǐng)求的http報(bào)頭一起使用,服務(wù)器首先產(chǎn)生Last-Modified/Etag標(biāo)記,服務(wù)器可在稍后使用它來(lái)判斷頁(yè)面是否已經(jīng)被修改,來(lái)決定文件是否繼續(xù)緩存
過(guò)程如下:
1.客戶端請(qǐng)求一個(gè)頁(yè)面(A)。
2.服務(wù)器返回頁(yè)面A,并在給A加上一個(gè)Last-Modified/ETag。
3.客戶端展現(xiàn)該頁(yè)面,并將頁(yè)面連同Last-Modified/ETag一起緩存。
4.客戶再次請(qǐng)求頁(yè)面A,并將上次請(qǐng)求時(shí)服務(wù)器返回的Last-Modified/ETag一起傳遞給服務(wù)器。
5.服務(wù)器檢查該Last-Modified或ETag,并判斷出該頁(yè)面自上次客戶端請(qǐng)求之后還未被修改,直接返回響應(yīng)304和一個(gè)空的響應(yīng)體。
注:
1、Last-Modified和Etag頭都是由WebServer發(fā)出的HttpReponse Header,WebServer應(yīng)該同時(shí)支持這兩種頭。
2、WebServer發(fā)送完Last-Modified/Etag頭給客戶端后,客戶端會(huì)緩存這些頭;
3、客戶端再次發(fā)起相同頁(yè)面的請(qǐng)求時(shí),將分別發(fā)送與Last-Modified/Etag對(duì)應(yīng)的HttpRequestHeader:If-Modified-Since和If-None-Match。我們可以看到這兩個(gè)Header的值和WebServer發(fā)出的Last-Modified,Etag值完全一樣;

4、通過(guò)上述值到服務(wù)器端檢查,判斷文件是否繼續(xù)緩存;
從資源更新原理來(lái)看Last-Modified和Etag基本是類似的,那為什么http協(xié)議中要搞2個(gè)標(biāo)識(shí)呢?
Last-Modified存在的問(wèn)題:
1、在集群服務(wù)器上各個(gè)服務(wù)器上的文件時(shí)間可能不同。
2、如果用舊文件覆蓋新文件,因?yàn)闀r(shí)間更前,瀏覽器不會(huì)請(qǐng)求這個(gè)更舊的文件。
3、時(shí)間精度為s級(jí),對(duì)文件修改精度有嚴(yán)格要求的場(chǎng)景不能滿足

為什么使用Etag請(qǐng)求頭?
Etag 主要為了解決 Last-Modified 無(wú)法解決的一些問(wèn)題。
1、一些文件也許會(huì)周期性的更改,但是他的內(nèi)容并不改變(僅僅改變的修改時(shí)間),這個(gè)時(shí)候我們并不希望客戶端認(rèn)為這個(gè)文件被修改了,而重新GET;
2、某些文件修改非常頻繁,比如在秒以下的時(shí)間內(nèi)進(jìn)行修改,(比方說(shuō)1s內(nèi)修改了N次),If-Modified-Since能檢查到的粒度是s級(jí)的,這種修改無(wú)法判斷(或者說(shuō)UNIX記錄MTIME只能精確到秒)
3、某些服務(wù)器不能精確的得到文件的最后修改時(shí)間;
為此,HTTP/1.1引入了 Etag(Entity Tags).Etag僅僅是一個(gè)和文件相關(guān)的標(biāo)記,可以是一個(gè)版本標(biāo)記,比如說(shuō)v1.0.0或者說(shuō)"2e681a-6-5d044840"這么一串看起來(lái)很神秘的編碼。但是HTTP/1.1標(biāo)準(zhǔn)并沒(méi)有規(guī)定Etag的內(nèi)容是什么或者說(shuō)要怎么實(shí)現(xiàn),唯一規(guī)定的是Etag需要放在""內(nèi)。尤其是在做斷點(diǎn)下載/續(xù)傳時(shí),表現(xiàn)得比較明顯

頁(yè)面加載速度優(yōu)化
影響頁(yè)面加載速度的因素有非常多,我們?cè)趯?duì) WebView 加載一個(gè)網(wǎng)頁(yè)的過(guò)程進(jìn)行調(diào)試發(fā)現(xiàn),每次加載的過(guò)程中都會(huì)有較多的網(wǎng)絡(luò)請(qǐng)求,除了 web 頁(yè)面自身的 URL 請(qǐng)求,還會(huì)有 web 頁(yè)面外部引用的JS、CSS、字體、圖片等等都是個(gè)獨(dú)立的 http 請(qǐng)求。這些請(qǐng)求都是串行的,這些請(qǐng)求加上瀏覽器的解析、渲染時(shí)間就會(huì)導(dǎo)致 WebView 整體加載時(shí)間變長(zhǎng),消耗的流量也對(duì)應(yīng)的真多。接下來(lái)我們就來(lái)說(shuō)說(shuō)幾種優(yōu)化方案來(lái)是怎么解決這個(gè)問(wèn)題的。
選擇合適的 WebView 緩存

WebView 緩存看似就是開(kāi)啟幾個(gè)開(kāi)關(guān)的問(wèn)題,但是要弄懂這幾種緩存機(jī)制還是很有深度。下圖是騰訊某工程師總結(jié)六種 H5 常用的緩存機(jī)制的優(yōu)勢(shì)及適用場(chǎng)景。

瀏覽器緩存機(jī)制:

主要前端負(fù)責(zé),Android 端不需要進(jìn)行特別的配置。
Dom Storage(Web Storage)存儲(chǔ)機(jī)制:

配合前端使用,使用時(shí)需要打開(kāi) DomStorage 開(kāi)關(guān)。

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setDomStorageEnabled(true);

Web SQL Database 存儲(chǔ)機(jī)制:

雖然已經(jīng)不推薦使用了,但是為了兼容性,還是提供下 Android 端使用的方法

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setDatabaseEnabled(true);
final String dbPath = getApplicationContext().getDir("db",Context.MODE_PRIVATE).getPath();
webSettings.setDatabasePath(dbPath)

Application Cache 存儲(chǔ)機(jī)制

Application Cache(簡(jiǎn)稱 AppCache)似乎是為支持 Web App 離線使用而開(kāi)發(fā)的緩存機(jī)制。它的緩存機(jī)制類似于瀏覽器的緩存(Cache-Control 和 Last-Modified)機(jī)制,都是以文件為單位進(jìn)行緩存,且文件有一定更新機(jī)制。但 AppCache 是對(duì)瀏覽器緩存機(jī)制的補(bǔ)充,不是替代。
不過(guò)根據(jù)官方文檔,AppCache 已經(jīng)不推薦使用了,標(biāo)準(zhǔn)也不會(huì)再支持?,F(xiàn)在主流的瀏覽器都是還支持 AppCache的,以后就不太確定了。同樣給出 Android 端啟用 AppCache 的代碼。

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setAppCacheEnabled(true);
final String cachePath = getApplicationContext().getDir("cache",Context.MODE_PRIVATE).getPath();
webSettings.setAppCachePath(cachePath);
webSettings.setAppCacheMaxSize(510241024);

Indexed Database 存儲(chǔ)機(jī)制

IndexedDB 也是一種數(shù)據(jù)庫(kù)的存儲(chǔ)機(jī)制,但不同于已經(jīng)不再支持的 Web SQL Database。IndexedDB 不是傳統(tǒng)的關(guān)系數(shù)據(jù)庫(kù),可歸為 NoSQL 數(shù)據(jù)庫(kù)。IndexedDB 又類似于 Dom Storage 的 key-value 的存儲(chǔ)方式,但功能更強(qiáng)大,且存儲(chǔ)空間更大。
Android 在4.4開(kāi)始加入對(duì) IndexedDB 的支持,只需打開(kāi)允許 JS 執(zhí)行的開(kāi)關(guān)就好了。

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

File System API

File System API 是 H5 新加入的存儲(chǔ)機(jī)制。它為 Web App 提供了一個(gè)虛擬的文件系統(tǒng),就像 Native App 訪問(wèn)本地文件系統(tǒng)一樣。由于安全性的考慮,這個(gè)虛擬文件系統(tǒng)有一定的限制。Web App 在虛擬的文件系統(tǒng)中,可以進(jìn)行文件(夾)的創(chuàng)建、讀、寫、刪除、遍歷等操作。很可惜到目前,Android 系統(tǒng)的 WebView 還不支持 File System API。
簡(jiǎn)單的介紹完了上面六種 H5 常用的緩存模式,想必大家能對(duì) Android WebView 所支持的緩存模式有個(gè)粗略的了解。如果想和前端更好的配合使用 Android WebView 所支持的緩存,建議看下這篇文章《H5 緩存機(jī)制淺析 移動(dòng)端 Web 加載性能優(yōu)化》

*常用資源預(yù)加載:*

上面介紹的緩存技術(shù),能優(yōu)化二次啟動(dòng) WebView 的加載速度,那首次加載 H5 頁(yè)面的速度該怎么優(yōu)化呢?上面分析了一次加載過(guò)程會(huì)有許多外部依賴的 JS、CSS、圖片等資源需要下載,那我們能不能提前將這些資源下載好,等H5 加載時(shí)直接替換呢?
好在從 API 11(Android 3.0)開(kāi)始,WebView 引入了 shouldInterceptRequest 函數(shù),這個(gè)函數(shù)有兩種重載。
public WebResourceResponse shouldInterceptRequest(WebView webView, String url) 從 API 11 引入,API 21 廢棄
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) 從 API 21 開(kāi)始引入
考慮到目前大多數(shù) App 還要支持 API 14,所以還是使用 shouldInterceptRequest (WebView view, String url) 為例。

WebView mWebView = (WebView) findViewById(R.id.webview);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, final String url) {
WebResourceResponse response = null;
// 檢查該資源是否已經(jīng)提前下載完成。我采用的策略是在應(yīng)用啟動(dòng)時(shí),用戶在 wifi 的網(wǎng)絡(luò)環(huán)境下 // 提前下載 H5 頁(yè)面需要的資源。
boolean resDown = JSHelper.isURLDownValid(url);
if (resDown) {
jsStr = JsjjJSHelper.getResInputStream(url);
if (url.endsWith(".png")) {
response = getWebResourceResponse(url, "image/png", ".png");
} else if (url.endsWith(".gif")) {
response = getWebResourceResponse(url, "image/gif", ".gif");
} else if (url.endsWith(".jpg")) {
response = getWebResourceResponse(url, "image/jepg", ".jpg");
} else if (url.endsWith(".jepg")) {
response = getWebResourceResponse(url, "image/jepg", ".jepg");
} else if (url.endsWith(".js") && jsStr != null) {
response = getWebResourceResponse("text/javascript", "UTF-8", ".js");
} else if (url.endsWith(".css") && jsStr != null) {
response = getWebResourceResponse("text/css", "UTF-8", ".css");
} else if (url.endsWith(".html") && jsStr != null) {
response = getWebResourceResponse("text/html", "UTF-8", ".html");
}
}
// 若 response 返回為 null , WebView 會(huì)自行請(qǐng)求網(wǎng)絡(luò)加載資源。
return response;
}
});

private WebResourceResponse getWebResourceResponse(String url, String mime, String style) {
WebResourceResponse response = null;
try {
response = new WebResourceResponse(mime, "UTF-8", new FileInputStream(new File(getJSPath() + TPMD5.md5String(url) + style)));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return response;
}

public String getJsjjJSPath() {
String splashTargetPath = JarEnv.sApplicationContext.getFilesDir().getPath() + "/JS";
if (!TPFileSysUtil.isDirFileExist(splashTargetPath)) {
TPFileSysUtil.createDir(splashTargetPath);
}
return splashTargetPath + "/";
}

**
*1:常用 JS 本地化及延遲加載***

**
**資源等文件(不需要更新)本地存儲(chǔ),在需要的時(shí)候直接從本地獲取。哪些資源需要我們?nèi)ゴ鎯?chǔ)在本地呢,當(dāng)然是一些不會(huì)被更新的資源,例如圖片文件,js文件,css文件,比預(yù)加載更粗暴的優(yōu)化方法是直接將常用的 JS 腳本本地化,直接打包放入 apk 中。比如 H5 頁(yè)面獲取用戶信息,設(shè)置標(biāo)題等通用方法,就可以直接寫入一個(gè) JS 文件,放入 asserts 文件夾,在 WebView 調(diào)用了onPageFinished() 方法后進(jìn)行加載。需要注意的是,在該 JS 文件中需要寫入一個(gè) JS 文件載入完畢的事件,這樣前端才能接受都愛(ài) JS 文件已經(jīng)種植完畢,可以調(diào)用 JS 中的方法了。 附上一段本地化的 JS 代碼。

javascript: ;
(function() {
try{
window.JSBridge = {
'invoke': function(name) {
var args = [].slice.call(arguments, 1),
callback = args.pop(),
params, obj = this[name];
if (typeof callback !== 'function') {
params = callback;
callback = function() {}
} else {
params = args[0]
} if (typeof obj !== 'object' || typeof obj.func !== 'function') {
callback({
'err_msg': 'system:function_not_exist'
});
return
}
obj.callback = callback;
obj.params = params;
obj.func(params)
},
'on': function(event, callback) {
var obj = this['on' + event];
if (typeof obj !== 'object') {
callback({
'err_msg': 'system:function_not_exist'
});
retrun
}
if (typeof callback !== 'undefined') obj.callback = callback
},
'login': {
'func': function(params) {
prompt("login", JSON.stringify(params))
},
'params': {},
'callback': function(res) {}
},
'settitle': {
'func': function(params) {
prompt("settitle",JSON.stringify(params))
},
'params': {},
'callback': function(res) {}
},
}catch(e){
alert('demo.js error:'+e);
}
var readyEvent = document.createEvent('Events');
readyEvent.initEvent('JSBridgeReady', true, true);
document.dispatchEvent(readyEvent)
})();

關(guān)于 JS 延遲加載
Android 的 OnPageFinished 事件會(huì)在 Javascript 腳本執(zhí)行完成之后才會(huì)觸發(fā)。如果在頁(yè)面中使 用JQuery,會(huì)在處理完 DOM 對(duì)象,執(zhí)行完 $(document).ready(function() {}); 事件自會(huì)后才會(huì)渲染并顯示頁(yè)面。而同樣的頁(yè)面在 iPhone 上卻是載入相當(dāng)?shù)目?,因?yàn)?iPhone 是顯示完頁(yè)面才會(huì)觸發(fā)腳本的執(zhí)行。所以我們這邊的解決方案延遲 JS 腳本的載入,這個(gè)方面的問(wèn)題是需要Web前端工程師幫忙優(yōu)化的。

**
*2:使用第三方 WebView 內(nèi)核***

WebView 的兼容性一直也是困擾我們 Android 開(kāi)發(fā)者的一個(gè)大問(wèn)題,不說(shuō) Android 4.4 版本 Google 使用了Chromium 替代 Webkit 作為 WebView 內(nèi)核,就看看國(guó)內(nèi)眾多的第三方 ROM 都有可能會(huì)對(duì)原生的 WebView 做出修改,這時(shí)候如果出現(xiàn)兼容問(wèn)題,是非常難定位到問(wèn)題和解決的。
在一次使用微信瀏覽訂閱公眾號(hào)文章的過(guò)程中,發(fā)現(xiàn)微信的 H5 頁(yè)面有一行 『QQ 瀏覽器 X5 內(nèi)核提供技術(shù)支持』。順著這個(gè)線索我就找到了騰訊瀏覽服務(wù)。發(fā)現(xiàn)騰訊已經(jīng)把這個(gè)功能開(kāi)放了,而且集成的 SDK 很小只有212 KB。這是很驚人的,通過(guò)介紹才發(fā)現(xiàn)這個(gè) SDK 是可以共享微信和手機(jī) QQ 的 X5 內(nèi)核。這就很方便了,作為國(guó)內(nèi)市場(chǎng)最不可或缺的兩個(gè) App,我們能只需要集成一個(gè)很小的 SDK 就可以共享使用 X5 內(nèi)核了,不得不說(shuō)騰訊還是很有想法的。
簡(jiǎn)單摘錄些功能亮點(diǎn),想必能讓大家高潮一番。詳細(xì)內(nèi)容大家可以直接到騰訊瀏覽服務(wù)看看,我相信不會(huì)讓你們失望的。
網(wǎng)頁(yè)瀏覽能力
Web頁(yè)面crash率降低75%
頁(yè)面打開(kāi)速度提升35%
流量節(jié)省60%
閱讀模式
去除網(wǎng)頁(yè)中廣告等雜質(zhì)
優(yōu)化文章的閱讀體驗(yàn)
文件打開(kāi)能力
包括會(huì)話頁(yè)的互傳文件及郵件中附件
支持doc、ppt、xls、pdf等辦公格式
支持jpg、gif、png、bmp等圖片格式
支持zip、rar等壓縮文件
支持mp3、mp4、RMVB等音視頻格式
視頻菜單能力
支持屏幕調(diào)節(jié)等常規(guī)視頻菜單功能
靈活切換全屏&小窗功能

*3:加快HTML網(wǎng)頁(yè)裝載完成的速度*

默認(rèn)情況html代碼下載到WebView后,webkit開(kāi)始解析網(wǎng)頁(yè)各個(gè)節(jié)點(diǎn),發(fā)現(xiàn)有外部樣式文件或者外部腳本文件時(shí),會(huì)異步發(fā)起網(wǎng)絡(luò)請(qǐng)求下載文件,但如果在這之前也有解析到image節(jié)點(diǎn),那勢(shì)必也會(huì)發(fā)起網(wǎng)絡(luò)請(qǐng)求下載相應(yīng)的圖片。在網(wǎng)絡(luò)情況較差的情況下,過(guò)多的網(wǎng)絡(luò)請(qǐng)求就會(huì)造成帶寬緊張,影響到css或js文件加載完成的時(shí)間,造成頁(yè)面空白loading過(guò)久。解決的方法就是告訴WebView先不要自動(dòng)加載圖片,等頁(yè)面finish后再發(fā)起圖片加載。

故在WebView初始化時(shí)設(shè)置如下代碼:
public void int () {
if(Build.VERSION.SDK_INT >= 19) {
webView.getSettings().setLoadsImagesAutomatically(true);
} else {
webView.getSettings().setLoadsImagesAutomatically(false);
}
}

同時(shí)在WebView的WebViewClient實(shí)例中的onPageFinished()方法添加如下代碼:
@Override
public void onPageFinished(WebView view, String url) {
if(!webView.getSettings().getLoadsImagesAutomatically()) {
webView.getSettings().setLoadsImagesAutomatically(true);
}
}

從上面的代碼,可以看出我們對(duì)系統(tǒng)API在19以上的版本作了兼容。因?yàn)?.4以上系統(tǒng)在onPageFinished時(shí)再恢復(fù)圖片加載時(shí),如果存在多張圖片引用的是相同的src時(shí),會(huì)只有一個(gè)image標(biāo)簽得到加載,因而對(duì)于這樣的系統(tǒng)我們就先直接加載。

**
*4:自定義出錯(cuò)界面***

當(dāng)WebView加載頁(yè)面出錯(cuò)時(shí)(一般為404 NOT FOUND),安卓WebView會(huì)默認(rèn)顯示一個(gè)賣萌的出錯(cuò)界面。但我們?cè)趺茨懿蛔層脩舭l(fā)現(xiàn)原來(lái)我使用的是網(wǎng)頁(yè)應(yīng)用呢,我們期望的是用戶在網(wǎng)頁(yè)上得到是如原生般應(yīng)用的體驗(yàn),那就先要從干掉這個(gè)默認(rèn)出錯(cuò)頁(yè)面開(kāi)始。當(dāng)WebView加載出錯(cuò)時(shí),我們會(huì)在WebViewClient實(shí)例中的onReceivedError()方法接收到錯(cuò)誤,我們就在這里做些手腳:
@Override
public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
mErrorFrame.setVisibility(View.VISIBLE);
}

從上面可以看出,我們先使用loadDataWithBaseURL清除掉默認(rèn)錯(cuò)誤頁(yè)內(nèi)容,再讓我們自定義的View得到顯示(mErrorFrame為蒙在WebView之上的一個(gè)LinearLayout布局,默認(rèn)為View.GONE)。

遠(yuǎn)程網(wǎng)頁(yè)需訪問(wèn)本地資源

當(dāng)我們?cè)赪ebView中加載出從web服務(wù)器上拿取的內(nèi)容時(shí),是無(wú)法訪問(wèn)本地資源的,如assets目錄下的圖片資源,因?yàn)檫@樣的行為屬于跨域行為(Cross-Domain),而WebView是禁止的。解決這個(gè)問(wèn)題的方案是把html內(nèi)容先下載到本地,然后使用loadDataWithBaseURL加載html。這樣就可以在html中使用 file:///android_asset/xxx.png 的鏈接來(lái)引用包里面assets下的資源了。示例如下:
private void loadWithAccessLocal(final String htmlUrl) {
new Thread(new Runnable() {
public void run() {
try {
final String htmlStr = NetService.fetchHtml(htmlUrl);
if (htmlStr != null) {
TaskExecutor.runTaskOnUiThread(new Runnable() {
@Override
public void run() {
loadDataWithBaseURL(htmlUrl, htmlStr, "text/html", "UTF-8", "");
}
});
return;
}
} catch (Exception e) {
Log.e("Exception:" + e.getMessage());
}

  TaskExecutor.runTaskOnUiThread(new Runnable() {  
    @Override  
    public void run() {  
      onPageLoadedError(-1, "fetch html failed");  
    }  
  });  
}  

}).start();
}

上面有幾點(diǎn)需要注意:
?從網(wǎng)絡(luò)上下載html的過(guò)程應(yīng)放在工作線程中
?html下載成功后渲染出html的步驟應(yīng)放在UI主線程,不然WebView會(huì)報(bào)錯(cuò)
?html下載失敗則可以使用我們前面講述的方法來(lái)顯示自定義錯(cuò)誤界面

5:WebView 導(dǎo)致的內(nèi)存泄露

Android 中的 WebView 存在很大的兼容性問(wèn)題,不僅僅是 Android 系統(tǒng)版本的不同對(duì) WebView 產(chǎn)生很大的差異,另外不同的廠商出貨的 ROM 里面 WebView 也存在著很大的差異。更嚴(yán)重的是標(biāo)準(zhǔn)的 WebView 存在內(nèi)存泄露的問(wèn)題,看這里WebView causes memory leak - leaks the parent Activity。所以通常根治這個(gè)問(wèn)題的辦法是為 WebView 開(kāi)啟另外一個(gè)進(jìn)程,通過(guò) AIDL 與主進(jìn)程進(jìn)行通信,WebView 所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷毀,從而達(dá)到內(nèi)存的完整釋放。
這段話來(lái)自胡凱翻譯的 Google Android 內(nèi)存優(yōu)化之 OOM 。這里提到的讓 WebView 獨(dú)立運(yùn)行在一個(gè)進(jìn)程里,用完 WebView 后直接銷毀這個(gè)進(jìn)程,即使內(nèi)存泄露了,也不會(huì)影響到主進(jìn)程。微信,手 Q 等 App 也采用了這個(gè)方案。但是這就涉及到了跨進(jìn)程通訊,處理起來(lái)就比較麻煩。
另外個(gè)解決方案,就是使用自己封裝的 WebView,比如上面提到的 X5 內(nèi)核,且使用 WebView 的時(shí)候,不在 XML 里面聲明,而是在代碼中直接 new 出來(lái),傳入 application context 來(lái)防止 activity 引用被濫用。

WebView webView = new WebView(getContext().getApplicationContext();
webFrameLayout.addView(webView, 0);

在使用了這個(gè)方式后,基本上 90% 的 WebView 內(nèi)存泄漏的問(wèn)題便得以解決。

6:客戶端UI優(yōu)化

怎么讓用戶看不到WebView加載前的白色頁(yè)面呢?首次加載后頁(yè)面的跳轉(zhuǎn)可以用上面的步驟進(jìn)行優(yōu)化,可以提供給用戶一個(gè)很好的體驗(yàn),那加載的第一頁(yè)呢?我們需要WebView預(yù)加載頁(yè)面,這個(gè)該怎么做到的呢?下面提供兩種方法:
ViewPager,將歡迎頁(yè)面與WebView頁(yè)面一起放進(jìn)ViewPager中,設(shè)置預(yù)加載頁(yè)面?zhèn)€數(shù),使WebView所在頁(yè)面可以預(yù)加載,在加載完畢的時(shí)候切換到WebView所在頁(yè)面。
FrameLayout,將歡迎頁(yè)面與WebView頁(yè)面的布局合在一起,顯示在一個(gè)頁(yè)面內(nèi),起始隱藏WebView布局,待WebView加載完畢,隱藏歡迎布局,顯示W(wǎng)ebView布局。
使用FrameLayout簡(jiǎn)單一些,兩種方法都是需要對(duì)WebChromeClient的onProgressChanged進(jìn)行監(jiān)聽(tīng),加載完畢進(jìn)行頁(yè)面切換,如下:
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (newProgress >= 100) {
// 切換頁(yè)面
}
}
});

*7:WebView獨(dú)立進(jìn)程*

有效增大App的運(yùn)存,減少由webview引起的內(nèi)存泄露對(duì)主進(jìn)程內(nèi)存的占用。
避免WebView的Crash影響App主進(jìn)程的運(yùn)行。
擁有對(duì)WebView獨(dú)立進(jìn)程操控權(quán)。

WebView進(jìn)程與其他進(jìn)程通訊的方式

把webview獨(dú)立進(jìn)程之后會(huì)發(fā)現(xiàn),埋點(diǎn)功能和接收主進(jìn)程數(shù)據(jù)都不正常了,這里就涉及到進(jìn)程間通訊的問(wèn)題了;
進(jìn)程通訊無(wú)非就是那幾種,aidl,messager,content provider,廣播;
在這里就不再?gòu)?fù)述了,我是采用廣播的方式來(lái)做的。

*8:WebView硬件加速導(dǎo)致頁(yè)面渲染閃爍*

4.0以上的系統(tǒng)我們開(kāi)啟硬件加速后,WebView渲染頁(yè)面更加快速,拖動(dòng)也更加順滑。但有個(gè)副作用就是,當(dāng)WebView視圖被整體遮住一塊,
然后突然恢復(fù)時(shí)(比如使用SlideMenu將WebView從側(cè)邊滑出來(lái)時(shí)),這個(gè)過(guò)渡期會(huì)出現(xiàn)白塊同時(shí)界面閃爍。
解決這個(gè)問(wèn)題的方法是在過(guò)渡期前將WebView的硬件加速臨時(shí)關(guān)閉,過(guò)渡期后再開(kāi)啟,代碼如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);}

9:webview的配置

WebSettings用于管理WebView狀態(tài)配置,當(dāng)WebView第一次被創(chuàng)建時(shí),WebView包含著一個(gè)默認(rèn)的配置,這些默認(rèn)的配置將通過(guò)get方法返回,通過(guò)WebView中的getSettings方法獲得一個(gè)WebSettings對(duì)象,如果一個(gè)WebView被銷毀,在WebSettings中所有回調(diào)方法將拋出IllegalStateException異常。

1、setSupportZoom(boolean support)
設(shè)置WebView是否支持使用屏幕控件或手勢(shì)進(jìn)行縮放,默認(rèn)是true,支持縮放。
getSettings.setSupportZoom(false);
2、setMediaPlaybackRequiresUserGesture(boolean require)
設(shè)置WebView是否通過(guò)手勢(shì)觸發(fā)播放媒體,默認(rèn)是true,需要手勢(shì)觸發(fā)。
getSettings.setMediaPlaybackRequiresUserGesture(false);
3、setBuiltInZoomControls(boolean enabled)
設(shè)置WebView是否使用其內(nèi)置的變焦機(jī)制,該機(jī)制集合屏幕縮放控件使用,默認(rèn)是false,不使用內(nèi)置變焦機(jī)制。
getSettings.setBuiltInZoomControls(true);
4、setDisplayZoomControls(boolean enabled)
設(shè)置WebView使用內(nèi)置縮放機(jī)制時(shí),是否展現(xiàn)在屏幕縮放控件上,默認(rèn)true,展現(xiàn)在控件上。
getSettings.setDisplayZoomControls(false);
5、setAllowFileAccess(boolean allow)
設(shè)置在WebView內(nèi)部是否允許訪問(wèn)文件,默認(rèn)允許訪問(wèn)。
getSettings.setAllowFileAccess(false);
6、setAllowContentAccess(boolean allow)
設(shè)置WebView是否使用其內(nèi)置的變焦機(jī)制,該機(jī)制結(jié)合屏幕縮放控件使用,默認(rèn)是false,不使用內(nèi)置變焦機(jī)制。
getSettings.setAllowContentAccess(false);
7、setLoadWithOverviewMode(boolean overview)
設(shè)置WebView是否使用預(yù)覽模式加載界面。
getSettings.setLoadWithOverviewMode(false);
8、setSaveFormData(boolean save)
設(shè)置WebView是否保存表單數(shù)據(jù),默認(rèn)true,保存數(shù)據(jù)。
getSettings.setSaveFormData(false);
9、setTextZoom(int textZoom)
設(shè)置WebView中加載頁(yè)面字體變焦百分比,默認(rèn)100,整型數(shù)。
getSettings.setTextZoom(100);
10、setAcceptThirdPartyCookies(boolean accept)
設(shè)置WebView訪問(wèn)第三方Cookies策略,參考CookieManager提供的方法:setShouldAcceptThirdPartyCookies。
getSettings.setAcceptThirdPartyCookies(false);
11、setUseWideViewPort(boolean use)
設(shè)置WebView是否使用viewport,當(dāng)該屬性被設(shè)置為false時(shí),加載頁(yè)面的寬度總是適應(yīng)WebView控件寬度;當(dāng)被設(shè)置為true,當(dāng)前頁(yè)面包含viewport屬性標(biāo)簽,在標(biāo)簽中指定寬度值生效,如果頁(yè)面不包含viewport標(biāo)簽,無(wú)法提供一個(gè)寬度值,這個(gè)時(shí)候該方法將被使用。
getSettings.setUseWideViewPort(false);
12、setSupportMultipleWindows(boolean support)
設(shè)置WebView是否支持多屏窗口,參考WebChromeClient#onCreateWindow,默認(rèn)false,不支持。
getSettings.setSupportMultipleWindows(true);
13、setLayoutAlgorithm(LayoutAlgorithm l)
設(shè)置WebView底層的布局算法,參考LayoutAlgorithm#NARROW_COLUMNS,將會(huì)重新生成WebView布局
getSettings.setLayoutAlgorithm(LayoutAlgorithm l);
14、setStandardFontFamily(String font)
設(shè)置WebView標(biāo)準(zhǔn)字體庫(kù)字體,默認(rèn)字體“sans-serif”。
getSettings.setStandardFontFamily("sans-serif");
15、setFixedFontFamily(String font)
設(shè)置WebView固定的字體庫(kù)字體,默認(rèn)“monospace”。
getSettings.setFixedFontFamily("monospace");
16、setSansSerifFontFamily(String font)
設(shè)置WebView Sans SeriFontFamily字體庫(kù)字體,默認(rèn)“sans-serif”。
getSettings.setSansSerifFontFamily("sans-serif");
17、setSerifFontFamily(String font)
設(shè)置WebView seri FontFamily字體庫(kù)字體,默認(rèn)“sans-serif”。
getSettings.setSansSerifFontFamily("sans-serif");
18、setCursiveFontFamily(String font)
設(shè)置WebView字體庫(kù)字體,默認(rèn)“cursive”
getSettings.setCursiveFontFamily("cursive");
19、setFantasyFontFamily(String font)
設(shè)置WebView字體庫(kù)字體,默認(rèn)“fantasy”。
getSettings.setFantasyFontFamily("fantasy");
20、setMinimumFontSize(int size)
設(shè)置WebView字體最小值,默認(rèn)值8,取值1到72
getSettings.setMinimumFontSize(8);
21、setMinimumLogicalFontSize(int size)
設(shè)置WebView邏輯上最小字體值,默認(rèn)值8,取值1到72
getSettings.setMinimumLogicalFontSize(8);
22、setDefaultFontSize(int size)
設(shè)置WebView默認(rèn)值字體值,默認(rèn)值16,取值1到72
getSettings.setDefaultFontSize(16);
23、setDefaultFixedFontSize(int size)
設(shè)置WebView默認(rèn)固定的字體值,默認(rèn)值16,取值1到72
getSettings.setDefaultFixedFontSize(16);
24、setLoadsImagesAutomatically(boolean flag)
設(shè)置WebView是否加載圖片資源,默認(rèn)true,自動(dòng)加載圖片
getSettings.setLoadsImagesAutomatically(false);
25、setBlockNetworkImage(boolean flag)
設(shè)置WebView是否以http、https方式訪問(wèn)從網(wǎng)絡(luò)加載圖片資源,默認(rèn)false
getSettings.setBlockNetworkImage(true);
26、setBlockNetworkLoads(boolean flag)
設(shè)置WebView是否從網(wǎng)絡(luò)加載資源,Application需要設(shè)置訪問(wèn)網(wǎng)絡(luò)權(quán)限,否則報(bào)異常
getSettings.setBlockNetworkLoads(true);
27、setJavaScriptEnabled(boolean flag)
設(shè)置WebView是否允許執(zhí)行JavaScript腳本,默認(rèn)false,不允許
getSettings.setJavaScriptEnabled(true);
28、setAllowUniversalAccessFromFileURLs(boolean flag)
設(shè)置WebView運(yùn)行中的腳本可以是否訪問(wèn)任何原始起點(diǎn)內(nèi)容,默認(rèn)true
getSettings.setAllowUniversalAccessFromFileURLs(false);
29、setAllowFileAccessFromFileURLs(boolean flag)
設(shè)置WebView運(yùn)行中的一個(gè)文件方案被允許訪問(wèn)其他文件方案中的內(nèi)容,默認(rèn)值true
getSettings.setAllowFileAccessFromFileURLs(false);
30、setGeolocationDatabasePath(String databasePath)
設(shè)置WebView保存地理位置信息數(shù)據(jù)路徑,指定的路徑Application具備寫入權(quán)限
getSettings.setGeolocationDatabasePath(String path);
31、setAppCacheEnabled(boolean flag)
設(shè)置Application緩存API是否開(kāi)啟,默認(rèn)false,設(shè)置有效的緩存路徑參考setAppCachePath(String path)方法
getSettings.setAppCacheEnabled(true);
32、setAppCachePath(String appCachePath)
設(shè)置當(dāng)前Application緩存文件路徑,Application Cache API能夠開(kāi)啟需要指定Application具備寫入權(quán)限的路徑
getSettings.setAppCachePath(String appCachePath);
33、setDatabaseEnabled(boolean flag)
設(shè)置是否開(kāi)啟數(shù)據(jù)庫(kù)存儲(chǔ)API權(quán)限,默認(rèn)false,未開(kāi)啟,可以參考setDatabasePath(String path)
getSettings.setDatabaseEnabled(false);
34、setDomStorageEnabled(boolean flag)
設(shè)置是否開(kāi)啟DOM存儲(chǔ)API權(quán)限,默認(rèn)false,未開(kāi)啟,設(shè)置為true,WebView能夠使用DOM storage API
getSettings.setDomStorageEnabled(true);
35、setGeolocationEnabled(boolean flag)
設(shè)置是否開(kāi)啟定位功能,默認(rèn)true,開(kāi)啟定位
getSettings.setGeolocationEnabled(false);
36、setJavaScriptCanOpenWindowsAutomatically(boolean flag)
設(shè)置腳本是否允許自動(dòng)打開(kāi)彈窗,默認(rèn)false,不允許
getSettings.setJavaScriptCanOpenWindowsAutomatically(true);
37、setDefaultTextEncodingName(String encoding)
設(shè)置WebView加載頁(yè)面文本內(nèi)容的編碼,默認(rèn)“UTF-8”。
getSettings.setDefaultTextEncodingName("UTF-8");
38、setUserAgentString(String ua)
設(shè)置WebView代理字符串,如果String為null或?yàn)榭?,將使用系統(tǒng)默認(rèn)值
getSettings.setUserAgentString(String ua);
39、setNeedInitialFocus(boolean flag)
設(shè)置WebView是否需要設(shè)置一個(gè)節(jié)點(diǎn)獲取焦點(diǎn)當(dāng)被回調(diào)的時(shí)候,默認(rèn)true
getSettings.setNeedInitialFocus(false);
40、setCacheMode(int mode)
重寫緩存被使用到的方法,該方法基于Navigation Type,加載普通的頁(yè)面,將會(huì)檢查緩存同時(shí)重新驗(yàn)證是否需要加載,如果不需要重新加載,將直接從緩存讀取數(shù)據(jù),允許客戶端通過(guò)指定LOAD_DEFAULT、LOAD_CACHE_ELSE_NETWORK、LOAD_NO_CACHE、LOAD_CACHE_ONLY其中之一重寫該行為方法,默認(rèn)值LOAD_DEFAULT
getSettings.setCacheMode(WebSettings.LOAD_DEFAULT);

LOAD_CACHE_ONLY: 不使用網(wǎng)絡(luò),只讀取本地緩存數(shù)據(jù)
LOAD_DEFAULT: 根據(jù)cache-control決定是否從網(wǎng)絡(luò)上取數(shù)據(jù)。
LOAD_CACHE_NORMAL: API level 17中已經(jīng)廢棄, 從API level 11開(kāi)始作用同LOAD_DEFAULT模式
LOAD_NO_CACHE: 不使用緩存,只從網(wǎng)絡(luò)獲取數(shù)據(jù).
LOAD_CACHE_ELSE_NETWORK,只要本地有,無(wú)論是否過(guò)期,或者no-cache,都使用緩存中的數(shù)據(jù)。
如:www.taobao.com的cache-control為no-cache,在模式LOAD_DEFAULT下,無(wú)論如何都會(huì)從網(wǎng)絡(luò)上取數(shù)據(jù),如果沒(méi)有網(wǎng)絡(luò),就會(huì)出現(xiàn)錯(cuò)誤頁(yè)面;在LOAD_CACHE_ELSE_NETWORK模式下,無(wú)論是否有網(wǎng)絡(luò),只要本地有緩存,都使用緩存。本地沒(méi)有緩存時(shí)才從網(wǎng)絡(luò)上獲取。
www.360.com.cn的cache-control為max-age=60,在兩種模式下都使用本地緩存數(shù)據(jù)。
根據(jù)以上兩種模式,建議緩存策略為,判斷是否有網(wǎng)絡(luò),有的話,使用LOAD_DEFAULT,無(wú)網(wǎng)絡(luò)時(shí),使用LOAD_CACHE_ELSE_NETWORK。

41、setMixedContentMode(int mode)
設(shè)置當(dāng)一個(gè)安全站點(diǎn)企圖加載來(lái)自一個(gè)不安全站點(diǎn)資源時(shí)WebView的行為,android.os.Build.VERSION_CODES.KITKAT默認(rèn)為MIXED_CONTENT_ALWAYS_ALLOW,android.os.Build.VERSION_CODES#LOLLIPOP默認(rèn)為MIXED_CONTENT_NEVER_ALLOW,取值其中之一:MIXED_CONTENT_NEVER_ALLOW、MIXED_CONTENT_ALWAYS_ALLOW、MIXED_CONTENT_COMPATIBILITY_MODE.
getSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);

下面貼上我自己的配置代碼:
WebSettings settings = webview.getSettings();
settings.setJavaScriptEnabled(true);//啟用js
settings.setJavaScriptCanOpenWindowsAutomatically(true);//js和android交互
String cacheDirPath = PathCommonDefines.WEBVIEW_CACHE;
settings.setAppCachePath(cacheDirPath); //設(shè)置緩存的指定路徑
settings.setAllowFileAccess(true); // 允許訪問(wèn)文件
settings.setAppCacheEnabled(true); //設(shè)置H5的緩存打開(kāi),默認(rèn)關(guān)閉
settings.setUseWideViewPort(true);//設(shè)置webview自適應(yīng)屏幕大小
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);//設(shè)置,可能的話使所有列的寬度不超過(guò)屏幕寬度
settings.setLoadWithOverviewMode(true);//設(shè)置webview自適應(yīng)屏幕大小
settings.setDomStorageEnabled(true);//設(shè)置可以使用localStorage
settings.setSupportZoom(false);//關(guān)閉zoom按鈕
settings.setBuiltInZoomControls(false);//關(guān)閉zoom
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
webview.setWebViewClient(new WebViewClient() {
@Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return false; }
@Override public void onLoadResource(WebView view, String url) { }
@Override public void onPageFinished(WebView view, String url) { } });

*10:html5跳原生界面*

**
**網(wǎng)頁(yè)跳原生界面的方法有很多種,比如js調(diào)java方法,或者是通過(guò)uri scheme啦,也可以通過(guò)自己解析url來(lái)做。
在這兒,考慮到兼容性,攔截的是url,并且在清單文件中自定義了scheme~

webview.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { parserURL(url); //解析url,如果存在有跳轉(zhuǎn)原生界面的url規(guī)則,則跳轉(zhuǎn)原生。 return super.shouldOverri deUrlLoading(view, url); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished (view, url); } @Override public void onLoadResource(WebView view, String url) { super.onLoadResource(view, url); } });

清單文件中,聲明一下 就可以在自帶瀏覽器通過(guò)uri scheme跳到本app頁(yè)面了,這個(gè)activity作為各個(gè)頁(yè)面的分發(fā)頁(yè)面,通過(guò) 這個(gè)界面解析數(shù)據(jù)決定接下來(lái)要跳轉(zhuǎn)哪個(gè)頁(yè)面:

<activity
android:name=".ui.webview.CommWebviewActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:process=":webview"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden">
<intent-filter>
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />

 <action android:name="android.intent.action.VIEW" /> 


<data 
   android:host="xxxx.com" 
   android:scheme="kingp2p" /> 

</intent-filter>
</activity>

Android中處理網(wǎng)頁(yè)時(shí)我們必然用到WebView,這里我們有這樣一個(gè)需求,我們想讓W(xué)ebView在處理網(wǎng)絡(luò)請(qǐng)求的時(shí)候?qū)⒛承┱?qǐng) 求攔截替換成某些特殊的資源。具體一點(diǎn)兒說(shuō),在WebView加載 http://m.sogou.com 時(shí),會(huì)加載一個(gè)logo圖片,我們的需 求就是將這個(gè)logo圖片換成另一張圖片。

shouldOverrideUrlLoading(攔截url加載,除資源請(qǐng)求的url) shouldInterceptRequest(攔截所有url請(qǐng)求)

shouldInterceptRequest:在每一次請(qǐng)求資源時(shí),都會(huì)通過(guò)這個(gè)函數(shù)來(lái)回調(diào),比如超鏈接、JS文件、CSS文件、圖片等,
也就是說(shuō)瀏覽器中每一次請(qǐng)求資源時(shí),都會(huì)回調(diào)回來(lái),無(wú)論任何資源!

但是必須注意的是shouldInterceptRequest函數(shù)是在非UI線程中執(zhí)行的,在其中不能直接做UI操作,如果需要做UI操作,則需要利用Handler來(lái)實(shí)現(xiàn)
好在Android中的WebView比較強(qiáng)大,從API 11(Android 3.0)開(kāi)始, shouldInterceptRequest被引入就是為了解決這一類的問(wèn)題。
shouldInterceptRequest這個(gè)回調(diào)可以通知主程序WebView處理的資源(css,js,image等)請(qǐng)求,并允許主程序進(jìn)行處理后返回?cái)?shù)據(jù)。如果主程序返回的數(shù)據(jù)為null,WebView會(huì)自行請(qǐng)求網(wǎng)絡(luò)加載資源,否則使用主程序提供的數(shù)據(jù)。注意這個(gè)回調(diào)發(fā)生在非UI線程中,所以進(jìn)行UI系統(tǒng)相關(guān)的操作是不可以的。
shouldInterceptRequest有兩種重載。
public WebResourceResponse shouldInterceptRequest (WebView view, String url) 從API 11開(kāi)始引入,API 21棄用
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) 從API 21開(kāi)始引入

本次例子暫時(shí)使用第一種,即shouldInterceptRequest (WebView view, String url)。
示例代碼

WebView webView = new WebView(this);

webView.setWebViewClient(new WebViewClient() {
@Override

public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

Log.i(LOGTAG, "shouldInterceptRequest url=" + url + ";threadInfo" + Thread.currentThread());
WebResourceResponse response = null;

if (url.contains("logo")) {

try {

InputStream localCopy = getAssets().open("droidyue.png");
response = new WebResourceResponse("image/png", "UTF-8", localCopy);}
catch (IOException e) {

e.printStackTrace();}}return response;}});

}
setContentView(webView);
webView.loadUrl("http://m.sogou.com");

}

其中WebResourceResponse需要設(shè)定三個(gè)屬性,MIME類型,數(shù)據(jù)編碼,數(shù)據(jù)(InputStream流形式)。

shouldOverrideUrlLoading(攔截url加載,除資源請(qǐng)求的url) shouldInterceptRequest(攔截所有url請(qǐng)求)

shouldOverrideUrlLoading:
這個(gè)函數(shù)會(huì)在加載超鏈接時(shí)回調(diào)過(guò)來(lái)
對(duì)網(wǎng)頁(yè)中超鏈接按鈕的響應(yīng)。當(dāng)按下某個(gè)連接時(shí)WebViewClient會(huì)調(diào)用這個(gè)方法,并傳遞參數(shù):按下的url。
通過(guò)重寫shouldOverrideUrlLoading,可以實(shí)現(xiàn)對(duì)網(wǎng)頁(yè)中超鏈接的攔截;
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url); //在當(dāng)前的webview中跳轉(zhuǎn)到新的url
return true;

} });

shouldoverrideurlloading返回true表明點(diǎn)擊網(wǎng)頁(yè)里面的鏈接還是在當(dāng)前的webview里跳轉(zhuǎn),不跳到瀏覽器那邊。
這個(gè)方法在手動(dòng)調(diào)用WebView.loadUrl(url);時(shí)是不會(huì)被調(diào)用的,再進(jìn)一步點(diǎn)擊才會(huì)走這個(gè)方法。
返回值是boolean類型,表示是否屏蔽WebView繼續(xù)加載URL的默認(rèn)行為,因?yàn)檫@個(gè)函數(shù)是WebView加載URL前回調(diào)的,所以如果我們r(jià)eturn true,則WebView接下來(lái)就不會(huì)再加載這個(gè)URL了,所有處理都需要在WebView中操作,包含加載。如果我們r(jià)eturn false,則系統(tǒng)就認(rèn)為上層沒(méi)有做處理,接下來(lái)還是會(huì)繼續(xù)加載這個(gè)URL的。在利用shouldOverrideUrlLoading來(lái)攔截URL時(shí),如果return true,則會(huì)屏蔽系統(tǒng)默認(rèn)的顯示URL結(jié)果的行為,不需要處理的URL也需要調(diào)用loadUrl()來(lái)加載進(jìn)WebVIew,不然就會(huì)出現(xiàn)白屏;如果return false,則系統(tǒng)默認(rèn)的加載URL行為是不會(huì)被屏蔽的,所以一般建議大家return false,我們只關(guān)心我們關(guān)心的攔截內(nèi)容,對(duì)于不攔截的內(nèi)容,讓系統(tǒng)自己來(lái)處理即可。

WebChromeClient與WebViewClient的區(qū)別
WebView的內(nèi)部實(shí)現(xiàn)并不是完全使用Chrome的內(nèi)核,而是部分使用Chome內(nèi)核,其它都是與Chrome不相同的;
WebViewClient:在影響View的事件到來(lái)時(shí),會(huì)通過(guò)WebViewClient中的方法回調(diào)通知用戶
WebChromeClient:當(dāng)影響瀏覽器的事件到來(lái)時(shí),就會(huì)通過(guò)WebChromeClient中的方法回調(diào)通知用法。
通過(guò)上面的對(duì)比,我們發(fā)現(xiàn)WebViewClient和WebChromeClient都是針對(duì)不同事件的回調(diào),而google將這些回調(diào)進(jìn)行分類集合,就產(chǎn)生了WebViewClient、WebChromeClient這兩個(gè)大類,其中管理著針對(duì)不同類型的回調(diào)而已。
在程序中,我們只需要加上mWebView.setWebChromeClient(new WebChromeClient());就可以實(shí)現(xiàn)confrim()、alert()、prompt()的彈出效果了
如:mWebView.setWebViewClient(new WebViewClient()); mWebView.setWebChromeClient(new WebChromeClient());
如果需要使網(wǎng)頁(yè)中的confrim()、alert()、prompt()函數(shù)生效,需要設(shè)置WebChromeClient!
除了alert,prompt,confirm以外,其它時(shí)候都不需要強(qiáng)制設(shè)置WebChromeClient
在使用onJsAlert來(lái)攔截alert對(duì)話框時(shí),如果不需要再?gòu)棾鯽lert對(duì)話框,一定要return true;在return false以后,會(huì)依然調(diào)用系統(tǒng)的默認(rèn)處理來(lái)彈出對(duì)話框的

*11:LoadData()與loadDataWithBaseURL()*

**
**通過(guò)loadUrl()來(lái)加載本地頁(yè)面和在線地址的方式需要很長(zhǎng)的加載時(shí)間,而LoadData()與loadDataWithBaseURL(),它們不是用來(lái)加載整個(gè)頁(yè)面文件的,而是用來(lái)加載一段代碼片的
比如可以先通過(guò)http將網(wǎng)頁(yè)HTML數(shù)據(jù)從服務(wù)器下載回來(lái),在通過(guò)這兩個(gè)方法就可以很快把頁(yè)面加載出來(lái),沒(méi)有很長(zhǎng)的訪問(wèn)網(wǎng)絡(luò)cDN解析等時(shí)間

在使用loadData時(shí),在數(shù)據(jù)里面不能出現(xiàn)英文字符:’#’, ‘%’, ‘\’ , ‘?’ 這四個(gè)字符,如果有的話可以用 %23, %25,%27, %3f,這些字符來(lái)替換
Android給我們提供了一個(gè)專門用來(lái)轉(zhuǎn)碼的函數(shù):URLEncoder.encode(String s, String charsetName) ,它能將沖突的字符進(jìn)行轉(zhuǎn)義,然后再傳給webview,這樣webview在加載時(shí)就不會(huì)有沖突了,如:mWebView.loadData(URLEncoder.encode(summary, "utf-8"), "text/html", "utf-8");
loadData()應(yīng)該是不能加載圖片的,為了防止字符沖突,在傳遞loadData的數(shù)據(jù)時(shí),必須使用URLEncoder.encode()函數(shù)來(lái)轉(zhuǎn)義,頁(yè)面的編碼格式必須與代碼中傳參的編碼格式一致,不然會(huì)導(dǎo)致亂碼

public void loadDataWithBaseURL(String baseUrl, String data,String mimeType, String encoding, String historyUrl)
參數(shù)意義如下:
String baseUrl:基準(zhǔn)URL,不需要可以傳null,它的意思是,如果data中的url是相對(duì)地址,則就會(huì)加上基準(zhǔn)url來(lái)拼接出完整的地址,比如baseUrl是http://img6.ph.126.net,data中有個(gè)Img標(biāo)簽,它的內(nèi)容是:<img src='hBiG96B8egigBULxUWcOpA==/109212290980771276.jpg'>,很明顯src的地址不是本地地址也不是在線地址,那它就是一個(gè)相對(duì)地址,所以加上baseUrl以后才是它的完整地址:http://img6.ph.126.net/hBiG96B8egigBULxUWcOpA==/109212290980771276.jpg
String mimeType:MIME類型
String encoding:編碼方式
String historyUrl:當(dāng)前的歷史記錄所要存儲(chǔ)的值。如果不需要可以傳Null,loadDataWithBaseURL它本身并不會(huì)向歷史記錄中存儲(chǔ)數(shù)據(jù),要想實(shí)現(xiàn)歷史記錄,需要我們自己來(lái)實(shí)現(xiàn);有關(guān)歷史記錄的實(shí)現(xiàn)方式是比較復(fù)雜的,歷史記錄是以Key/value的方式存儲(chǔ)在一個(gè)historyList里的,當(dāng)前進(jìn)后退時(shí),會(huì)用Key來(lái)取出對(duì)應(yīng)的value值來(lái)加載進(jìn)webview中。而Key就是這里的baseUrl,Value就是這里的historyUrl;history所指向的必須是一個(gè)頁(yè)面,并且頁(yè)面存在于SD卡中或程序中(assets);

如果網(wǎng)頁(yè)地址是絕對(duì)地址,本地文件地址也是絕對(duì)地址,而另外一個(gè)是相對(duì)地址的情況下,絕對(duì)的http地址,通過(guò)file指定的本地地址,對(duì)于這兩類的絕對(duì)地址,baseUrl是不起作用的,而對(duì)于第三個(gè)相對(duì)地址,是會(huì)啟用baseUrl的來(lái)拼接完整地址的。

這兩種方法,我建議使用后者,雖然loadData的歷史記錄不需要我們自己來(lái)實(shí)現(xiàn),但在使用時(shí),加載上后者比前者快一到兩倍。
另外loadData不能加載圖片,而loadDataWithBaseURL是可以加載圖片的

*總結(jié):*

**
**1:圖片延遲加載:圖片的網(wǎng)絡(luò)請(qǐng)求會(huì)造成帶寬緊張,影響到css或js文件加載完成的時(shí)間,造成頁(yè)面空白loading過(guò)久

2: 常用資源預(yù)加載,shouldInterceptRequest直接替換

3:常用資源的緩存shouldInterceptRequest攔截保存

4:合理使用http緩存策略:Expires , max-age, Etag , Last-Modified (其中Expires,max-age是客戶端在這個(gè)時(shí)間 之前 不去向服務(wù)器端發(fā)送請(qǐng)求驗(yàn)證資源是否有更新, Etag, Last-Modified是服務(wù)器決定是否需要返回資源,未更新的資源不需要返回)

5:getSettings.setCacheMode建議緩存策略為,判斷是否有網(wǎng)絡(luò),有的話,使用LOAD_DEFAULT,無(wú)網(wǎng)絡(luò)時(shí),使用LOAD_ CACHE_ELSE_NETWORK。

6:使用JSBridge,支持異步回調(diào)

7:將HTML以接口的形式返回,webView用loadDataWithBaseURL加載HTML,并且可以解決跨域行為的問(wèn)題

8:使用第三方 WebView 內(nèi)核 如騰訊的X5 內(nèi)核

9:WebView 導(dǎo)致的內(nèi)存泄露:讓 WebView 獨(dú)立運(yùn)行在一個(gè)進(jìn)程里減少由webview引起的內(nèi)存泄露對(duì)主進(jìn)程內(nèi)存的占用, 且不在 XML 里面聲明,而是在代碼中直接 new 出來(lái),傳入 application context 來(lái)防止 activity 引用被濫用

10:WebView.HitTestResult 這個(gè)函數(shù)可獲取到我們點(diǎn)擊的目標(biāo)類型來(lái)得到當(dāng)前點(diǎn)擊的資源是什么,可實(shí)現(xiàn)長(zhǎng)按下載圖片

11:支持URLscheme和魔窗來(lái)完成應(yīng)用內(nèi)和應(yīng)用間和微信中頁(yè)面的跳轉(zhuǎn)和恢復(fù)

12:還有就是具體的業(yè)務(wù)邏輯的優(yōu)化

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容