前面的章節(jié)中,我們創(chuàng)建了wordpress主題trans的公共模板:header.php頭部模板、sidebar.php側(cè)邊欄模板、footer.php底部模板,這樣就更加方便了trans主題的其它動(dòng)態(tài)模板的開發(fā)。就比如,今天我們要開發(fā)的模板——文章列表頁(yè)模板,就不需要再去修改頭部、側(cè)邊欄、底部的代碼了。好了,閑話不多說,我們直接進(jìn)入到今天的主題當(dāng)中,怎樣添加trans主題的文章列表頁(yè)模板?
第一步:創(chuàng)建列表頁(yè)模板文件。
在trans主題目錄下創(chuàng)建一個(gè)列表頁(yè)模板的文件——archive.php。wordpress程序默認(rèn)的文章列表頁(yè)模板的名字必須是:archive 或 categoty,也就是說,可以是archive.php,也可以是category.php,我們這里用archive.php。
第二步:引入頭部模板。
用sublime等編輯器打開trans主題的靜態(tài)模板list.html,把你的代碼復(fù)制到archive.php文件中。然后,在代碼找到</header>這句代碼,從<!doctype html>與</header>之間的所有代碼全部刪除,因?yàn)檫@段代碼,我們已經(jīng)可以使用公共模板——header.php來(lái)代替了。
刪除后,我們?cè)賮?lái)引入header.php這個(gè)頭部模板:
< ?php get_header(); ?>
這時(shí),我們可以正常打開列表頁(yè)了。但是有一個(gè)小問題:標(biāo)題顯示的不是文章分類的名稱,仍然是網(wǎng)站的名稱。而我們需要的效果是,在列表頁(yè)時(shí),我們需要顯示的是文章分類目錄的名稱。所以,我們要在header.php文件中的<title></title>標(biāo)簽中修改一個(gè)調(diào)用代碼,
原代碼是:
< title>< ?php echo get_bloginfo("name"); ?></title>
修改成如下代碼:
< title>< ?php if(is_home()){echo get_bloginfo("name"); }else{if(is_category()){$cat_c = get_the_category();echo $cat_c[0]->cat_name; echo " - "; bloginfo("name");}else{the_title(); echo " - "; bloginfo("name");} } ?></title>
這段代碼的意思是:如果是網(wǎng)站的首頁(yè),就顯示網(wǎng)站的名稱;如果是列表頁(yè),就顯示為“分類目錄名稱 + 網(wǎng)站名稱”;否則就顯示“文章標(biāo)題+網(wǎng)站名稱”。
第三步:引入右側(cè)邊欄模板。
在archive.php中找到< div class="c_right">標(biāo)簽中的所有代碼,刪除掉,然后,在當(dāng)前位置上,引入公共模板sidebar.php側(cè)邊欄模板,代碼如下:
< ?php get_sidebar(); ?>
這樣,我們就不需要再去修改原< div class="c_right">標(biāo)簽內(nèi)的代碼了,而只需直接把sidebar.php拿來(lái)用就可以了。
第四步:引入底部模板。
同上,在archive.php代碼中找到< footer>標(biāo)簽,然后,把< footer>及它后面的所有代碼全部刪除掉,現(xiàn)在不需要了,因?yàn)椋覀冇衒ooter.php底部公共模板了。刪除掉后,我們?cè)谠恢蒙现苯右脒@個(gè)footer.php底部模板,代碼如下:
< ?php get_footer(); ?>
在引入完頭部、側(cè)邊欄、底部的公共模板后,我們?cè)賮?lái)看看archive.php的模板代碼,如下圖:
然后,我們只需要修改archive.php模板代碼中的 < div class="c_left">標(biāo)簽內(nèi)部的代碼就可以了。
第五步:修改左側(cè)主體部分。
其實(shí),我們查看trans主題的靜態(tài)代碼的效果時(shí),我們可以看到,列表頁(yè)與首頁(yè)基本上是一樣的,只是左側(cè)的頂部多了一個(gè)面包屑導(dǎo)航。所以,我們只需要修改< div class="c_left">標(biāo)簽里的代碼就可以了(如上圖所示)。我們可以先把a(bǔ)rchive.php代碼中的< div class="c_left">里的代碼全部刪除掉,然后把首頁(yè)模板index.php代碼中的< div class="c_left">所有代碼全部復(fù)制下來(lái),粘貼到archive.php中。這段代碼包括了左側(cè)的文章列表以及分頁(yè)按鈕。這樣,archive.php的左側(cè)主體部分基本弄好。
第六步:添加面包屑導(dǎo)航。
我們?cè)赼rchive.php模板的< div class="c_left"> 標(biāo)簽中的< div class="left_bottom">標(biāo)簽的上方,添加發(fā)下代碼:
< div class="left_top">< ul>< li><span class="dashicons-before dashicons-admin-generic"></span><?php echo $cat_c[0]->cat_name; //獲取當(dāng)前分類名 ?></li>< li>< a href="< ?php bloginfo("siteurl"); ?>">< span class="dashicons-before dashicons-admin-home"></span>首頁(yè)</a> > < ?php the_category(","); ?></li></ul>< ul>< ?php $cat_desc = $cat_c[0]->category_description; //獲取當(dāng)前分類的描述。if($cat_desc){echo $cat_desc; }else{echo $cat_c[0]->cat_name; //獲取當(dāng)前分類名}?></ul></div>
這段代碼中,我們又用到了幾個(gè)wordpress的函數(shù):
the_category(","):獲取當(dāng)前分類導(dǎo)航;$cat_c = get_the_category():獲取當(dāng)前分類的信息,并賦值給$cat_c這個(gè)變量
面包屑導(dǎo)航的主要作用,就是讓用戶可以看到當(dāng)前頁(yè)面所在的個(gè)體位置。而且,可以通過點(diǎn)擊面包屑導(dǎo)航中的相關(guān)鏈接,可以跳轉(zhuǎn)到鏈接頁(yè)面,如:返回首頁(yè)(如下圖)。
?通過上面的幾步,我們就完成了wordpress主題trans的文章列表頁(yè)動(dòng)態(tài)模板的修改。嗯,是不是有一個(gè)小小的發(fā)現(xiàn):修改trans主題的列表頁(yè)模板要比修改首頁(yè)模板index.php所花的時(shí)間要少得多。是的,因?yàn)樵谛薷膇ndex.php首頁(yè)模板時(shí),我們需要所有的代碼,而修改archive.php模板時(shí),我們只需要把公共模板拿來(lái)直接用就可以了,省去了大量的時(shí)間,這就是公共模板的魅力之所在。好了,本節(jié)就介紹到這里,如有疑問,歡迎點(diǎn)評(píng)。
作為一個(gè)內(nèi)容類應(yīng)用,看新聞讀資訊一直是頭條用戶的核心需求,頁(yè)面的打開速度直接關(guān)系到用戶使用頭條的核心體驗(yàn),在頭條中,為了更多的承載足夠豐富的樣式和邏輯下保持多端體驗(yàn)的統(tǒng)一,詳情頁(yè)的內(nèi)容我們是通過 WebView 來(lái)承載的,但 WebView 本身的性能相比 Native 來(lái)說比較差,因此,技術(shù)團(tuán)隊(duì)一直致力于優(yōu)化詳情頁(yè)的加載速度。
經(jīng)過不斷的優(yōu)化,目前中詳情頁(yè)在線上的打開體驗(yàn),從肉眼上基本已經(jīng)感知不到加載過程。在接下來(lái)這篇文章里,我們會(huì)逐步拆解和介紹我們對(duì)詳情頁(yè)加載優(yōu)化的思路和實(shí)踐。
先讓我們來(lái)看看優(yōu)化前后的效果吧~
詳情頁(yè)加載體驗(yàn)優(yōu)化前
詳情頁(yè)加載體驗(yàn)優(yōu)化后
當(dāng)我們開始著手優(yōu)化頁(yè)面加載速度之前,我們需要明確一個(gè)問題,怎樣才是用戶真正體驗(yàn)到的頁(yè)面加載時(shí)間。
首先我們可以看下面這個(gè)公式:
頁(yè)面加載時(shí)間 = 頁(yè)面加載完成時(shí)間 - 頁(yè)面開始加載時(shí)間
頁(yè)面開始加載時(shí)間很好確定,當(dāng)用戶點(diǎn)擊了 Feed 上的卡片,我們就可以認(rèn)為頁(yè)面開始加載了。
問題是怎么定義頁(yè)面加載完成了呢?從客戶端的角度上看,無(wú)論是 iOS 還是 Android,WebView 都提供了一個(gè) loadFinsih 的回調(diào),但在實(shí)際應(yīng)用中我們發(fā)現(xiàn),loadFinish 回調(diào)并不能反應(yīng)用戶的真實(shí)體驗(yàn)。
一般來(lái)說,WebView 渲染需要經(jīng)過下面幾個(gè)步驟
而 loadFinish 實(shí)際上是在頁(yè)面加載完畢階段,而 DOM 構(gòu)建完成時(shí)頁(yè)面結(jié)構(gòu)就已經(jīng)基本渲染完成,所以從用戶真實(shí)體驗(yàn)的角度出發(fā),我們以 DOM 結(jié)構(gòu)構(gòu)建完成(即 domReady)的時(shí)間點(diǎn)作為頁(yè)面加載完成時(shí)間點(diǎn)。
在詳情頁(yè)瀏覽過程中,除了頁(yè)面加載速度之外,還有一個(gè)特別影響用戶體驗(yàn)的問題,就是頁(yè)面的白屏,也是早期的時(shí)候用戶反饋比較多的問題,但有很多場(chǎng)景都可能導(dǎo)致詳情頁(yè)發(fā)生白屏,比如說網(wǎng)絡(luò)異常,WebView 異常等等,需要從用戶體驗(yàn)的角度出發(fā)去檢測(cè)用戶發(fā)生白屏的情況。
目前可以想到最直觀的方案就是對(duì) WebView 進(jìn)行截圖,遍歷截圖的像素點(diǎn)的顏色值,如果非白屏顏色的顏色點(diǎn)超過一定的閾值,就可以認(rèn)為不是白屏,目前需要考慮的是這個(gè)方案的性能問題和檢測(cè)時(shí)機(jī)。
iOS 中提供了 WebView 快照的接口獲取當(dāng)前 WebView 渲染的內(nèi)容,底層采用異步回調(diào)的實(shí)現(xiàn)方式,API 耗時(shí) 10ms 左右,用戶基本無(wú)感知。
- (void)takeSnapshotWithConfiguration:(nullable WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(UIImage * _Nullable snapshotImage, NSError * _Nullable error))completionHandler API_AVAILABLE(ios(11.0));
Android 中系統(tǒng)提供的獲取視圖內(nèi)容的接口為 getDrawingCache,API 耗時(shí)在 40ms 左右,性能損耗也不是特別大。
除了截圖的性能損耗,像素點(diǎn)檢測(cè)也是白屏檢測(cè)中比較耗時(shí)的場(chǎng)景,經(jīng)過實(shí)驗(yàn),我們把 WebView 截圖的圖片進(jìn)行縮小到原圖的 1/6,遍歷檢測(cè)圖片的像素點(diǎn),當(dāng)非白色的像素點(diǎn)大于 5% 的時(shí)候我們就認(rèn)為是非白屏的情況,可以相對(duì)高效檢測(cè)準(zhǔn)確得出詳情頁(yè)是否發(fā)生了白屏。
確定好口徑之后,我們還有需要明確的一個(gè)問題是,什么指標(biāo)可以反映用戶刷頭條時(shí)的真實(shí)體驗(yàn)。
最早的時(shí)候,我們用的是詳情頁(yè)頁(yè)面的頁(yè)面平均加載時(shí)長(zhǎng),也就是頁(yè)面加載時(shí)長(zhǎng)的總和/頁(yè)面 pv,在開始的時(shí)候這個(gè)指標(biāo)也的確可以明確我們的加載速度。
后來(lái)隨著詳情頁(yè)的加載優(yōu)化逐漸的深入,會(huì)發(fā)現(xiàn)平均加載時(shí)長(zhǎng)雖然也可以反映詳情頁(yè)加載速度,但是因?yàn)樵斍轫?yè)的 pv 比較高,如果使用平均加載速度化很多用戶體驗(yàn)問題就被平均掉了,并不能反映用戶的真實(shí)情況,后面我們又調(diào)整了口徑,將指標(biāo)調(diào)整為所有用戶進(jìn)入詳情頁(yè)的 80 分位值,比如說,假如頭條詳情頁(yè)加載速度 80 分位值是 1 秒,那么就說明 80% 的情況下用戶進(jìn)入詳情頁(yè)都能在 1s 內(nèi)加載完成,當(dāng)然經(jīng)過我們的不斷優(yōu)化,詳情頁(yè)加載的 80 分位值已經(jīng)能夠達(dá)到 0.3s 以內(nèi),也就是說,80% 的情況下用戶都能夠在 0.3s 內(nèi)完成頁(yè)面加載。
80分位優(yōu)化數(shù)據(jù)對(duì)比
再后來(lái)我們又發(fā)現(xiàn),在頭條詳情頁(yè)的量級(jí)下面,即使是 80 分位的數(shù)據(jù)也不能反應(yīng)許多長(zhǎng)尾用戶的真實(shí)情況,也為了更極致的追求詳情頁(yè)的加載性能,我們最后將詳情頁(yè)的性能口徑調(diào)整到 95 分位。到目前在我們的努力下,詳情頁(yè)的加載速度 95 分位也優(yōu)化了將近 80% 。
我們究竟做了什么呢,接下來(lái)會(huì)慢慢介紹一下。
如前所述,圖文詳情頁(yè)是通過 WebView 來(lái)承載的,而 WebView 承載頁(yè)面最簡(jiǎn)單的做法就是直接通過 URL 去加載一個(gè)線上頁(yè)面。那么先來(lái)一道簡(jiǎn)單的面試題,當(dāng)用戶從瀏覽器輸入一個(gè) URL 到頁(yè)面展現(xiàn)發(fā)生了什么呢?
之前已經(jīng)介紹過頁(yè)面的渲染流程了,現(xiàn)在我們?cè)俸?jiǎn)單看看用戶從點(diǎn)擊到看到頁(yè)面內(nèi)容需要經(jīng)歷如下幾個(gè)階段:
WebView 加載流程
可以看到,通過線上頁(yè)面加載用戶每次進(jìn)入詳情頁(yè)都要通過多次網(wǎng)絡(luò)加載,極容易受網(wǎng)絡(luò)波動(dòng)的影響,這種情況下,也無(wú)法保證頁(yè)面加載的時(shí)長(zhǎng)和成功率,極大的影響了用戶體驗(yàn)。
于是在頭條中,我們將新聞中標(biāo)題和正文內(nèi)容進(jìn)行拆分,把頭條詳情頁(yè)的公共樣式 CSS 和 邏輯 JS 都抽離出來(lái),形成一個(gè)獨(dú)立而完備的詳情頁(yè)模板,這樣我們就可以把模板直接內(nèi)置在客戶端中。
同時(shí)我們會(huì)與前端約定好的 JS 腳本,通過接口將正文內(nèi)容數(shù)據(jù)注入頁(yè)面完成詳情頁(yè)的頁(yè)面展示,通過該這種方式我們可以將接口放到客戶端上進(jìn)行請(qǐng)求。
這樣用戶進(jìn)入詳情頁(yè)的時(shí)候只需要本地加載模板,而且加載模板的時(shí)候也可以同時(shí)并行請(qǐng)求詳情頁(yè)數(shù)據(jù),再將數(shù)據(jù)注入進(jìn)模板中。
那么用戶點(diǎn)擊到看到頁(yè)面內(nèi)容只需要經(jīng)歷下面的階段:
模板拆分
如上圖所示,我們只需要通過一次網(wǎng)絡(luò)加載就可以完成頁(yè)面渲染。
還能不能更快一點(diǎn)呢?當(dāng)然能!
為了提高頁(yè)面的加載速度,客戶端通過一定的策略去預(yù)加載新聞數(shù)據(jù),這樣在理想狀態(tài)下用戶進(jìn)入頁(yè)面時(shí)看到頁(yè)面時(shí)就可以直接使用緩存的數(shù)據(jù),用戶在看新聞的時(shí)候可以實(shí)現(xiàn)完全離線化,避免受到網(wǎng)絡(luò)的影響。
本地加載
完全脫離了網(wǎng)絡(luò)加載之后,還能再快一點(diǎn)呢?當(dāng)然還是可以的!
當(dāng)全流程離線化之后,頁(yè)面加載的瓶頸就變成了本地模板的加載時(shí)間,所以我們接下來(lái)要做的就是優(yōu)化模板加載時(shí)間。
對(duì)于模板來(lái)說,我們做了兩件事情
通過上面優(yōu)化,我們就已經(jīng)將模板加載時(shí)間大大優(yōu)化了,但是還能不能更給力呢?還是可以的。
對(duì)于客戶端來(lái)說,當(dāng)模板跟數(shù)據(jù)分離之后,由于每次用戶點(diǎn)擊的時(shí)候加載的都是同一個(gè)模板,所以實(shí)際上,我們并不需要在用戶進(jìn)入頁(yè)面的時(shí)候才去創(chuàng)建 WebView 以及加載模板,我們只需要在合適的時(shí)機(jī)在后臺(tái)創(chuàng)建 WebView,并且提前預(yù)熱加載模板,當(dāng)用戶點(diǎn)擊進(jìn)入頁(yè)面的時(shí)候就能使用已經(jīng)加載好模板的 WebView,直接將詳情頁(yè)的內(nèi)容數(shù)據(jù)通過 JS 注入到頁(yè)面中,前端收到數(shù)據(jù)后進(jìn)行頁(yè)面渲染即可。
此時(shí)用戶進(jìn)入詳情頁(yè)實(shí)際就不再需要重新加載模板了,路徑就變成了:
模板預(yù)熱
可以看下,通過本地測(cè)試的模板預(yù)熱和數(shù)據(jù)預(yù)取的優(yōu)化效果,還是比較明顯的,基本上已經(jīng)達(dá)到了上面的截圖中的驗(yàn)證效果。
本地測(cè)試數(shù)據(jù)
當(dāng)我們拆分完模板和數(shù)據(jù)之后,數(shù)據(jù)上優(yōu)化已經(jīng)比較明顯,但我們說過,除了驗(yàn)證數(shù)據(jù),我們還需要看線上用戶的真實(shí)體驗(yàn)數(shù)據(jù),從 95 分位上看實(shí)際數(shù)據(jù)優(yōu)化卻不是很明顯,所以我們從數(shù)據(jù)上觀察,用戶預(yù)熱模板的命中率只有 53%,還有進(jìn)一步的提升空間。
模板預(yù)熱率
為了盡可能的提高頁(yè)面的加載速度,我們希望用戶每次進(jìn)入詳情頁(yè)的時(shí)候都能夠使用預(yù)熱好模板的 WebView,一般情況下,我們都會(huì)使用模板預(yù)創(chuàng)建池的手段來(lái)優(yōu)化用戶進(jìn)入詳情頁(yè)時(shí)的預(yù)熱模板命中率。
但其實(shí)在很多情況下,WebView 的創(chuàng)建是一個(gè)性能開銷比較大的操作,如果我們使用預(yù)創(chuàng)建池的方案,那么就會(huì)在后臺(tái)頻繁創(chuàng)建 WebView,這樣對(duì)用戶在 Feed 場(chǎng)景的瀏覽體驗(yàn)也會(huì)有一定的影響。
而且假如用戶頻繁且快速進(jìn)出詳情頁(yè)時(shí),實(shí)際場(chǎng)景中用戶也很容易遇到無(wú)法命中預(yù)熱模板的場(chǎng)景。
這個(gè)時(shí)候?yàn)榱藘?yōu)化用戶的體驗(yàn),如前文所述,我們每次使用的時(shí)候都是同一個(gè)模板,所以我們使用完當(dāng)前 WebView 之后,只需要在用戶退出頁(yè)面的時(shí)候把正文數(shù)據(jù)清空,這樣進(jìn)入下一個(gè)頁(yè)面的時(shí)候就能夠繼續(xù)復(fù)用這個(gè) WebView 重新注入數(shù)據(jù)即可。
通過這個(gè)手段,我們既避免了頻繁在后臺(tái)預(yù)創(chuàng)建 WebView 對(duì)用戶刷 Feed 體驗(yàn)的影響,把用戶進(jìn)入頁(yè)面時(shí)候的預(yù)熱模板命中率從 53% 提升到 92%,優(yōu)化了用戶體驗(yàn)。
預(yù)熱模板命中率
說完我們?cè)谀0?WebView 方面的優(yōu)化之后,再介紹一下我們?cè)趦?nèi)容請(qǐng)求上的優(yōu)化。
由于頭條詳情頁(yè)請(qǐng)求有以下特點(diǎn)
所以我們將詳情頁(yè)內(nèi)容數(shù)據(jù)分為靜態(tài)和動(dòng)態(tài)兩部分,將正文內(nèi)容、標(biāo)題、作者欄等用戶主要消費(fèi)的又基本不變的內(nèi)容托管到了 CDN 上。
CDN 的全稱是 Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò)。其目的是通過在現(xiàn)有的 Internet 中增加一層新的網(wǎng)絡(luò)架構(gòu),將網(wǎng)站的內(nèi)容發(fā)布到最接近用戶的網(wǎng)絡(luò)“邊緣”,使用戶可以就近取得所需的內(nèi)容,提高用戶訪問網(wǎng)站的響應(yīng)速度。CDN 有別于鏡像,因?yàn)樗如R像更智能,或者可以做這樣一個(gè)比喻:CDN=更智能的鏡像+緩存+流量導(dǎo)流。因而,CDN 可以明顯提高 Internet 網(wǎng)絡(luò)中信息流動(dòng)的效率。從技術(shù)上全面解決由于網(wǎng)絡(luò)帶寬小、用戶訪問量大、網(wǎng)點(diǎn)分布不均等問題,提高用戶訪問網(wǎng)站的響應(yīng)速度。
托管到 CDN 之后,全國(guó)各地的用戶可以直接從最佳節(jié)點(diǎn)就獲取到詳情頁(yè)數(shù)據(jù),也大大節(jié)省了帶寬成本。
1. 多域名備份
為了防止某個(gè) CDN 出現(xiàn)故障,導(dǎo)致服務(wù)雪崩,服務(wù)端會(huì)下發(fā)多個(gè) CDN 鏈接,當(dāng)用戶訪問當(dāng)前 CDN 節(jié)點(diǎn)的出異常時(shí),可以快速自動(dòng)切換到下個(gè) CDN 節(jié)點(diǎn)。
2. 快速超時(shí)
一般的超時(shí)策略,客戶端在請(qǐng)求時(shí),會(huì)遍歷請(qǐng)求 CDN 1、2、3。如果這些 CDN 都請(qǐng)求失敗,則整個(gè)網(wǎng)絡(luò)請(qǐng)求算作失敗。
但這個(gè)方案的問題是,假設(shè)請(qǐng)求 CDN 的超時(shí)時(shí)間是 15s。如果 CDN 1 出現(xiàn)故障,則需要等待 15s 才能切換到 CDN 2 上,這對(duì)于詳情頁(yè)的加載時(shí)間來(lái)說是不可接受,如果用戶網(wǎng)絡(luò)突然變差,則需要等待 45s 才能返回失敗展示錯(cuò)誤頁(yè)。
基于此我們?cè)O(shè)計(jì)了詳情頁(yè)請(qǐng)求的快速動(dòng)態(tài)超時(shí)策略
幾個(gè) case:
可以看到,通過多域名備份和快速超時(shí)的策略,即使用戶在網(wǎng)絡(luò)或者服務(wù)異常的情況下,也能快速恢復(fù)或者讓用戶能感知到自身網(wǎng)絡(luò)問題。
當(dāng)我們?cè)谀0鍖雍途W(wǎng)絡(luò)層優(yōu)化到極致的時(shí)候,限制我們的就是 WebView 的渲染速度了!
正常來(lái)講,正常的內(nèi)容數(shù)據(jù)可能是類似 JSON 等數(shù)據(jù),客戶端獲取到數(shù)據(jù)之后,將數(shù)據(jù)注入給前端,前端還需要將 JSON 數(shù)據(jù)跟模板進(jìn)行組裝,拼上 HTML 標(biāo)簽等模板了之后再呈現(xiàn)到 WebView 渲染,導(dǎo)致前端渲染上耗時(shí)也比較久。
為了提高用戶的首屏效率,我們?cè)诜?wù)端就會(huì)把所有的詳情頁(yè)正文的 HTML 數(shù)據(jù)組裝好,通過將服務(wù)端直出內(nèi)容注入到頁(yè)面中時(shí),可以直接給 WebView 進(jìn)行渲染,對(duì)于其他動(dòng)態(tài)下發(fā)的內(nèi)容(比如相關(guān)搜索),前端再進(jìn)行二次異步處理,提升用戶效率。
一般來(lái)說,我們正文中所有內(nèi)容都是通過 WebView 渲染,經(jīng)過上述的優(yōu)化之后,文章的文字部分渲染效率已經(jīng)很高了,但是實(shí)際場(chǎng)景中,很多文章會(huì)包含比較多的圖片和視頻場(chǎng)景。
在實(shí)際場(chǎng)景中,WebView 渲染非文字內(nèi)容會(huì)存在以下問題:
所以在詳情頁(yè)中,我們會(huì)將圖片和視頻等非文字內(nèi)容通過原生組件的方式放在客戶端進(jìn)行渲染,既可以提高渲染效率,也可以減少不必要的流量消耗。
原生化渲染還有一個(gè)好處,圖片越來(lái)越成為文章體驗(yàn)的重要部分,對(duì)于多圖文章,我們?cè)?Feed 頁(yè)面也可以智能加載詳情頁(yè)需要的圖片,增加用戶的文章首屏體驗(yàn)。
講完了性能優(yōu)化,最后再分享一下我們對(duì)詳情頁(yè)白屏率的一些優(yōu)化,其實(shí)很多用戶反饋白屏問題大部分都可能是由于網(wǎng)絡(luò)等問題導(dǎo)致頁(yè)面加載時(shí)間過長(zhǎng),導(dǎo)致用戶從體驗(yàn)上觀感是白屏了,這部分通過上面分享的性能優(yōu)化手段已經(jīng)能夠解決,所以下面只是簡(jiǎn)單介紹下一些非網(wǎng)絡(luò)原因的白屏問題。
我們通過白屏檢測(cè)和上報(bào)之后的數(shù)據(jù)分析之后發(fā)現(xiàn),非網(wǎng)絡(luò)原因?qū)е碌脑斍轫?yè)的白屏問題大體是 WebView 加載的問題。
在 iOS 中,我們使用的是系統(tǒng)提供的 WKWebView,WKWebView 是運(yùn)行在一個(gè)獨(dú)立進(jìn)程中的組件,所以當(dāng) WKWebView 上占用內(nèi)存過大時(shí),WKWebView 所在的 WebContent Process 會(huì)被系統(tǒng) kill 掉,反映在用戶體驗(yàn)上就是發(fā)生了白屏。
根據(jù)網(wǎng)上的做法,我們可以在 WKWebView 提供的回調(diào) webViewWebContentProcessDidTerminate 函數(shù)中通過 reload 方法重新加載當(dāng)前頁(yè)面恢復(fù),但是這種情況只適用于通過 loadRequest 加載的請(qǐng)求,在詳情頁(yè)中,由于使用了模板化的 WebView 中,重新 reload 只能重新 reload 模板,并不能正常恢復(fù)整個(gè)詳情頁(yè),需要客戶端重新加載模板之后再重新注入數(shù)據(jù)。
另外由于我們有預(yù)熱模板的邏輯,所以可能在進(jìn)入詳情頁(yè)的時(shí)候使用的 WKWebView 就已經(jīng)崩潰,在調(diào)用 JS 注入數(shù)據(jù)時(shí)會(huì)直接返回失敗,失敗時(shí),我們會(huì)嘗試重新加載模板。但后來(lái)實(shí)際操作中發(fā)現(xiàn)一個(gè)問題,如果直接調(diào)用數(shù)據(jù)注入的方法,等待系統(tǒng) WebView 返回失敗的回調(diào)耗時(shí)比較久,所以后續(xù)也調(diào)整了數(shù)據(jù)注入的接口,我們提前在注入的腳本中判斷是否存在數(shù)據(jù)注入的接口,如果不存在,就說明模板存在問題,直接重試即可。
而在 Android 中,我們采用的是自研內(nèi)核 WebView,也會(huì)遇到一些奇奇怪怪的坑。
當(dāng)然不管是 iOS 和 Android, WebView 加載的邏輯都比較復(fù)雜,有時(shí)候怎么重試也無(wú)法成功,這個(gè)時(shí)候我們會(huì)直接降級(jí)到加載線上的詳情頁(yè),優(yōu)先保證用戶的體驗(yàn)。
限于篇幅原因,我們還做了很多其他事情,包括請(qǐng)求精簡(jiǎn),push 文章預(yù)拉取,數(shù)據(jù)注入的方式優(yōu)化等等,也做了很多其他的方向的探索,這里不做展開,希望有機(jī)會(huì)能再分享給大家。
最后總結(jié)一下我們?cè)趦?yōu)化詳情頁(yè)打開速度之后的一些想法
Android Camera 內(nèi)存問題剖析
字節(jié)跳動(dòng)自研線上引流回放系統(tǒng)的架構(gòu)演進(jìn)
iOS大解密:玄之又玄的KVO
Android '秒' 級(jí)編譯速度優(yōu)化
技術(shù)團(tuán)隊(duì)不僅致力于在業(yè)務(wù)上不斷深耕挖掘,在技術(shù)上也一直在追求極致的用戶體驗(yàn)。
如果你也向往在一個(gè)億級(jí) DAU 業(yè)務(wù)里成長(zhǎng),也期待在技術(shù)上有突飛猛進(jìn)的提升,歡迎你加入我們。
無(wú)論你是 iOS/Android/前端/后端,我們?cè)谏钲?北京/廣州等你來(lái),一起做更有挑戰(zhàn)的事!簡(jiǎn)歷投遞郵箱: tech@bytedance.com ;郵件標(biāo)題:姓名-工作年限-頭條技術(shù)團(tuán)隊(duì)。
歡迎關(guān)注字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)
頁(yè)切圖過程中div+css命名規(guī)則
標(biāo)簽屬性命名規(guī)范 (建議)
下劃線連接符命名法“hello_world”
中杠 連接符命名法“hello-world”
駱駝式命名法“helloWorld”
內(nèi)容:content/container 導(dǎo)航:nav 側(cè)欄:sidebar
欄目:column 標(biāo)志:logo 頁(yè)面主體:main
廣告:banner 熱點(diǎn):hot 新聞:news
下載:download 子導(dǎo)航:subnav 菜單:menu
搜索:search 頁(yè)腳:footer 滾動(dòng):scroll
版權(quán):copyright 友情鏈接:friendlink 子菜單:submenu
內(nèi)容:content 標(biāo)簽頁(yè):tab 文章列表:list
注冊(cè):regsiter 提示信息:msg 小技巧:tips
加入:joinus 欄目標(biāo)題:title 指南:guild
服務(wù):service 狀態(tài):status 投票:vote
尾:footer 合作伙伴:partner 登錄條:loginbar
頁(yè)面外圍控制整體布局寬度:wrapper 左右中:left right center
(二)注釋的寫法:
/* Footer */
內(nèi)容區(qū)
/* End Footer */
(三)id(具有唯一性)的命名:
(1)頁(yè)面結(jié)構(gòu)
容器: container 頁(yè)頭:header 內(nèi)容:content/container
頁(yè)面主體:main 頁(yè)尾:footer 導(dǎo)航:nav
側(cè)欄:sidebar 欄目:column 左右中:left right center
頁(yè)面外圍控制整體布局寬度:wrapper
(2)導(dǎo)航
導(dǎo)航:nav
主導(dǎo)航:mainbav
子導(dǎo)航:subnav
頂導(dǎo)航:topnav
邊導(dǎo)航:sidebar
左導(dǎo)航:leftsidebar
右導(dǎo)航:rightsidebar
菜單:menu 子菜單:submenu 標(biāo)題: title 摘要: summary
(3)功能
標(biāo)志:logo
廣告:banner
登陸:login
登錄條:loginbar
注冊(cè):regsiter
搜索:search
功能區(qū):shop
標(biāo)題:title
加入:joinus
狀態(tài):status
按鈕:btn
滾動(dòng):scroll
標(biāo)簽頁(yè):tab
文章列表:list
提示信息:msg
當(dāng)前的: current
小技巧:tips
圖標(biāo): icon
注釋:note
指南:guild
服務(wù):service
熱點(diǎn):hot
新聞:news
下載:download
投票:vote
合作伙伴:partner
友情鏈接:link
版權(quán):copyright
(四)class的命名:
(1)顏色:使用顏色的名稱或者16進(jìn)制代碼,如
.red { color: red; }
.f60 { color: #f60; }
.ff8600 { color: #ff8600; }
(2)字體大小,直接使用"font+字體大小"作為名稱,如
.font12px { font-size: 12px; }
.font9pt {font-size: 9pt; }
(3)對(duì)齊樣式,使用對(duì)齊目標(biāo)的英文名稱,如
.left { float:left; }
.bottom { float:bottom; }
(4)標(biāo)題欄樣式,使用"類別+功能"的方式命名,如
.barnews { }
.barproduct { }
注意事項(xiàng):
1.一律小寫;
2.盡量用英文;
3.不加中杠和下劃線; (我倒是經(jīng)常加)
4.盡量不縮寫,除非一看就明白的單詞. (偷懶經(jīng)常縮寫)
主要的 master.css 模塊 module.css 基本共用 base.css
主題 themes.css 專欄 columns.css 打印 print.css
文字 font.css 表單 forms.css 補(bǔ)丁 mend.css
布局,版面 layout.css
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。