深刻Weex系列(五)之Component組件源碼解析

一、前言

在上一篇文章《深刻Weex系列(四)之Module組件源碼解析》中咱們結合源碼學習了Module的註冊、調用、回調等流程,而且分析一個Weex自帶Module的實現。html

那麼本篇文章咱們開始分析Weex的另外一個重要組件Component,關於Component的註冊、調用等分析本文爲你娓娓道來。android

二、初始Component

2.1 Component的定位

《Android 擴展》中咱們能夠看到Component的定位:git

Component 擴展 實現特別功能的Native控件。例如:RichTextview,RefreshListview 等。github

你們知道Android的四大組件中用戶惟一有感知的就是Activity,而在Weex的這些組件中用戶惟一有感知的也就是Component。實際上它就是Weex裏的Widget,好比咱們在WeexList中開發的Js代碼中寫的那些控件,最終在Native都是一個個的Component。apache

2.2 Component的使用

2.2.1 內置Component

對於普通的界面開發,咱們通常不會見到Component的蹤影,由於Weex已經提供了一套基礎的Component組件與基礎Html標籤的對應,例如基礎Component組件的註冊:bash

registerComponent(
        new SimpleComponentHolder(
            WXImage.class,
            new WXImage.Ceator()
        ),false,WXBasicComponentType.IMAGE,WXBasicComponentType.IMG
    );
    registerComponent(WXBasicComponentType.CELL, WXCell.class, true);
    registerComponent(WXBasicComponentType.INDICATOR, WXIndicator.class, true);
    registerComponent(WXBasicComponentType.VIDEO, WXVideo.class, false);
    registerComponent(WXBasicComponentType.INPUT, WXInput.class, false);
    registerComponent(WXBasicComponentType.TEXTAREA, Textarea.class,false);
    registerComponent(WXBasicComponentType.SWITCH, WXSwitch.class, false);
複製代碼
2.2.2 自定義Component

項目中總有些效果是內置的Component沒法實現的,**此時咱們就要像自定義控件同樣自定義Component,具體實例能夠參考WeexList中的CircleImageView與RefreshView兩個自定義Component。**下面主要說下注意事項:微信

  • 自定義Component須要提供的方法上加上註解WXComponentProp,並加上name,做爲Js端調用的方法名;
@WXComponentProp(name = "setSrc")
    public void setImage(String url) {
        
    }
複製代碼
  • Weex初始化的時候註冊這個Component;
WXSDKEngine.registerComponent("circleImageView", CircleImageView.class);
複製代碼
  • Js端的使用;
<circleImageView :setSrc="item.bphoto" style="width:100;height:100"></circleImageView>
複製代碼

注意這個 :setSrc 就是上面註解上的name;weex

三、Component源碼分析

3.1 Component註冊

Component註冊時序圖分析

Component的註冊和Module很像,你們從時序圖上能夠看出有幾個很熟悉的類;Component一樣分爲本地註冊與Js註冊;ide

  • 本地註冊源碼分析

    • 一樣由WXSDKEngine發起,須要注意IFComponentHolder中的loadIfNonLazy,會判斷Component是否是lazy(爲提高初始化效率),不是lazy的話則直接解析這個Component,獲取其Method信息保存;是Lazy的話則在第一次使用的時候解析、保存;能夠類比Module是否是global;
    • 實際上仍是保存了本地Component與Js端的一個對應;
  • Js註冊

    • Js註冊更和Module的Js註冊沒有區別,一樣最終也是經過WXBridge執行的與Js交互;

3.2 Component調用

以自定義Component的方法調用爲例,Component的調用相對比較複雜,咱們拆分紅兩步來看,調用準備和調用執行;

Component調用準備時序圖

調用準備說明:

  • 照例Js的調用由WXBridge開始;
  • 將Js調用的任務封裝成一個DOMAction;
  • 而後由WXDomHandler將任務切換到Dom線程也就是主線程執行;
  • 接下來的步驟就是在DOMActionContextImpl的任務註冊了;
@Override
    public void postRenderTask(RenderAction action) {
        mNormalTasks.add(new RenderActionTask(action, mWXRenderManager.getRenderContext(mInstanceId)));
        mDirty = true;
    }
複製代碼

Component調用執行時序圖

調用執行說明:

  • 執行由WXDomHandler發起,觸發繪製;
  • 而後到了DOMActionContextImpl中的layout方法————》其中有一個consumeRenderTasks方法,從名字咱們就能夠看出是消費RenderTask的(備註:此處對應了保存任務的mNormalTasks);
  • 而後RenderActionTask調用executeRender方法,開始觸發執行;
  • 最終是在WXComponent中包裝成MethodInvoker,反射調用執行;

3.3 Component源碼盤點

經過上述對Component註冊、調用等的源碼分析,咱們能夠看到Component相比較Module仍是比較複雜的。若是你們仔細跟Component源碼的話會發現一個問題:第一步調用準備和第二步調用執行是如何串起來的?

在第一步調用準備結束以後,只是將任務加到了mNormalTasks保存,並無任何執行任務的代碼,那第二步調用執行是如何被調用的? 這個問題也困擾了我若干分鐘。

下面說說我對這塊的探索:Weex的繪製邏輯和Android原生很相似,Android會每隔16毫秒發出一次VSYNC信號觸發對UI進行渲染,而Weex也會每隔16毫秒發出一個消息觸發繪製,具體的邏輯在WXDomHandler中的WX_DOM_BATCH類型消息中;

public class WXDomHandler implements Handler.Callback {
    
  @Override
  public boolean handleMessage(Message msg) {
        if (!mHasBatch) {
            mHasBatch = true;
            mWXDomManager.sendEmptyMessageDelayed(WXDomHandler.MsgType.WX_DOM_BATCH, DELAY_TIME);
        }
        case MsgType.WX_DOM_BATCH:
            mWXDomManager.batch();
            mHasBatch = false;
            break;
  }
}
複製代碼

從mWXDomManager.batch()開始後續的邏輯就串起來了,DOMActionContextImpl中的consumeRenderTasks方法對RenderTask進行消費也就是第一步調用準備Task的執行;

四、內置組件

Weex對經常使用控件都進行了封裝具體在com.taobao.weex.ui.component下能夠找到,一些文檔上沒寫的屬性之類的能夠在源碼中查找,畢竟源碼面前,了無祕密。

下面咱們簡單看一個經常使用的控件:列表控件,Weex裏內置了WXListComponent來支持列表控件;

@Component(lazyload = false)
public class WXListComponent extends BasicListComponent<BounceRecyclerView> {
    ......
    
    @WXComponentProp(name = Constants.Name.COLUMN_GAP)
    public void setColumnGap(float columnGap) throws InterruptedException {
        if(mRecyclerDom != null && mRecyclerDom.getColumnGap() != mColumnGap) {
            markComponentUsable();
            updateRecyclerAttr();
            WXRecyclerView wxRecyclerView = getHostView().getInnerView();
            wxRecyclerView.initView(getContext(), mLayoutType, mColumnCount, mColumnGap, getOrientation());
        }
    }
    
    ......
}
複製代碼

能夠看出:

  • WXListComponent基於RecycleView,這樣View的複用是有保障的;
  • lazyload爲false,也就是註冊即解析(重量級組件優先級就是高);
  • 提供了類如setColumnGap來設置列間隔等方法;

對於咱們自定義的Component均可以參照這個思路,具體實例能夠參考WeexList

五、Component總結

  • Component的定位是實現特別功能的Native控件;
  • Component的源碼實現相較於Module更加複雜,尤爲是從調用準備到調用執行這個斷點的連接;
    • 調用準備是包裝成RenderTask,而後加到集合保存;
    • 調用執行是RenderTask的執行過程,最終是方法的反射調用;
    • 兩步之間的斷點連接是由Weex的渲染機制保證的;

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

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

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