這里首屏優(yōu)化是指用戶安裝應(yīng)用后,首次打開應(yīng)用消耗時間的優(yōu)化。 其中比較耗時的一點是首次打開webview應(yīng)用,加載靜態(tài)資源。 優(yōu)化思路是,首次打開應(yīng)用,使用客戶端攔截請求,返回本地文件。以后的請求優(yōu)化由ServiceWorker接管。
webview請求過程
一個靜態(tài)資源的請求,分為以下4個過程按順序執(zhí)行
- ServiceWorker
- http緩存
- App應(yīng)用攔截
- 網(wǎng)絡(luò)服務(wù)
注意:sw.js文件的規(guī)則不在此列,sw.js文件好像始終請求網(wǎng)絡(luò)

有以下幾點需要注意:
第一步 ServiceWorker 必須是安裝好的
第一次進(jìn)入應(yīng)用,ServiceWorker并未安裝,所以會被直接跳過,進(jìn)入http緩存
ServiceWorker的請求,也會先查看http緩存
就是說,從ServiceWorker發(fā)出的請求,也會遵循h(huán)ttp緩存規(guī)范,http緩存中有的文件,會被直接返回給ServiceWorker
只有ServiceWoker和http緩存都未命中,才會被App應(yīng)用攔截
App應(yīng)用攔截的限制
App應(yīng)用攔截webview請求,是通過復(fù)寫shouldInterceptRequest方法實現(xiàn)
@TargetApi(VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
經(jīng)過測試,他有限制:
只會攔截html請求和寫在html中的資源請求(即通過<link><script>等直接加載的資源。通過js請求的資源都不會被攔截,例如ajax請求、main.js文件中請求的sw.js、ServiceWorker中發(fā)起的請求)。沒有深入研究測試,理解也可能有誤。
優(yōu)化思路
App應(yīng)用安裝時候,將首屏所需靜態(tài)資源下載到本地文件中
首屏
首次打開應(yīng)用,ServiceWorker未安裝,也沒有任何http緩存,所以所有請求會直接被App應(yīng)用攔截,在這一步返回本地文件,達(dá)到秒開效果。
同時不攔截sw.js文件(ServiceWorker的注冊文件),去網(wǎng)絡(luò)服務(wù)器請求sw.js文件進(jìn)行注冊安裝。
第二次打開
因為在首屏打開應(yīng)用同時,sw.js也同時注冊安裝好了,同時通過cacheAll,也在ServiceWorker中緩存了最新的網(wǎng)絡(luò)服務(wù)器中文件。
所以第二次打開,所有的請求,都可以被ServiceWorker進(jìn)行攔截處理,根據(jù)上面的注意事項(通過js請求的資源都不會被App應(yīng)用攔截),所以第二次及之后的請求,基本就和App應(yīng)用攔截?zé)o關(guān)了。
ServiceWoker緩存的更新
- 在每次請求時候,返回ServiceWorker緩存同時,請求最新文件(副作用,在每次頁面版本發(fā)生改變時候,第二次進(jìn)入應(yīng)用會卡,因為第一次返回ServiceWorker緩存,不會卡,但是請求到了新的index.html,第二次進(jìn)入應(yīng)用,根據(jù)新的index.html,發(fā)生變化的靜態(tài)資源,都會從網(wǎng)絡(luò)請求等待,第三次進(jìn)入應(yīng)用才不會卡)
- 更新sw.js文件時候,更新所有靜態(tài)資源文件(未實踐,應(yīng)該可行,且無上面的副作用)
ServiceWoker文件sw.js自身的更新
對sw.js不設(shè)置緩存,每次都到網(wǎng)絡(luò)請求最新sw.js文件即可(對于如何安裝更新sw.js,本文不探討)
具體實現(xiàn)
首屏加載


1. App緩存文件存放位置

2. 聲明攔截列表
private void initData() {
// 主頁
mMap.put(BaseUrl, "index.html");
mMap.put(BaseUrl+"/", "index.html");
mMap.put(BaseUrl + "/index.html", "index.html");
// js文件
mMap.put(BaseUrl + "/main-v1.js", "main.js");
// css文件
mMap.put(BaseUrl + "/static/css/main-v1.css", "static/css/main-v1.css");
// 圖片
mMap.put(BaseUrl + "/favicon.ico", "favicon.ico");
mMap.put(BaseUrl + "/images/log.png", "images/log.png");
}
3. App應(yīng)用攔截
@TargetApi(VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
String url = request.getUrl().toString();
Log.d(TAG, "shouldInterceptRequest>5.0: url = " + url);
if (!mIsLoadLocal) {
return super.shouldInterceptRequest(view, request);
}
if (mDataHelper.hasLocalResource(url)) {
Log.d(TAG, "shouldInterceptRequest>5.0: 資源命中:url="+url);
// super.shouldInterceptRequest(view, request); // 同時去請求網(wǎng)絡(luò)
WebResourceResponse response =
mDataHelper.getReplacedWebResourceResponse(getApplicationContext(),
url);
if (response != null) {
return response;
}
}
return super.shouldInterceptRequest(view, request);
}
第二次加載


之后的處理流程都是類似的
說明
1. 和http緩存結(jié)合使用
ServiceWorker中的請求,也會去http緩存中抓取的,所以可以結(jié)合使用,緩存策略:
- index.html設(shè)定no-cache
- 靜態(tài)資源文件緩存1年(通過每次打包后新版本號更新)
以Nginx為例:
location / {
# root html;
root /Users/frru/nginxServers;
index index.html index.htm;
autoindex on; ##顯示索引
autoindex_exact_size on; ##顯示大小
autoindex_localtime on; ##顯示時間
if ($request_uri ~* ".html|htm") {
add_header Cache-Control "no-cache";
}
if ($request_uri ~* ".css|js|png") {
expires 360d;
# add_header Cache-Control "public, max-age=2592000, s-maxage=2592000";
add_header wall "hey!guys!give me a star.";
}
}
2. 每次項目打包時候,請同步更新sw.js文件,主要是更新其中cacheAll部分文件列表
例如版本變?yōu)関2之后,sw.js中cacheAll部分應(yīng)該為
//Service Worker安裝事件,其中可以預(yù)緩存資源
this.addEventListener('install', function(event) {
console.log('install');
//需要緩存的頁面資源
var urlsToPrefetch = [
'./index.html',
'./main-v2.js',
'./static/css/main-v2.css',
'./static/image/log.png',
];
event.waitUntil(
caches.open(OFFLINE_CACHE_NAME).then(function(cache) {
return cache.addAll(urlsToPrefetch);
})
);
});
3. 注意:并未實踐
以上部分,關(guān)于sw.js部分的主動更新文件,我并未實現(xiàn)調(diào)試,理論上可行。之前我是采取邊返還ServiceWorker緩存,邊請求最新數(shù)據(jù)的方案,副作用上面已經(jīng)說明了。
4. sw.js主動更新文件和邊返回緩存,邊加載新資源,可以結(jié)合使用
5. 關(guān)于緩存文件列表
現(xiàn)在打包工具一般都會生成靜態(tài)資源列表,例如Create React App打包項目會生成一個asset-manifest.json文件:
{
"main.css": "./static/css/main.484bfb43.chunk.css",
"main.js": "./static/js/main.a763f74c.chunk.js",
"main.js.map": "./static/js/main.a763f74c.chunk.js.map",
"runtime~main.js": "./static/js/runtime~main.9eb600ee.js",
"runtime~main.js.map": "./static/js/runtime~main.9eb600ee.js.map",
"static/css/2.81f174d0.chunk.css": "./static/css/2.81f174d0.chunk.css",
"static/js/2.3caf1636.chunk.js": "./static/js/2.3caf1636.chunk.js",
"static/js/2.3caf1636.chunk.js.map": "./static/js/2.3caf1636.chunk.js.map",
"index.html": "./index.html",
"precache-manifest.95764df349dcc4f784ad6dbed9254278.js": "./precache-manifest.95764df349dcc4f784ad6dbed9254278.js",
"service-worker.js": "./service-worker.js",
"static/css/2.81f174d0.chunk.css.map": "./static/css/2.81f174d0.chunk.css.map",
"static/css/main.484bfb43.chunk.css.map": "./static/css/main.484bfb43.chunk.css.map"
}
App應(yīng)用可以利用,拉取靜態(tài)文件。
也可以用來生成ServiceWorker主動獲取文件列表
騰訊VasSonic框架
簡介:騰訊SNG增值產(chǎn)品部技術(shù)團(tuán)隊研發(fā)的輕量級高性能Hybrid框架VasSonic
官方介紹文檔
業(yè)務(wù)場景
主要是應(yīng)對手Q的業(yè)務(wù),一些重點常用業(yè)務(wù),例如游戲分發(fā)中心、會員特權(quán)中心、個性裝扮商場等,是H5頁面,首屏加載時候,會比較慢。
隨著業(yè)務(wù)發(fā)展,有些業(yè)務(wù)形態(tài)發(fā)生了變化,例如個性推薦是動態(tài)內(nèi)容。
解決方案
VasSonic的前身
-
終端優(yōu)化
一些常見的首頁優(yōu)化:懶加載、X5內(nèi)核預(yù)加載、WebView對象復(fù)用 - 靜態(tài)直出
-
離線預(yù)推(即熟悉的離線包)
VasSonic做了功能增強,即離線增量包,大大減少了包大小。 -
動態(tài)直出功能
因為業(yè)務(wù)形態(tài)發(fā)生了變化(個性推薦),做了對應(yīng)的功能。
實時拉取用戶數(shù)據(jù)并在服務(wù)端渲染后返回給客戶端,也就是動態(tài)直出的方案。 - webso的嘗試
QQ空間技術(shù)團(tuán)隊在解決動態(tài)直出方面的wns+html解決方案。不太適合手Q業(yè)務(wù)場景。有一定的借鑒參考。
綜上,不管改進(jìn)下,最后誕生了VasSonic。
VasSonic的誕生
- 并行加載
就是把WebView初始化和請求資源由原來的串行改成并行,減少等待時間。 - 動態(tài)緩存
把頁面拆分成靜態(tài)部分和動態(tài)部分,動態(tài)部分更新后去主動更新界面。 - 頁面分離
對上面動態(tài)緩存的具體實現(xiàn)
后面的小章節(jié)都是具體使用VasSonic的規(guī)范。
???

這兩種方式感覺都是針對手Q業(yè)務(wù)場景,在用戶打開手Q時候,預(yù)加載一些重要頁面和預(yù)判頁面。
騰訊瀏覽服務(wù) Service Worker最佳實踐
ServiceWorker的不用講了,這里關(guān)于首屏的加載,比較狠,直接打成壓縮文件,讓x5內(nèi)核注冊成ServiceWorker,省去了用戶需要打開頁面才能注冊ServiceWorker的過程。
X5內(nèi)核Service Worker功能擴(kuò)展
首次訪問解決方案
首次訪問解決方案旨在用戶訪問業(yè)務(wù)前實現(xiàn)業(yè)務(wù)的資源緩存,讓用戶在第一次真正訪問業(yè)務(wù)時能夠讓業(yè)務(wù)頁面以最快的速度展示出來。針對該主旨,X5內(nèi)核實現(xiàn)了三套具體實現(xiàn)方案:
- 離線包方式
- X5內(nèi)核后臺云下發(fā)指令
- X5內(nèi)核擴(kuò)展接口