Feed流上的優(yōu)化實踐

之前一直負責(zé)小紅書的關(guān)注Feed的迭代工作,因為一直是在完成新功能的迭代工作,對于Feed的性能和消費體驗就沒有特別關(guān)注,加上對于這塊業(yè)務(wù)的一些監(jiān)控也沒有落地,所以長期對于這塊的性能基本上就是一個忽視的狀態(tài)。隨著業(yè)務(wù)越來越復(fù)雜,功能越來越多,收到好多反饋都是說關(guān)注頁面的滑動體驗很不好,于是決定對關(guān)注Feed做一個性能優(yōu)化。

這里先從優(yōu)化說起,之前在網(wǎng)上看到一個關(guān)于優(yōu)化的分類,覺得很有道理。

請看下面這張圖:


optimization_category.png

關(guān)于優(yōu)化

優(yōu)化整體可以分為兩大類,軟優(yōu)化和硬優(yōu)化。那么什么是軟優(yōu)化呢?軟優(yōu)化并不會改變某段代碼的執(zhí)行效率,而是通過調(diào)整這段代碼的執(zhí)行時機,來優(yōu)化整體代碼的運行性能。軟優(yōu)化里由分為三大類,分別為預(yù)加載,異步加載和懶加載。

軟優(yōu)化

  • 預(yù)加載
    這幾種加載方式都很好理解,改變的都是執(zhí)行時機,預(yù)加載就是提前加載,把一些重要的數(shù)據(jù)提前加載好,等到合適的時機,就可以直接展示給用戶,而不是在這個合適的時機才去加載數(shù)據(jù),這種方式給用戶地感覺是加載變快了。
  • 異步加載
    而異步加載在Android中則很常見了,移動應(yīng)用中有主線程的概念,主線程需要實時地對用戶的操作予以反饋,這種反饋要非常及時,否則就會讓用戶覺得卡頓。因此,主線程的工作不宜有耗時操作,而且對于一些執(zhí)行比較慢的代碼,而且不需要同步執(zhí)行的代碼,我們可以考慮將它放到異步線程去執(zhí)行。
  • 懶加載
    最后就是懶加載了,懶加載就是把一些不是需要立即展示的東西往后放,因為我們的手機性能,內(nèi)存是有限的,有些功能用戶不是需要馬上看到,則可以考慮在后續(xù)的某個適當(dāng)?shù)臅r機執(zhí)行加載。

硬優(yōu)化

接下來說說硬優(yōu)化,硬優(yōu)化這個東西就比較偏細節(jié)了。一段邏輯,我們用兩層循環(huán)實現(xiàn),功能肯定是OK的,但是如果在實現(xiàn)功能的情況下,我們用一層循環(huán)就可以解決了,這樣不就提高了這段代碼的運行效率了。這種方式的優(yōu)化就是硬優(yōu)化了,關(guān)于硬優(yōu)化往往沒有具體的方法,很多都是各種見仁見智,通過調(diào)整代碼的邏輯來提高運行效率都可以稱為硬優(yōu)化。

Feed流上的問題發(fā)現(xiàn)和解決

很多人會說,上面說了這么多軟優(yōu)化和硬優(yōu)化,有什么用呢?別急,我們慢慢來說。上面說的算是一種對于優(yōu)化的指導(dǎo)思想吧,下次我們遇到類似的問題可以從這幾個維度來進行分析。

我們要做優(yōu)化,首先肯定是要發(fā)現(xiàn)問題,也就是對癥下藥。

我之前主要處理的是Feed流的卡頓優(yōu)化,所以我基本上都是通過Android Studio的Profiler工具來查看問題,現(xiàn)在Profiler的體驗相對之前功能算是更加強大了,我每次都是使用Profiler來嘗試抓一下Feed流滑動時的代碼執(zhí)行情況。
然后一個一個查看滑動過程中我們的耗時是在哪些地方。

通過Profiler發(fā)現(xiàn)的問題

通過Profiler發(fā)現(xiàn)了以下幾點問題:

  1. 發(fā)現(xiàn)有一些比較耗時的方法,比如圖文筆記上的濾鏡的回收方法比較耗時,針對這樣的可以考慮做這個release操作放到異步里面去;
  2. 另外也發(fā)現(xiàn)一些業(yè)務(wù)的打點會在OnBindViewHolder中調(diào)用的很頻繁,雖然點位數(shù)據(jù)的發(fā)送是在異步線程里,但是在發(fā)送數(shù)據(jù)之前會有數(shù)據(jù)的拼裝操作,多少會有些性能損耗,而且打點這一類數(shù)據(jù)完全可以放到異步線程中去,因為這種打點并不需要很高的實時性;
  3. 對于一些跨進程的操作,比如從Context中拿Service獲取WiFi狀態(tài),這種在onBind中回調(diào)是非常不好的,所以針對這個我們只需要構(gòu)造一個廣播來監(jiān)聽WIFI狀態(tài)變化即可,然后每次只需要拿那個記錄好的網(wǎng)絡(luò)狀態(tài)變量即可
  4. 還有關(guān)于視頻筆記的處理,我們需要根據(jù)RecyclerView滑動過程中當(dāng)前item中的視頻start,stop和prepare做相應(yīng)的調(diào)整,正常情況下,當(dāng)RecyclerView處于滑動狀態(tài)是不應(yīng)該去prepare和start的,而且也不應(yīng)該創(chuàng)建播放器實例和Texture View的;
  5. 最后,通過Profiler我們還發(fā)現(xiàn)TextView的setText是很耗時的,當(dāng)我們復(fù)雜多行文本的情況時,TextView去setText的時候需要進行復(fù)雜的測量,在測量完之后還需要繪制,所以針對這個場景可以考慮使用StaticLayout對Feed流中的文本作預(yù)渲染,關(guān)于StaticLayout的教程網(wǎng)上也有不少,后面有時間我也會抽空寫下關(guān)于這塊的實踐。

上面都是通過Profiler來發(fā)現(xiàn)的一些問題。

View層級

另外還有一些算是基礎(chǔ)的優(yōu)化吧。因為View的層級隨著Feed流的業(yè)務(wù)變得越來越深,我們需要進可能地減少View的層級,同時也盡量減少View的個數(shù),可以考慮使用ConstraintLayout,但是ConstraintLayout這個東西要慎用,對于一些迭代比較快的業(yè)務(wù)場景,使用ConstraintLayout會增加我們的維護成本,而且會有一些比較坑的問題。

關(guān)于View層級的縮減基本沒啥可講的,盡量用merge和ViewStub,這個就是根據(jù)實際情況去消除一些不必要的層級。

RecyclerView緩存的利用

最后,我們也嘗試了從RecyclerView緩存的角度來解決這個問題,RecyclerView默認會有四級緩存的概念,正常情況下,當(dāng)我們的RecyclerView的卡片撐滿屏幕的時候,我們進入該頁面的時候,我們的RecyclerView會先創(chuàng)建ViewHolder,然后再bind這個ViewHolder,然后當(dāng)我們向下滑動的時候,會再次創(chuàng)建一個ViewHolder,接著再執(zhí)行Bind操作,只有當(dāng)我們的RecycledView Pool的池子滿了之后才不會繼續(xù)創(chuàng)建ViewHolder,往后的滑動RecyclerViwe就只會執(zhí)行bind操作了,這個時候我們發(fā)現(xiàn)這個OnCreateViewHolder需要從xml里去解析view,并且創(chuàng)建這個ViewHolder,這個創(chuàng)建對于我們的滑動是會有性能損耗,其實還是因為我們的這個卡片太復(fù)雜了。

所以,我們可以考慮提前創(chuàng)建一些ViewHolder,放到ViewHolder的池子里去,這樣我們在滑動的時候,就不需要先Create再Bind了。

提前創(chuàng)建ViewHolder,在主線程Idle的狀態(tài)下提前創(chuàng)建ViewHolder放到RecycledViewPool中,這個pool的size默認是5。

 recyclerView.setRecycledViewPool(RecyclerView.RecycledViewPool().apply {
     LightExecutor.postIdle(Runnable {
         repeat(5) {
             //RecycledViewPool 默認size為5個,RecyclerView內(nèi)部會有判斷,如果滿了不會create
             //注意這里取得是typePool的倒數(shù)第一個和第二個位置,需要保證注冊的時候視頻卡片和筆記在最后兩個位置
             putRecycledView(mAdapter.createViewHolder(followFeedRecyclerView, mAdapter.typePool.size -1))
             putRecycledView(mAdapter.createViewHolder(followFeedRecyclerView, mAdapter.typePool.size -2))
         }
     })
 })

結(jié)果

在做完這些優(yōu)化之后,在Feed流上的卡頓就有很大改善了,另外,對于業(yè)務(wù)側(cè)的指標(biāo)也有一定的數(shù)據(jù)提升。

后續(xù)

對于這塊業(yè)務(wù)的性能監(jiān)控可以實施起來,這樣有利于我們盡早發(fā)現(xiàn)卡頓問題,另外,對于一些優(yōu)化的點其實還可以繼續(xù)深挖,比如前創(chuàng)建ViewHolder是否可以使用AsyncLayoutInflator進行inflate,還有針對文本的預(yù)渲染可以考慮抽成組件形式,方便其他業(yè)務(wù)使用。

感謝您的閱讀,如果覺得我的文章對你有幫助,請幫忙點贊,有問題可以評論留言。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。