整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          WP主題開發(fā)14:怎樣添加wordpress主題trans的文章列表頁(yè)模板?

          前面的章節(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)化后

          數(shù)據(jù)建立

          性能

          當(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è)步驟

          1. 解析 HTML 文件
          2. 加載 JavaScript 和 CSS 文件
          3. 解析并執(zhí)行 JavaScript
          4. 構(gòu)建 DOM 結(jié)構(gòu)
          5. 加載圖片等資源
          6. 頁(yè)面加載完畢

          而 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ā)生了白屏。

          指標(biāo)建立

          確定好口徑之后,我們還有需要明確的一個(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ōu)化

          模板拆分

          如前所述,圖文詳情頁(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ò)的影響。

          本地加載

          模板預(yù)熱

          完全脫離了網(wǎng)絡(luò)加載之后,還能再快一點(diǎn)呢?當(dāng)然還是可以的!

          當(dāng)全流程離線化之后,頁(yè)面加載的瓶頸就變成了本地模板的加載時(shí)間,所以我們接下來(lái)要做的就是優(yōu)化模板加載時(shí)間。

          對(duì)于模板來(lái)說,我們做了兩件事情

          1. 模板合并,正常來(lái)說,WebView 需要在加載玩主 HTML 之后再去加載 HTML 中的 JS 和 CSS,需要多次 IO 操作,于是我們將 JS 和 CSS 還有一些圖片都內(nèi)聯(lián)到一個(gè)文件中,這樣,加載模板時(shí)就只需要一次 IO 操作,也大大減少因?yàn)?IO 加載沖突導(dǎo)致模板加載失敗問題
          2. 模板簡(jiǎn)化,我們將部分非必須的腳本異步化拉取,精簡(jiǎn)不必要的樣式和 JS 代碼,將模板大小壓縮了 20% 以上

          通過上面優(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ù)

          模板復(fù)用

          當(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ù)熱模板命中率

          網(wǎng)絡(luò)優(yōu)化

          說完我們?cè)谀0?WebView 方面的優(yōu)化之后,再介紹一下我們?cè)趦?nèi)容請(qǐng)求上的優(yōu)化。

          CDN 加速

          由于頭條詳情頁(yè)請(qǐng)求有以下特點(diǎn)

          1. 流量大,之前說過,看新聞作為用戶在頭條的核心場(chǎng)景,每天都有上億用戶在使用頭條,詳情頁(yè)的數(shù)據(jù)流量十分大。
          2. 數(shù)據(jù)屬性基本不變,在詳情頁(yè)的請(qǐng)求中,很多熱點(diǎn)文章是重復(fù)渲染計(jì)算的,正文、標(biāo)題、作者信息、圖片控制以及一些樣式和業(yè)務(wù)邏輯渲染是基本不變的,這部分重復(fù)計(jì)算耗費(fèi)了帶寬、服務(wù)器資源,是比較沒有必要的。
          3. 用戶分布廣,網(wǎng)絡(luò)狀況難以保證,頭條的用戶量很大,覆蓋了各種運(yùn)營(yíng)商網(wǎng)絡(luò)和網(wǎng)絡(luò)狀態(tài),網(wǎng)絡(luò)質(zhì)量無(wú)法得到很好的保證。而 CDN 能夠?qū)?shù)據(jù)緩存在各地的邊緣節(jié)點(diǎn),用戶就近接入了邊緣節(jié)點(diǎn),避免在網(wǎng)絡(luò)質(zhì)量無(wú)法保證的公網(wǎng)上長(zhǎng)時(shí)間傳輸,從而提高了響應(yīng)速度和響應(yīng)的成功率。
          4. 接口數(shù)據(jù)大,由于正文數(shù)據(jù)的存在,接口返回的數(shù)據(jù)常常會(huì)很大,如果每一次都實(shí)時(shí)返回,對(duì)網(wǎng)絡(luò)的壓力會(huì)比較大,可能會(huì)把帶寬打滿而影響其他服務(wù)

          所以我們將詳情頁(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é)省了帶寬成本。

          容災(zāi)

          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í)策略

          • 單次請(qǐng)求 CDN 的超時(shí)時(shí)間,根據(jù)上次成功請(qǐng)求 CDN 的值計(jì)算,因子 1.5(z 值)。且最小為 1s(x 值),最大為 4s(y 值)。超過這一時(shí)間不取消,直接請(qǐng)求下個(gè) CDN。
          • 單次請(qǐng)求 CDN 有一個(gè)硬性超時(shí)時(shí)間 4s(w 值,w 需>=y),超過這一時(shí)間請(qǐng)求取消。n 個(gè) CDN 的請(qǐng)求全部取消后反饋用戶失敗。

          幾個(gè) case:

          • 第 1 個(gè) CDN 突然掛掉(假設(shè)上次成功請(qǐng)求的耗時(shí)為 a) 下一次請(qǐng)求:第一個(gè) CDN 很快超時(shí)(a _ 1.5);開始請(qǐng)求第二個(gè) CDN(超時(shí)時(shí)間為 a _ 1.5,但實(shí)際上 b 秒就會(huì)返回請(qǐng)求)。用戶本次等待時(shí)間為 a _ 1.5 + b 下兩次請(qǐng)求:第一個(gè) CDN 很快超時(shí)(b _ 1.5);開始請(qǐng)求第二個(gè) CDN(超時(shí)時(shí)間為 b _ 1.5,但實(shí)際 c 秒就會(huì)返回請(qǐng)求)。用戶本次等待時(shí)間為 b _ 1.5 + c

          • 用戶突然進(jìn)入了一個(gè)網(wǎng)絡(luò)很差的環(huán)境(假設(shè)上次成功請(qǐng)求的耗時(shí)為 a) 下一次請(qǐng)求:第一個(gè) CDN 很快超時(shí)(a _ 1.5);開始請(qǐng)求第二個(gè) CDN(a _ 1.5)也超時(shí);開始請(qǐng)求第三個(gè) CDN(a _ 1.5)。最后一個(gè)請(qǐng)求會(huì)在 a _ 3 + w 后返回失敗(這個(gè)值會(huì)在 12s 以內(nèi))。

          可以看到,通過多域名備份和快速超時(shí)的策略,即使用戶在網(wǎng)絡(luò)或者服務(wù)異常的情況下,也能快速恢復(fù)或者讓用戶能感知到自身網(wǎng)絡(luò)問題。

          渲染優(yōu)化

          當(dāng)我們?cè)谀0鍖雍途W(wǎng)絡(luò)層優(yōu)化到極致的時(shí)候,限制我們的就是 WebView 的渲染速度了!

          服務(wù)端預(yù)渲染

          正常來(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ì)存在以下問題:

          1. 相比于文字內(nèi)容,非文字內(nèi)容比如說圖片和視頻類資源的渲染對(duì)于 WebView 來(lái)說渲染效率比較差
          2. 在詳情頁(yè)中文章有大量圖片的場(chǎng)景,對(duì)于 WebView 的渲染內(nèi)存占用和滑動(dòng)體驗(yàn)也有問題
          3. 最后,如果用戶多次打開同一篇文章,這篇文章中的圖片也會(huì)存在多次加載的問題,無(wú)法與客戶端進(jìn)行緩存共享,對(duì)用戶的流量也是一種浪費(fèi)。

          所以在詳情頁(yè)中,我們會(huì)將圖片和視頻等非文字內(nèi)容通過原生組件的方式放在客戶端進(jìn)行渲染,既可以提高渲染效率,也可以減少不必要的流量消耗。

          原生化渲染還有一個(gè)好處,圖片越來(lái)越成為文章體驗(yàn)的重要部分,對(duì)于多圖文章,我們?cè)?Feed 頁(yè)面也可以智能加載詳情頁(yè)需要的圖片,增加用戶的文章首屏體驗(yàn)。

          白屏優(yōu)化

          講完了性能優(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ì)遇到一些奇奇怪怪的坑。

          1. 多線程讀模板文件問題,WebView 在運(yùn)行中會(huì)讀取的文件模板,如果此時(shí)另外一個(gè)線程同時(shí)更新模板文件時(shí),就出現(xiàn)了模板加載問題,所以需要保證模板加載的原子性
          2. Render 卡死問題,內(nèi)核是一個(gè)比較復(fù)雜的邏輯,內(nèi)部渲染極少數(shù)情況也會(huì)出現(xiàn) Render 卡死問題,但是在詳情頁(yè)整體用戶的量級(jí)下,即使只有十萬(wàn)分之一的可能,對(duì)用戶來(lái)說也是一個(gè)比較大的問題,此時(shí)我們會(huì)從業(yè)務(wù)上做白屏監(jiān)控進(jìn)行重試

          當(dāng)然不管是 iOS 和 Android, WebView 加載的邏輯都比較復(fù)雜,有時(shí)候怎么重試也無(wú)法成功,這個(gè)時(shí)候我們會(huì)直接降級(jí)到加載線上的詳情頁(yè),優(yōu)先保證用戶的體驗(yàn)。

          總結(jié)

          限于篇幅原因,我們還做了很多其他事情,包括請(qǐng)求精簡(jiǎn),push 文章預(yù)拉取,數(shù)據(jù)注入的方式優(yōu)化等等,也做了很多其他的方向的探索,這里不做展開,希望有機(jī)會(huì)能再分享給大家。

          最后總結(jié)一下我們?cè)趦?yōu)化詳情頁(yè)打開速度之后的一些想法

          • 數(shù)據(jù)很重要,我們?cè)趦?yōu)化加載速度之前做的第一件事情其實(shí)是建立了一個(gè)詳情頁(yè)的數(shù)據(jù)看板,只有通過數(shù)據(jù)我們才能真正了解目前線上用戶的現(xiàn)狀,從真實(shí)用戶的體驗(yàn)中找到瓶頸和優(yōu)化點(diǎn)。
          • 用戶體驗(yàn)優(yōu)先,優(yōu)化方案有很多,除了加載速度之外,還需要從整體應(yīng)用體驗(yàn)出發(fā),選擇對(duì)用戶最佳的方案
          • 追求極致,其實(shí)最開始的優(yōu)化是比較簡(jiǎn)單的,但是越到后面越難,需要一點(diǎn)點(diǎn)摳細(xì)節(jié),才能達(dá)到極致的用戶體驗(yàn)

          更多分享

          Android Camera 內(nèi)存問題剖析

          字節(jié)跳動(dòng)自研線上引流回放系統(tǒng)的架構(gòu)演進(jìn)

          iOS大解密:玄之又玄的KVO

          Android '秒' 級(jí)編譯速度優(yōu)化

          技術(shù)團(tuán)隊(duì)

          技術(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


          主站蜘蛛池模板: 精品国产免费观看一区| 一区二区三区精密机械| 日韩高清国产一区在线| 波多野结衣高清一区二区三区 | 亚洲精品精华液一区二区| 内射少妇一区27P| 日韩a无吗一区二区三区| 日韩精品无码一区二区三区免费| 免费高清av一区二区三区| 久久99热狠狠色精品一区 | 精品国产香蕉伊思人在线在线亚洲一区二区| 中文字幕一区二区三区乱码| 国产人妖视频一区二区破除| 国产福利一区二区三区视频在线 | 日本激情一区二区三区| 国产精品丝袜一区二区三区 | 国产免费一区二区三区| 91成人爽a毛片一区二区| 亚洲国产福利精品一区二区| 无码人妻精品一区二区三18禁| 日韩免费一区二区三区| 亚洲一区二区在线免费观看| 中文字幕一区日韩在线视频| 日韩精品一区二区三区老鸦窝| 亚洲视频一区二区| 爆乳无码AV一区二区三区 | 国产亚洲一区二区三区在线观看 | 国产在线视频一区二区三区98| 中文字幕一区二区人妻| 香蕉久久一区二区不卡无毒影院| 99久久精品国产高清一区二区| 国产熟女一区二区三区四区五区| 一夲道无码人妻精品一区二区| 色偷偷久久一区二区三区| 免费无码一区二区三区| 国产精品视频免费一区二区三区| 亚洲午夜精品第一区二区8050| 亚洲一区二区三区AV无码| 亚洲AV无码一区二区三区在线| 国产高清在线精品一区小说| 精品国产一区二区三区香蕉|