前言
React Native App(后稱(chēng)RN App)的UI由JS端的View tree構(gòu)成沸手,在App運(yùn)行時(shí)會(huì)創(chuàng)建相應(yīng)的原生View tree。從結(jié)果看草姻,這和安卓原生開(kāi)發(fā)時(shí)用xml布局文件是一樣的饶碘,最終結(jié)果都是由Java對(duì)象構(gòu)成的View tree。View tree中每個(gè)節(jié)點(diǎn)必須擁有正確的位置和尺寸數(shù)據(jù)刨摩,才能渲染出正確的界面。安卓原生App渲染流程(測(cè)量世吨,布局澡刹,繪制)中前兩步剛好在做這個(gè)工作,那么RN App里渲染流程是怎么樣的耘婚?RN采用的是Flexbox布局(實(shí)現(xiàn)體稱(chēng)為Yoga)罢浇,這種布局方式如何應(yīng)用到原生渲染流程中?本文先簡(jiǎn)單介紹安卓平臺(tái)原生渲染流程沐祷,然后在此基礎(chǔ)上著重分析RN在安卓平臺(tái)的渲染流程嚷闭。本文將從以下幾個(gè)方面進(jìn)行闡述:
- 安卓原生平臺(tái)的渲染流程概述
- RN渲染流程介紹
2.1 根據(jù)js端view tree,創(chuàng)建平臺(tái)原生view tree流程概述
2.2 使用Yoga的計(jì)算結(jié)果赖临,來(lái)跑原生渲染流程 - Yoga布局和原生布局共存胞锰,安卓自定義View也可以正常工作
- 總結(jié)
本文的主要讀者是有安卓基礎(chǔ)同學(xué),在有安卓知識(shí)的前提下閱讀會(huì)更容易一些兢榨,比如其中的MessageQueue切換嗅榕,F(xiàn)rameLayout等控件,原生渲染流程等內(nèi)容將不會(huì)有理解負(fù)擔(dān)吵聪,能直接過(guò)渡到RN部分的閱讀凌那。其他讀者可能就需要先建立安卓中這些概念的理解。閱讀完本文后暖璧,你將會(huì)理解RN在安卓平臺(tái)的渲染流程實(shí)現(xiàn)原理案怯,對(duì)RN App在渲染時(shí)都執(zhí)行了哪些邏輯有一個(gè)具體的概念,理解我們開(kāi)發(fā)時(shí)寫(xiě)的MRN代碼究竟都做了什么澎办,知其然也知其所以然嘲碱。在遇到問(wèn)題時(shí)也可從流程上進(jìn)行分析定位金砍,或者對(duì)這個(gè)流程的某一步進(jìn)行修改來(lái)滿(mǎn)足定制化需求。文中難免有紕漏之處麦锯,歡迎大家不吝指出恕稠,共同學(xué)習(xí)成長(zhǎng)。
參考源碼版本:RN:0.59.8扶欣,安卓:28鹅巍。
1、安卓原生App UI渲染流程簡(jiǎn)介
GUI程序都用View tree來(lái)描述界面內(nèi)容料祠,不管界面多復(fù)雜骆捧,元素有多少,都可以收納在這顆樹(shù)里髓绽。View tree很好的提供了渲染所需的必要信息:位置(含尺寸)和顏色敛苇。簡(jiǎn)單來(lái)說(shuō),有了這倆信息顺呕,系統(tǒng)就可以生成圖形庫(kù)(OpenGL ES或者Skia)所需的繪制指令枫攀,繪制出由View tree所表達(dá)的一幀畫(huà)面。雖然我們?cè)趯?xiě)View時(shí)可以用{ flex: 1 }(react)或者android:layout_width="match_parent"等來(lái)指定組件的尺寸株茶,但最終尺寸屬性還是需要被計(jì)算成具體的數(shù)值来涨。
在App啟動(dòng)過(guò)程的onResume階段,系統(tǒng)會(huì)觸發(fā)ViewRootImpl.performTraversals開(kāi)始渲染流程启盛,這個(gè)函數(shù)里會(huì)依次觸發(fā)root view的測(cè)量蹦掐、布局、繪制驰徊,最后通知系統(tǒng)渲染到物理屏幕上笤闯,這個(gè)過(guò)程如下圖所示堕阔。測(cè)量遍歷用于計(jì)算每個(gè)節(jié)點(diǎn)的尺寸棍厂,布局遍歷時(shí)會(huì)參考尺寸進(jìn)行位置擺放,算出位置數(shù)據(jù)超陆。經(jīng)過(guò)這兩步以后牺弹,每個(gè)節(jié)點(diǎn)就擁有了位置和尺寸,接著就可以遍歷繪制每個(gè)節(jié)點(diǎn)的內(nèi)容了时呀。view tree中父子節(jié)點(diǎn)的遍歷銜接主要得益于measure/onMeasure张漂,layout/onLayout,draw/onDraw的設(shè)計(jì)谨娜,如下圖中measure過(guò)程所示航攒,layout和draw同理。
以上就是安卓原生渲染的三個(gè)主要步驟趴梢,測(cè)量和布局其實(shí)是為繪制服務(wù)漠畜,前兩步所計(jì)算出的位置和尺寸數(shù)據(jù)在繪制時(shí)需要用到币他,用于生成圖形庫(kù)的繪制指令。這樣說(shuō)來(lái)憔狞,如果view的位置和尺寸數(shù)據(jù)已經(jīng)準(zhǔn)備好蝴悉,測(cè)量和布局這兩步就可以省去了。這也正是RN在原生渲染流程中的切入點(diǎn):把view中onMeasure和onLayout的計(jì)算邏輯都去掉瘾敢,同時(shí)阻斷這兩步遍歷拍冠,通過(guò)Yoga來(lái)計(jì)算位置和尺寸,并將這些數(shù)據(jù)親自“交給”view tree中的每個(gè)節(jié)點(diǎn)簇抵,然后只需要一次繪制遍歷庆杜,就完成了整個(gè)渲染流程。下面來(lái)看看RN是如何完成這個(gè)工作的碟摆。
2欣福、RN渲染流程介紹
我們用js寫(xiě)成的view tree勢(shì)必要翻譯成安卓平臺(tái)的原生view tree,才可以在安卓上正常工作焦履。這個(gè)翻譯過(guò)程并不只是簡(jiǎn)單的映射而已拓劝,RN并不是將映射后的原生view tree直接交給系統(tǒng),它還接管了測(cè)量和布局工作嘉裤。安卓有自己的布局方式郑临,體現(xiàn)在渲染流程的measure和layout中,RN采用的Flexbox是一種完全不同的布局方式屑宠,它如何參與到原生渲染流程中呢厢洞?事實(shí)上不管哪種布局方式,他們都是為了一個(gè)目的:給出view節(jié)點(diǎn)的邊界數(shù)據(jù)bounds(left典奉,top躺翻,right,bottom)卫玖,有了邊界位置公你,view的尺寸也就有了,比如width = right - left假瞬,因此我們可以猜測(cè)兩種布局方式的結(jié)合點(diǎn)就是View的邊界數(shù)據(jù)bounds陕靠,在下面介紹的渲染流程中進(jìn)行驗(yàn)證。RN在mqt_native線(xiàn)程中執(zhí)行Flexbox布局計(jì)算脱茉,計(jì)算結(jié)果將直接用于渲染流程剪芥,省去了主線(xiàn)程中measure和layou的計(jì)算量。如果這兩個(gè)線(xiàn)程是運(yùn)行的不同的核心上琴许,在執(zhí)行復(fù)雜的布局動(dòng)畫(huà)時(shí)將會(huì)有明顯的優(yōu)勢(shì)税肪。下面分小節(jié)來(lái)具體看看這個(gè)過(guò)程是如何進(jìn)展的。
2.1、根據(jù)js端view tree益兄,創(chuàng)建平臺(tái)原生view tree流程概述
RN提供的View系列組件签财,都有相應(yīng)的原生端組件實(shí)現(xiàn),js端的view tree相當(dāng)于一個(gè)“劇本”偏塞,用來(lái)描述UI界面唱蒸,RN會(huì)根據(jù)這個(gè)“劇本”來(lái)生成平臺(tái)原生View tree。對(duì)于js端View tree中的每一個(gè)節(jié)點(diǎn)灸叼,都會(huì)在native端生成一個(gè)ReactShadowNode節(jié)點(diǎn)作為對(duì)應(yīng)神汹,同時(shí)還會(huì)創(chuàng)建一個(gè)原生View節(jié)點(diǎn)(先不考慮RN的布局優(yōu)化,可認(rèn)為他們是一一對(duì)應(yīng)關(guān)系)古今。ReactShadowNode承擔(dān)了Yoga布局的計(jì)算工作屁魏,其內(nèi)部會(huì)創(chuàng)建一個(gè)YogaNode節(jié)點(diǎn),YogaNode內(nèi)部再創(chuàng)建c++端的YGNode捉腥。YogaNode本身是一個(gè)jni承載類(lèi)氓拼,代表的是c++端的YGNode,兩者都表示Yoga布局中的一個(gè)節(jié)點(diǎn)抵碟,當(dāng)Yoga引擎計(jì)算完畢后桃漾,YGNode中就填充滿(mǎn)了尺寸和位置數(shù)據(jù),通過(guò)jni回設(shè)到j(luò)ava端的YogaNode中拟逮,留著給原生view使用撬统。最終在native端會(huì)生成4顆tree:ReactShadowNode tree, YogaNode tree, YGNode tree, 原生View tree,如下圖所示敦迄。
RN App會(huì)在Activity的onCreate階段創(chuàng)建ReactRootView(其本質(zhì)是一個(gè)FrameLayout)恋追,并由此進(jìn)入RN的世界。下面我們來(lái)看看RN是如何創(chuàng)建native端的4顆tree結(jié)構(gòu)的罚屋。在RN Bridge完成初始化后苦囱,native端通過(guò)runApplication()觸發(fā)執(zhí)行我們的js業(yè)務(wù)代碼,根據(jù)js端view tree會(huì)執(zhí)行一系列的UIManager.createView, UIManager.setChildren, UIManager.manageChildren等函數(shù)脾猛。通過(guò)RN Bridge撕彤,這些函數(shù)會(huì)在nativeQueue中添加一系列的操作,用于創(chuàng)建ReactShadowNode節(jié)點(diǎn)尖滚,并把js端view的各屬性值保存在節(jié)點(diǎn)中喉刘,最后形成tree結(jié)構(gòu)。根據(jù)之前的介紹漆弄,此時(shí)也會(huì)同步生成YoagNode tree和YGNode tree。這個(gè)過(guò)程如下圖中左側(cè)兩個(gè)queue所示造锅。
從上圖還可以看出撼唾,每一次對(duì)ReactShadowNode的操作同時(shí)還會(huì)有相應(yīng)的原生View操作,以runnable的形式添加到batchUIQueue中(注意它并不是安卓中和線(xiàn)程相關(guān)的queue哥蔚,僅僅是個(gè)隊(duì)列容器)倒谷。batchUIQueue的名字非常恰當(dāng)?shù)拿枋隽怂淖饔茫罕4嬉幌盗械脑鶹iew操作蛛蒙,最后在App主線(xiàn)程中一次性批處理完。所以當(dāng)ReactShadowNode tree形成時(shí)渤愁,所有對(duì)應(yīng)的原生View操作(view創(chuàng)建牵祟,view屬性賦值,addView形成tree等)都添加到了batchUIQueue中抖格,不過(guò)此時(shí)還不到執(zhí)行的時(shí)機(jī)诺苹。
js端view節(jié)點(diǎn)的屬性可以大致分為兩類(lèi),一類(lèi)用于直接作用于原生view雹拄,比如背景顏色收奔,透明度等等和布局不相關(guān)的;另一類(lèi)主要是flexbox布局相關(guān)的屬性滓玖,比如flex坪哄,margin,padding势篡,alignItems等等翩肌,這些會(huì)通過(guò)ReactShadowNode保存在YogaNode和YGNode中,用于Yoga引擎計(jì)算禁悠。
到目前為止ReactShadowNode tree已經(jīng)形成摧阅,原生view的操作也已經(jīng)添加到batchUIQueue中等待被執(zhí)行。那什么時(shí)候會(huì)執(zhí)行呢绷蹲?答案是尺寸和位置數(shù)據(jù)計(jì)算出來(lái)以后棒卷。在本次js代碼執(zhí)行完之后,jsQueue里會(huì)根據(jù)是否是endOfBatch來(lái)執(zhí)行onBatchComplete祝钢,它會(huì)觸發(fā)在nativeQueue中執(zhí)行onBatchComplete比规,其中會(huì)調(diào)用dispatchViewUpdates,它的工作主要分為3步:
- 啟動(dòng)Yoga引擎對(duì)YGNode tree進(jìn)行計(jì)算拦英,履行Flexbox布局協(xié)議蜒什,并將計(jì)算后的結(jié)果遞歸回設(shè)到j(luò)ava端YogaNode節(jié)點(diǎn)
- 根據(jù)YogaNode tree中的數(shù)據(jù),遞歸向batchUIQueue中添加runnable:給對(duì)應(yīng)的原生View tree節(jié)點(diǎn)設(shè)置尺寸和位置(下文還會(huì)詳細(xì)分析)
- 將batchUIQueue中所有的runnable疤估,交給App主線(xiàn)程執(zhí)行灾常,所有的這些runnable都在主線(xiàn)程的一個(gè)事件循環(huán)中執(zhí)行完
至此,batchUIQueue的所有view操作在App主線(xiàn)程執(zhí)行完铃拇,UI界面也該顯示出來(lái)了钞瀑。其中關(guān)鍵代碼如下:
// ----- file: UIImplementation.java
public void dispatchViewUpdates(int batchId) {
...
updateViewHierarchy();
...
mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime); // 3、執(zhí)行batchUIQueue中所有的view操作
}
protected void updateViewHierarchy() {
...
calculateRootLayout(cssRoot); // 1慷荔、調(diào)用YogaNode.calculateLayout啟用yoga引擎雕什,并將計(jì)算結(jié)果遞歸回設(shè)到j(luò)ava端的YogaNode tree的每個(gè)節(jié)點(diǎn)里
...
applyUpdatesRecursive(cssRoot, 0f, 0f); // 2、將YogaNode tree節(jié)點(diǎn)的數(shù)據(jù),遞歸設(shè)置給原生view贷岸,將該操作添加到batchUIQueue中壹士,最后統(tǒng)一執(zhí)行
}
protected void applyUpdatesRecursive(...) {
...
for (int i = 0; i < cssNode.getChildCount(); i++) {
applyUpdatesRecursive(...); // 遞歸
}
...
cssNode.dispatchUpdates(...) --> uiViewOperationQueue.enqueueUpdateLayout(...) // updateLayout具體做什么?看下面小節(jié)的分析偿警。
}
// ----- file: YogaNode.java
public void calculateLayout(float width, float height) {
jni_YGNodeCalculateLayout(mNativePointer, width, height);
}
// ----- file: YGJNI.cpp
void jni_YGNodeCalculateLayout(alias_ref<jclass>, jlong nativePointer, jfloat width, jfloat height) {
const YGNodeRef root = _jlong2YGNodeRef(nativePointer);
YGNodeCalculateLayout( // Yoga引擎執(zhí)行計(jì)算躏救,里面是漫長(zhǎng)的c++代碼,F(xiàn)lexbox布局協(xié)議的實(shí)現(xiàn)
root,
static_cast<float>(width),
static_cast<float>(height),
YGNodeStyleGetDirection(_jlong2YGNodeRef(nativePointer)));
YGTransferLayoutOutputsRecursive(root); // 將計(jì)算結(jié)果螟蒸,以遞歸方式回設(shè)給java端每個(gè)YogaNode節(jié)點(diǎn)
}
// ----- UIViewOperationQueue.java
public void dispatchViewUpdates(final int batchId, final long commitStartTime, final long layoutTime) {
...
UiThreadUtil.runOnUiThread( // 切換到主線(xiàn)程處理原生View
new GuardedRunnable(mReactApplicationContext) {
@Override
public void runGuarded() {
flushPendingBatches();
}
}
);
}
private void flushPendingBatches() {
...
for (Runnable runnable : runnables) {
runnable.run();
}
}
2.2盒使、使用Yoga的計(jì)算結(jié)果,來(lái)跑原生渲染流程
上文提到在Yoga計(jì)算完畢后尿庐,會(huì)遞歸將YogaNode tree中的數(shù)據(jù)設(shè)置給原生View tree忠怖,簡(jiǎn)單的“設(shè)置”二字實(shí)在是太過(guò)敷衍,本小節(jié)來(lái)詳細(xì)看看這個(gè)過(guò)程抄瑟。首先看下關(guān)鍵代碼:
// ----- file: UIImplementation.java
protected void applyUpdatesRecursive(...) {
...
for (int i = 0; i < cssNode.getChildCount(); i++) {
applyUpdatesRecursive(...); // 遞歸調(diào)用
}
...
cssNode.dispatchUpdates(...); // 這種遞歸方式產(chǎn)生的效果是從葉子節(jié)點(diǎn)開(kāi)始凡泣,到根節(jié)點(diǎn)的遍歷
}
// ----- file: ReactShadowNodeImpl.java
public boolean dispatchUpdates(...) {
...
uiViewOperationQueue.enqueueUpdateLayout( // 添加到batchUIQueue中
getParent().getReactTag(),
getReactTag(),
getScreenX(), // 這些數(shù)據(jù)都是Yoga計(jì)算出來(lái)的
getScreenY(),
getScreenWidth(),
getScreenHeight());
}
// ----- file: NativeViewHierarchyManager.java
public synchronized void updateLayout(...) {
...
viewToUpdate.measure( // 看這里
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
...
updateLayout(...) --> viewToUpdate.layout(x, y, x + width, y + height); // 再看這里,Yoga輸出給View的皮假,就是簡(jiǎn)單的left, top, right, bottom
}
從上面的代碼中可以看到鞋拟,從YogaNode tree的根節(jié)點(diǎn)開(kāi)始遞歸處理,但卻是反向地將原生View tree對(duì)應(yīng)節(jié)點(diǎn)的layout操作添加到batchUIQueue中惹资,即先處理的是葉子結(jié)點(diǎn)贺纲,最后到根節(jié)點(diǎn)。個(gè)人分析這里的遍歷順序沒(méi)有什么分別褪测,top-down或者down-top結(jié)果是一樣的猴誊,畢竟各節(jié)點(diǎn)的尺寸和位置已經(jīng)算好,至于是先設(shè)置子節(jié)點(diǎn)還是父節(jié)點(diǎn)并無(wú)區(qū)別侮措,最終都是在batchUIQueue中一次性執(zhí)行完懈叹,再進(jìn)行reqeustLayout,進(jìn)而執(zhí)行performTraversals完成渲染分扎,此處若分析的不對(duì)還請(qǐng)大神指正澄成。這里的重點(diǎn)是RN親力親為的調(diào)用了原生View tree里所有節(jié)點(diǎn)的measure和layout(僅限和YogaNode tree對(duì)應(yīng)的節(jié)點(diǎn),自定義view group里子節(jié)點(diǎn)不在此范圍)畏吓,來(lái)完成渲染流程的前兩個(gè)階段墨状,將Yoga的計(jì)算結(jié)果應(yīng)用進(jìn)去。上文有提到過(guò)菲饼,容器節(jié)點(diǎn)onMeasure/onLayout會(huì)調(diào)用子節(jié)點(diǎn)的measure/layout肾砂,來(lái)銜接tree結(jié)構(gòu)父子節(jié)點(diǎn)的遍歷,這里是否和RN的遍歷重復(fù)了呢巴粪?答案當(dāng)然是沒(méi)有重復(fù)通今。RN既然選擇親自遍歷所有節(jié)點(diǎn)粥谬,當(dāng)然就會(huì)有處理:onMeasure和onLayout中的計(jì)算邏輯都去掉肛根,并且不調(diào)用子節(jié)點(diǎn)的measure和layout辫塌。代碼節(jié)選如下:
// ----- file: ReactRootView.java RN原生端的根view,用于承載RN所有的界面
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
setMeasuredDimension(width, height); // 沒(méi)有調(diào)用子節(jié)點(diǎn)的measure派哲,僅履行了安卓的約定調(diào)用setMeasuredDimension臼氨。
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// No-op since UIManagerModule handles actually laying out children.
// 這里更簡(jiǎn)單,什么都不做芭届,上面的注釋說(shuō)的很明白了储矩。
}
// ----- file: ReactViewGroup.java 這個(gè)類(lèi)表示的是js端的View.js,最基本的容器view褂乍。測(cè)量和布局均沒(méi)有任何計(jì)算持隧,沒(méi)有觸發(fā)子節(jié)點(diǎn)。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// No-op since UIManagerModule handles actually laying out children.
}
3逃片、Yoga布局和原生布局共存屡拨,安卓自定義View也可以正常工作
RN雖然是自己負(fù)責(zé)銜接安卓渲染流程的前兩步,但是接入自定view group并不需要額外做太多褥实,只需跟安卓自定義view group一樣重寫(xiě)onMeasure和onLayout呀狼,負(fù)責(zé)計(jì)算子節(jié)點(diǎn)的尺寸和位置。如下圖所示损离,自定義節(jié)點(diǎn)和RN的節(jié)點(diǎn)能夠完美合作哥艇,“各司其職”。
不過(guò)事情總是會(huì)有一些遺憾僻澎,當(dāng)自定義view group進(jìn)行requestLayout時(shí)會(huì)觸發(fā)view tree的渲染流程遍歷貌踏,但是RN的view容器并沒(méi)有在onMeasure/onLayout里銜接遍歷,導(dǎo)致自定義view group界面更新不生效窟勃。解決方式比較簡(jiǎn)單祖乳,在自定義view group里重寫(xiě)requestLayout,然后手動(dòng)調(diào)用measure和layout拳恋,這樣自定義view就可以正常工作了凡资。
// ----- 某自定義view group.java
@Override
public void requestLayout() {
super.requestLayout();
post(measureAndLayout);
}
private final Runnable measureAndLayout = new Runnable() {
@Override
public void run() {
measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
};
下面來(lái)看一個(gè)筆者之前在實(shí)際項(xiàng)目中遇到的例子,這個(gè)例子是電子答題谬运,主要功能是展示一張題目圖片隙赁,學(xué)生可以在下面手寫(xiě)作答,當(dāng)答題區(qū)域不夠時(shí)可以增加區(qū)域尺寸等等梆暖。該功能實(shí)現(xiàn)采用的是FrameLayout+自定義ImageView伞访,如下示意圖所示,自定義ImageView用于設(shè)置題目圖片和實(shí)現(xiàn)畫(huà)筆功能轰驳,F(xiàn)rameLayout用于移動(dòng)和縮放ImageView等厚掷。初始化時(shí)ImageView和FrameLayout尺寸一樣弟灼,當(dāng)用戶(hù)點(diǎn)擊增加一屏畫(huà)布時(shí),在FrameLayout里增加ImageView的高度冒黑。這其中沒(méi)有重寫(xiě)onMeasure和onLayout田绑,F(xiàn)rameLayout和ImageView默認(rèn)的就夠用。
功能在安卓原生側(cè)是正常的抡爹,接入到RN以后展示正常掩驱,寫(xiě)寫(xiě)畫(huà)畫(huà)正常,對(duì)畫(huà)布進(jìn)行放大縮卸埂(scale)正常欧穴,但是點(diǎn)擊增加一屏操作卻無(wú)效”门梗總結(jié)一下上述這些現(xiàn)象會(huì)發(fā)現(xiàn):
- onDraw里的操作執(zhí)行正常涮帘,比如畫(huà)筆操作,setScale和setTranslationX等
- setLayoutParams操作無(wú)效笑诅,如設(shè)置ImageView高度
也就是說(shuō)尺寸更新無(wú)效调缨,繪制操作有效,原因就是當(dāng)在View里進(jìn)行invalidate和setLayoutParams時(shí)苟鸯,都會(huì)執(zhí)行requestLayout向上反饋到ViewRootImpl來(lái)觸發(fā)一次渲染流程同蜻,其中RN阻斷了measure和layout的遍歷,沒(méi)有阻斷draw早处。解決方式就是在FrameLayout.requestLayout中主動(dòng)觸發(fā)measure和layout湾蔓,完成子View的尺寸刷新。另外砌梆,由于FrameLayout是作為自定義View接入到RN的默责,他的尺寸將會(huì)受RN來(lái)控制,原生側(cè)無(wú)需關(guān)心咸包。
4桃序、總結(jié)
以上就是RN在安卓端UI渲染流程的介紹,主要描述了我們?cè)趈s端寫(xiě)的view tree烂瘫,是如何一步一步轉(zhuǎn)化到原生view的媒熊,這也體現(xiàn)了RN確實(shí)就是native的一面。筆者一直以來(lái)就有一個(gè)疑惑坟比,安卓原生View本身有大量的屬性芦鳍,比如width,height葛账,padding柠衅,margin等等,RN所采用的Flexbox也有很多的屬性籍琳,flex菲宴,padding贷祈,alignItems等等,兩者的差距還是很大的喝峦,這兩套屬性該如何對(duì)接势誊?乍一想還是很頭疼的,大量的屬性剪不斷理還亂愈犹,但從RN源碼來(lái)看键科,其實(shí)兩者的銜接點(diǎn)只是view的邊界值而已(left, top, right, bottom)闻丑。分析一下可以發(fā)現(xiàn)漩怎,不管是Flexbox還是安卓原生布局,其他屬性存在的價(jià)值就是為了計(jì)算出left嗦嗡,top勋锤,right,bottom侥祭。RN參與渲染流程的切入點(diǎn)也正是這里叁执,剛好原生view就有一個(gè)layout(l, t, r, b)方法來(lái)接收這四個(gè)值,并且也剛好這個(gè)方法就是渲染流程中的一步矮冬,一切都恰到好處谈宛。另外,RN在一個(gè)子線(xiàn)程中來(lái)計(jì)算View的布局?jǐn)?shù)據(jù)胎署,省去了主線(xiàn)程刷新UI時(shí)的前兩步遍歷吆录,減輕了負(fù)擔(dān),但從整體上來(lái)看UI的連貫性未必就有提升琼牧,線(xiàn)程之間的配合成本可能也不低恢筝。筆者也是在學(xué)習(xí)過(guò)程中,想法難免有欠缺或錯(cuò)誤之處巨坊,歡迎各位大神提出寶貴建議撬槽。