背景介紹
Litho 是 FaceBook 2017年上半年開源的聲明式UI渲染框架。
為什么 Facebook 要開發(fā) Litho 喂饥?
APP中最常見的UI表現(xiàn)形式就是各種內(nèi)容豐富的Feed流置尔,比如各種新聞流杠步,圖片流。對于這些滾動列表榜轿,如何能夠保證流暢地滑動幽歼?
為了保證頁面滾動流暢,需要程序在1秒內(nèi)渲染60幀的數(shù)據(jù)才能保證頁面不卡頓差导,也就是在每16ms內(nèi)把需要顯示的幀畫在屏幕上试躏,否則就可能引起掉幀猪勇。
Android 系統(tǒng)是如何解決這個問題的设褐?
Android 系統(tǒng)中提供的滾動列表就是我們常見的 RecyclerView , RecyclerView 用于動態(tài)顯示大量數(shù)據(jù)集的列表,僅僅在屏幕中顯示有限的幾個條目泣刹,對于滑出屏幕的條目進行復用助析。
當某個條目第一次被顯示在屏幕上時,RecyclerVeiw 會讓 Adapter 新填充一個 View 顯示在屏幕上椅您。當用戶開始滾動列表的時候外冀,RecyclerView 會檢查是否有 Item 滾出屏幕,滾出屏幕的 View 會被放在pool中備用掀泳。當隨后需要再顯示這個 View 的時候雪隧,再從 pool 中取出來,讓 Adapter 用新數(shù)據(jù)對這個 View 顯示的數(shù)據(jù)進行更新员舵。
RecyclerView 在一幀內(nèi)都做了什么脑沿?
當需要在屏幕上顯示一個新的條目時,如果 pool 中沒有马僻,就新填充一個 View庄拇,然后更新 View 的數(shù)據(jù),之后是 measure韭邓,layout 措近, draw,所有這些都需要在 16ms 內(nèi)完成女淑。
那么問題來了:
- 如果列表中 Item 的類型很多瞭郑,該怎么處理?
- 如果單個 Item 比較復雜鸭你,既有圖片屈张,又有視頻我抠,還有各種文字鏈接的情況?
對于多種數(shù)據(jù)類型袜茧,RecylerView 中采用 ViewType菜拓,每一種ViewType 會分配一個 pool 。當 ViewType 大量增加笛厦,滾動列表的時候纳鼎,每一幀的數(shù)據(jù)里就可能更多地涉及到 View 的填充,View 的分配操作是個重操作裳凸,比較耗費資源贱鄙,也就是說 ViewType越多,就越有可能發(fā)生掉幀姨谷。另外在內(nèi)存上逗宁,那些使用頻率低的 ViewType 對應的 pool ,會一直存在于內(nèi)存中梦湘,內(nèi)存使用效率變低瞎颗。
如果 Item 本身比較復雜,想要提升性能捌议,就要對 itemview 進行優(yōu)化哼拔,比如靜態(tài)的 layout 優(yōu)化,減少布局層數(shù)瓣颅,或者 自定義layout 倦逐,但同時又帶來難以維護的問題。
Litho 是如何解決上述問題的宫补?
1. Async Layout 異步布局
采用 Yoga 這個布局引擎 把 measure 和 layout 放到后臺線程檬姥。在ReactNative中也是用的Yoga,Yoga 是一種脫離于Android 系統(tǒng)的高效布局引擎粉怕,實現(xiàn)了 FlexBox 布局規(guī)范健民。
異步布局的意思就是提前在后臺線程渲染畫面,要在16ms內(nèi)進行measure斋荞,layout 荞雏,draw 三個必須又耗時的操作,Android 傳統(tǒng)的做法是把所有這些操作放在 MainThread 平酿,Litho 沒有這個限制凤优,Litho 使用 Yoga 渲染組件,把 measure 和 layout 放在后臺線程蜈彼,在新一幀到來之前提前在后臺線程中計算出組件的大小和位置筑辨,除了 Yoga 之外,如何保證線程安全幸逆,Litho 這里采用了不可變的聲明式 API 棍辕,使組件自然而然地保持了線程安全暮现,使得后臺渲染變得可行。
除此之外楚昭,現(xiàn)在我們已經(jīng)節(jié)省了主線程的時間栖袋,還可以進一步進行優(yōu)化。如果一幀有時間空余的話抚太,可以提前 draw 下一幀的內(nèi)容塘幅。這個是用 DisplayList 實現(xiàn)的, DisplayList 在Android系統(tǒng)中用來保存 openGL commands ,提前創(chuàng)建和編譯 Displaylists尿贫,需要顯示這個組件的時候电媳,只需要執(zhí)行里面的命令就可,不需要再進行編譯庆亡。(DisplayList is a group of openGL commands(已被保存匾乓,編譯) for later execution)
2. View Flattering 鋪平View層次
好處:既減少了內(nèi)存使用,也減少了GC的頻率又谋。
在不改變顯示內(nèi)容的基礎上拼缝,鋪平 View 結構可以減少 View 的個數(shù)
a. 容器處理的優(yōu)化:
Android 里面有很多容器,如果下圖中的 Row 和 Column 不需要畫背景的話搂根,真正需要顯示在屏幕上的組件只有一個圖片加上兩個文本,那么就可以把 Row 和 Column 這兩個容器刪掉珍促。Litho 會去檢測容器是否影響組件的繪制,如果不影響就刪掉剩愧。由于使用了 Yoga,因為 Litho 允許 layout 過程中不使用 Android Views娇斩,可以提前得知在繪制組件的時候是否需要容器仁卷。
b. View層面處理的優(yōu)化:
如果 Row 和 Column 這兩個容器消失了,那對于這個圖片和兩個文本要如何處理犬第?
標準的Android系統(tǒng)锦积,圖片使用 ImageView,文本使用 TextView
Litho 中的實現(xiàn)使用 Drawables歉嗓,輕量級丰介,不會有 View 的內(nèi)存和性能負擔,使用 Drawables 讓Litho進一步減少 View 的層數(shù)鉴分。
3. Incremental Recycling 增量復用
Android ReyclerView 的復用是基于 ViewType 的哮幢,假設有上百個ViewType,各種文章文本視頻圖片組合在一起志珍,每個 Type 分配一個Pool橙垢,那將會有上百個 pool ,會對內(nèi)存造成負擔伦糯。
RecylerView 多類型復用
Litho中的細粒度復用
每個Item都是由一些核心組件構成的柜某,文本嗽元,圖片,視頻等喂击,只是不同的 Item 只是對這些核心組件進行不同的組合剂癌。不把每個 Item 的內(nèi)容看成一個整體,而是把 Item 拆分成各個核心組件翰绊。
當一個 Item 滾出屏幕珍手,就把這個 Item 分解成各個核心組件,每種核心組件放到一個 Pool 辞做,當顯示一個新的 Item 的時候琳要,再把這些組件進行重新組合。
更進一步的優(yōu)化:
因為我們不把一個 Item 當成一個整體秤茅,所以當這個 Item 滾出屏幕的時候稚补,也不用等整個 Item 滾出屏幕的時候再回收,只需要當每一個核心組件滾出屏幕就可以進行回收框喳。
下面這個例子课幕,當上面 Item 中的一部分移出屏幕,頭像和標題部分已經(jīng)被回收了五垮,不需要整個 Item 滾出屏幕乍惊,這些組件可以更快地回收和復用,以減少核心組件的數(shù)量放仗。