深刻Weex系列(六)之Weex渲染流程分析

一、前言

在前兩篇文章中咱們結合源碼學習了Module、Component的註冊、調用、回調等流程,相信你們必定收穫頗多,對Weex的理解也必定越發深刻。git

那麼本篇文章咱們分析Weex的渲染流程,來看一看咱們寫的Js文件是如何在Native端變成Android裏View的。github

二、Weex渲染過程

2.1 渲染觸發點

渲染觸發點

在Activity中咱們開個某個Weex頁面使用的是WXSDKInstance中的render方法,最終也是按照常規套路經過WXBridge調用Js繼續處理。bash

備註:Js引擎處理後回調Native這一部分要複雜的多,咱們拆分紅幾步來看。微信

2.2 渲染準備

Weex渲染準備
備註:這是渲染準備階段,實際上和上一篇分析Component的調用準備是同樣的;都是加一個任務保存到mNormalTasks。

2.2 渲染流程分析

Weex渲染流程分析

從上咱們能夠看出真實的渲染過程由WXDomHandler發起,關鍵方法在DOMActionContextImpl和WXComponent中(標出關鍵方法的地方);也經歷了測量、佈局、繪製、處理事件、設置數據等流程。app

渲染流程很是重要,裏面有不少關鍵步驟,下面咱們一一分析;dom

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

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分析:ui

  • addEvents()添加View的事件處理;

2.6 bindData

bindData邏輯

bindData:

  • 更新Style、綁定數據等;
  • 具體都會執行到updateProperties()方法中,其中實現是MethodInvoker反射調用方法;

三、渲染流程圖

總結:

  • Weex渲染流程由Native發起,經過JsBridge傳給V8引擎,處理後回傳指令到Native;
  • Dom相關的操做使用WXDomHandler切換到Dom線程操做;
  • layoutNodeImpl是核心測量過程解析FlexBox佈局,計算Dom的位置信息並存儲;
  • 接下來WXRenderHandler將後續工做線程切換到RenderThread也就是UI線程;
  • 由Component建立具體的View;
  • setLayout其實是將位置信息轉換爲原生View識別的params;
  • addEvents添加事件;
  • bindData設置style及賦值;

四、對比

下面咱們對Weex的渲染和Android的渲染流程進行一下對比

  • 對於Android原生的渲染須要通過Measure、Layout、Draw等步驟;
  • 對於Weex的來講,Android原生的渲染流程是全有的並且只是一部分,由於咱們雖然寫的是Js代碼可是實際顯示的確是Native控件;
  • 那麼Weex比原生多的流程就是:與V8的交互、關於Dom的解析與生成、設置屬性與賦值(擴展)等;

五、總結

  • Weex渲染流程的分析難度比Module、Component等組件難度要大的多,畢竟Module等只是一個組件而這是一個完整的流程;
  • Weex渲染流程的分析依賴於Module、Component等組件的實現,這也是我首先分析這兩個組件的緣由;
  • 在Weex渲染流程的分析中咱們第一次接觸到了Weex中的線程切換,以後會細說;
  • 建議你們都實際跟蹤下Weex的源碼,裏面有不少能夠學習的細節;

歡迎持續關注Weex源碼分析項目:Weex-Analysis-Project

歡迎關注微信公衆號:按期分享Java、Android乾貨!

歡迎關注
相關文章
相關標籤/搜索