這里首屏優(yōu)化是指用戶安裝應(yīng)用后葵礼,首次打開(kāi)應(yīng)用消耗時(shí)間的優(yōu)化。 其中比較耗時(shí)的一點(diǎn)是首次打開(kāi)webview應(yīng)用并鸵,加載靜態(tài)資源鸳粉。 優(yōu)化思路是,首次打開(kāi)應(yīng)用园担,使用客戶端攔截請(qǐng)求届谈,返回本地文件。以后的請(qǐng)求優(yōu)化由ServiceWorker接管弯汰。
webview請(qǐng)求過(guò)程
一個(gè)靜態(tài)資源的請(qǐng)求艰山,分為以下4個(gè)過(guò)程按順序執(zhí)行
- ServiceWorker
- http緩存
- App應(yīng)用攔截
- 網(wǎng)絡(luò)服務(wù)
注意:sw.js文件的規(guī)則不在此列,sw.js文件好像始終請(qǐng)求網(wǎng)絡(luò)
有以下幾點(diǎn)需要注意:
第一步 ServiceWorker 必須是安裝好的
第一次進(jìn)入應(yīng)用咏闪,ServiceWorker并未安裝曙搬,所以會(huì)被直接跳過(guò),進(jìn)入http緩存
ServiceWorker的請(qǐng)求鸽嫂,也會(huì)先查看http緩存
就是說(shuō)纵装,從ServiceWorker發(fā)出的請(qǐng)求,也會(huì)遵循h(huán)ttp緩存規(guī)范据某,http緩存中有的文件橡娄,會(huì)被直接返回給ServiceWorker
只有ServiceWoker和http緩存都未命中,才會(huì)被App應(yīng)用攔截
App應(yīng)用攔截的限制
App應(yīng)用攔截webview請(qǐng)求癣籽,是通過(guò)復(fù)寫(xiě)shouldInterceptRequest方法實(shí)現(xiàn)
@TargetApi(VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
經(jīng)過(guò)測(cè)試挽唉,他有限制:
只會(huì)攔截html請(qǐng)求和寫(xiě)在html中的資源請(qǐng)求(即通過(guò)<link><script>等直接加載的資源。通過(guò)js請(qǐng)求的資源都不會(huì)被攔截筷狼,例如ajax請(qǐng)求瓶籽、main.js文件中請(qǐng)求的sw.js、ServiceWorker中發(fā)起的請(qǐng)求)埂材。沒(méi)有深入研究測(cè)試塑顺,理解也可能有誤。
優(yōu)化思路
App應(yīng)用安裝時(shí)候楞遏,將首屏所需靜態(tài)資源下載到本地文件中
首屏
首次打開(kāi)應(yīng)用茬暇,ServiceWorker未安裝首昔,也沒(méi)有任何http緩存寡喝,所以所有請(qǐng)求會(huì)直接被App應(yīng)用攔截,在這一步返回本地文件勒奇,達(dá)到秒開(kāi)效果预鬓。
同時(shí)不攔截sw.js文件(ServiceWorker的注冊(cè)文件),去網(wǎng)絡(luò)服務(wù)器請(qǐng)求sw.js文件進(jìn)行注冊(cè)安裝。
第二次打開(kāi)
因?yàn)樵谑灼链蜷_(kāi)應(yīng)用同時(shí)格二,sw.js也同時(shí)注冊(cè)安裝好了劈彪,同時(shí)通過(guò)cacheAll,也在ServiceWorker中緩存了最新的網(wǎng)絡(luò)服務(wù)器中文件顶猜。
所以第二次打開(kāi)沧奴,所有的請(qǐng)求,都可以被ServiceWorker進(jìn)行攔截處理长窄,根據(jù)上面的注意事項(xiàng)(通過(guò)js請(qǐng)求的資源都不會(huì)被App應(yīng)用攔截)滔吠,所以第二次及之后的請(qǐng)求,基本就和App應(yīng)用攔截?zé)o關(guān)了挠日。
ServiceWoker緩存的更新
- 在每次請(qǐng)求時(shí)候疮绷,返回ServiceWorker緩存同時(shí),請(qǐng)求最新文件(副作用嚣潜,在每次頁(yè)面版本發(fā)生改變時(shí)候冬骚,第二次進(jìn)入應(yīng)用會(huì)卡,因?yàn)榈谝淮畏祷豐erviceWorker緩存懂算,不會(huì)卡只冻,但是請(qǐng)求到了新的index.html川抡,第二次進(jìn)入應(yīng)用活烙,根據(jù)新的index.html,發(fā)生變化的靜態(tài)資源籽慢,都會(huì)從網(wǎng)絡(luò)請(qǐng)求等待酸役,第三次進(jìn)入應(yīng)用才不會(huì)卡)
- 更新sw.js文件時(shí)候住诸,更新所有靜態(tài)資源文件(未實(shí)踐,應(yīng)該可行涣澡,且無(wú)上面的副作用)
ServiceWoker文件sw.js自身的更新
對(duì)sw.js不設(shè)置緩存贱呐,每次都到網(wǎng)絡(luò)請(qǐng)求最新sw.js文件即可(對(duì)于如何安裝更新sw.js,本文不探討)
具體實(shí)現(xiàn)
首屏加載
1. App緩存文件存放位置
2. 聲明攔截列表
private void initData() {
// 主頁(yè)
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); // 同時(shí)去請(qǐng)求網(wǎng)絡(luò)
WebResourceResponse response =
mDataHelper.getReplacedWebResourceResponse(getApplicationContext(),
url);
if (response != null) {
return response;
}
}
return super.shouldInterceptRequest(view, request);
}
第二次加載
之后的處理流程都是類(lèi)似的
說(shuō)明
1. 和http緩存結(jié)合使用
ServiceWorker中的請(qǐng)求奄薇,也會(huì)去http緩存中抓取的,所以可以結(jié)合使用抗愁,緩存策略:
- index.html設(shè)定no-cache
- 靜態(tài)資源文件緩存1年(通過(guò)每次打包后新版本號(hào)更新)
以Nginx為例:
location / {
# root html;
root /Users/frru/nginxServers;
index index.html index.htm;
autoindex on; ##顯示索引
autoindex_exact_size on; ##顯示大小
autoindex_localtime on; ##顯示時(shí)間
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. 每次項(xiàng)目打包時(shí)候馁蒂,請(qǐng)同步更新sw.js文件,主要是更新其中cacheAll部分文件列表
例如版本變?yōu)関2之后蜘腌,sw.js中cacheAll部分應(yīng)該為
//Service Worker安裝事件沫屡,其中可以預(yù)緩存資源
this.addEventListener('install', function(event) {
console.log('install');
//需要緩存的頁(yè)面資源
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. 注意:并未實(shí)踐
以上部分,關(guān)于sw.js部分的主動(dòng)更新文件撮珠,我并未實(shí)現(xiàn)調(diào)試沮脖,理論上可行。之前我是采取邊返還ServiceWorker緩存,邊請(qǐng)求最新數(shù)據(jù)的方案勺届,副作用上面已經(jīng)說(shuō)明了驶俊。
4. sw.js主動(dòng)更新文件和邊返回緩存,邊加載新資源免姿,可以結(jié)合使用
5. 關(guān)于緩存文件列表
現(xiàn)在打包工具一般都會(huì)生成靜態(tài)資源列表饼酿,例如Create React App打包項(xiàng)目會(huì)生成一個(gè)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)文件胚膊。
也可以用來(lái)生成ServiceWorker主動(dòng)獲取文件列表
騰訊VasSonic框架
簡(jiǎn)介:騰訊SNG增值產(chǎn)品部技術(shù)團(tuán)隊(duì)研發(fā)的輕量級(jí)高性能Hybrid框架VasSonic
官方介紹文檔
業(yè)務(wù)場(chǎng)景
主要是應(yīng)對(duì)手Q的業(yè)務(wù)嗜湃,一些重點(diǎn)常用業(yè)務(wù),例如游戲分發(fā)中心澜掩、會(huì)員特權(quán)中心购披、個(gè)性裝扮商場(chǎng)等,是H5頁(yè)面肩榕,首屏加載時(shí)候刚陡,會(huì)比較慢。
隨著業(yè)務(wù)發(fā)展株汉,有些業(yè)務(wù)形態(tài)發(fā)生了變化筐乳,例如個(gè)性推薦是動(dòng)態(tài)內(nèi)容。
解決方案
VasSonic的前身
-
終端優(yōu)化
一些常見(jiàn)的首頁(yè)優(yōu)化:懶加載乔妈、X5內(nèi)核預(yù)加載蝙云、WebView對(duì)象復(fù)用 - 靜態(tài)直出
-
離線預(yù)推(即熟悉的離線包)
VasSonic做了功能增強(qiáng),即離線增量包路召,大大減少了包大小勃刨。 -
動(dòng)態(tài)直出功能
因?yàn)闃I(yè)務(wù)形態(tài)發(fā)生了變化(個(gè)性推薦),做了對(duì)應(yīng)的功能股淡。
實(shí)時(shí)拉取用戶數(shù)據(jù)并在服務(wù)端渲染后返回給客戶端身隐,也就是動(dòng)態(tài)直出的方案。 - webso的嘗試
QQ空間技術(shù)團(tuán)隊(duì)在解決動(dòng)態(tài)直出方面的wns+html解決方案唯灵。不太適合手Q業(yè)務(wù)場(chǎng)景贾铝。有一定的借鑒參考。
綜上埠帕,不管改進(jìn)下垢揩,最后誕生了VasSonic。
VasSonic的誕生
- 并行加載
就是把WebView初始化和請(qǐng)求資源由原來(lái)的串行改成并行敛瓷,減少等待時(shí)間叁巨。 - 動(dòng)態(tài)緩存
把頁(yè)面拆分成靜態(tài)部分和動(dòng)態(tài)部分,動(dòng)態(tài)部分更新后去主動(dòng)更新界面琐驴。 - 頁(yè)面分離
對(duì)上面動(dòng)態(tài)緩存的具體實(shí)現(xiàn)
后面的小章節(jié)都是具體使用VasSonic的規(guī)范俘种。
?绝淡?宙刘?
這兩種方式感覺(jué)都是針對(duì)手Q業(yè)務(wù)場(chǎng)景,在用戶打開(kāi)手Q時(shí)候牢酵,預(yù)加載一些重要頁(yè)面和預(yù)判頁(yè)面悬包。
騰訊瀏覽服務(wù) Service Worker最佳實(shí)踐
ServiceWorker的不用講了,這里關(guān)于首屏的加載馍乙,比較狠布近,直接打成壓縮文件,讓x5內(nèi)核注冊(cè)成ServiceWorker丝格,省去了用戶需要打開(kāi)頁(yè)面才能注冊(cè)ServiceWorker的過(guò)程撑瞧。
X5內(nèi)核Service Worker功能擴(kuò)展
首次訪問(wèn)解決方案
首次訪問(wèn)解決方案旨在用戶訪問(wèn)業(yè)務(wù)前實(shí)現(xiàn)業(yè)務(wù)的資源緩存,讓用戶在第一次真正訪問(wèn)業(yè)務(wù)時(shí)能夠讓業(yè)務(wù)頁(yè)面以最快的速度展示出來(lái)显蝌。針對(duì)該主旨预伺,X5內(nèi)核實(shí)現(xiàn)了三套具體實(shí)現(xiàn)方案:
- 離線包方式
- X5內(nèi)核后臺(tái)云下發(fā)指令
- X5內(nèi)核擴(kuò)展接口