1、前言
在前兩篇文章中我們結合原始碼學習了Module、Component的註冊、呼叫、回撥等流程,相信大家一定收穫頗多,對Weex的理解也一定愈加深入。
那麼本篇文章我們分析Weex的渲染流程,來看一看我們寫的Js檔案是如何在Native端變成Android裡View的。
2、Weex渲染過程
2.1 渲染觸發點
在Activity中我們開個某個Weex頁面使用的是WXSDKInstance中的render方法,最終也是按照常規套路通過WXBridge呼叫Js繼續處理。
備註:Js引擎處理後回撥Native這一部分要複雜的多,我們拆分成幾步來看。
2.2 渲染準備
備註:這是渲染準備階段,實際上和上一篇分析Component的呼叫準備是一樣的;都是加一個任務儲存到mNormalTasks。2.2 渲染流程分析
從上我們可以看出真實的渲染過程由WXDomHandler發起,關鍵方法在DOMActionContextImpl和WXComponent中(標出關鍵方法的地方);也經歷了測量、佈局、繪製、處理事件、設定資料等流程。
渲染流程非常重要,裡面有很多關鍵步驟,下面我們一一分析;
2.3 calculateLayout
calculateLayout分析:
- DOMActionContextImpl.calculateLayout()開始,執行到LayoutEngine.layoutNodeImpl(),這步是真實的解析、儲存Js中儲存的佈局;layoutNodeImpl()方法長達720行,解析FlexBox佈局,並且儲存結果到CSSLayout中。
2.4 createView
/**
* create view
*/
public final void createView() {
mHost = initComponentHostView(mContext);
if (mHost == null && !isVirtualComponent()) {
//compatible
initView();
}
if(mHost != null){
mHost.setId(WXViewUtils.generateViewId());
ComponentObserver observer;
if ((observer = getInstance().getComponentObserver()) != null) {
observer.onViewCreated(this, mHost);
}
}
onHostViewInitialized(mHost);
}
複製程式碼
createView分析:
- 建立目標Component物件,跟進去可以看到initComponentHostView方法,就是我們自定義Component必須過載的方法;
2.5 setLayout
/**
* layout view
*/
public final void setLayout(ImmutableDomObject domObject) {
......
Spacing parentPadding = (nullParent?new Spacing():mParent.getDomObject().getPadding());
Spacing parentBorder = (nullParent?new Spacing():mParent.getDomObject().getBorder());
Spacing margin = mDomObj.getMargin();
int realWidth = (int) mDomObj.getLayoutWidth();
int realHeight = (int) mDomObj.getLayoutHeight();
int realLeft = (int) (mDomObj.getLayoutX() - parentPadding.get(Spacing.LEFT) -
parentBorder.get(Spacing.LEFT));
int realTop = (int) (mDomObj.getLayoutY() - parentPadding.get(Spacing.TOP) -
parentBorder.get(Spacing.TOP)) + siblingOffset;
int realRight = (int) margin.get(Spacing.RIGHT);
int realBottom = (int) margin.get(Spacing.BOTTOM);
......
mAbsoluteY = (int) (nullParent?0:mParent.getAbsoluteY() + mDomObj.getLayoutY());
mAbsoluteX = (int) (nullParent?0:mParent.getAbsoluteX() + mDomObj.getLayoutX());
......
setComponentLayoutParams(realWidth, realHeight, realLeft, realTop, realRight, realBottom, rawOffset);
......
}
複製程式碼
setLayout分析:
- setLayout()獲取真實的寬高及四個頂點的位置,類比原生Android中的Measure與Layout過程;
- setComponentLayoutParams()中轉換原生識別的LayoutParams,並且會呼叫setLayoutParams(),我們知道這個方法會呼叫走原生View的Measure、Layout、Draw等流程;
2.5 addEvents
public void addEvent(String type) {
......
View view = getRealView();
if (type.equals(Constants.Event.CLICK) && view != null) {
addClickListener(mClickEventListener);
} else if ((type.equals(Constants.Event.FOCUS) || type.equals(Constants.Event.BLUR))) {
addFocusChangeListener(new WXComponent.OnFocusChangeListener() {
public void onFocusChange(boolean hasFocus) {
Map<String, Object> params = new HashMap<>();
params.put("timeStamp", System.currentTimeMillis());
fireEvent(hasFocus ? Constants.Event.FOCUS : Constants.Event.BLUR, params);
}
});
} else if (view != null &&
needGestureDetector(type)) {
if (view instanceof WXGestureObservable) {
if (mGesture == null) {
mGesture = new WXGesture(this, mContext);
boolean isPreventMove = WXUtils.getBoolean(getDomObject().getAttrs().get(Constants.Name.PREVENT_MOVE_EVENT), false);
mGesture.setPreventMoveEvent(isPreventMove);
}
mGestureType.add(type);
((WXGestureObservable) view).registerGestureListener(mGesture);
} else {
WXLogUtils.e(view.getClass().getSimpleName() + " don't implement " +
"WXGestureObservable, so no gesture is supported.");
}
} else {
Scrollable scroller = getParentScroller();
if (type.equals(Constants.Event.APPEAR) && scroller != null) {
scroller.bindAppearEvent(this);
}
if (type.equals(Constants.Event.DISAPPEAR) && scroller != null) {
scroller.bindDisappearEvent(this);
}
}
}
複製程式碼
addEvents分析:
- addEvents()新增View的事件處理;
2.6 bindData
bindData:
- 更新Style、繫結資料等;
- 具體都會執行到updateProperties()方法中,其中實現是MethodInvoker反射呼叫方法;
3、渲染流程圖
總結:
- Weex渲染流程由Native發起,通過JsBridge傳給V8引擎,處理後回傳指令到Native;
- Dom相關的操作使用WXDomHandler切換到Dom執行緒操作;
- layoutNodeImpl是核心測量過程解析FlexBox佈局,計算Dom的位置資訊並儲存;
- 接下來WXRenderHandler將後續工作執行緒切換到RenderThread也就是UI執行緒;
- 由Component建立具體的View;
- setLayout實際上是將位置資訊轉換為原生View識別的params;
- addEvents新增事件;
- bindData設定style及賦值;
4、對比
下面我們對Weex的渲染和Android的渲染流程進行一下對比:
- 對於Android原生的渲染需要經過Measure、Layout、Draw等步驟;
- 對於Weex的來說,Android原生的渲染流程是全有的而且只是一部分,因為我們雖然寫的是Js程式碼但是實際顯示的確是Native控制元件;
- 那麼Weex比原生多的流程就是:與V8的互動、關於Dom的解析與生成、設定屬性與賦值(擴充套件)等;
5、總結
- Weex渲染流程的分析難度比Module、Component等元件難度要大的多,畢竟Module等只是一個元件而這是一個完整的流程;
- Weex渲染流程的分析依賴於Module、Component等元件的實現,這也是我首先分析這兩個元件的原因;
- 在Weex渲染流程的分析中我們第一次接觸到了Weex中的執行緒切換,之後會細說;
- 建議大家都實際跟蹤下Weex的原始碼,裡面有很多可以學習的細節;
歡迎持續關注Weex原始碼分析專案:Weex-Analysis-Project
歡迎關注微信公眾號:定期分享Java、Android乾貨!