weex源码浅析(android部分)一

小菜鸟yjm 小菜鸟yjm     2022-12-02     704

关键词:

最近预研阿里的weex方案,同最近比较火的ReactNative比较类似,提供了一个一次编写三端共用的解决方案.

下面引用官方的描述

Weex 是一套简单易用的跨平台开发方案,能以 web 的开发体验构建高性能、可扩展的 native 应用,为了做到这些,Weex 与 Vue 合作,使用 Vue 作为上层框架,并遵循 W3C 标准实现了统一的 JSEngine 和 DOM API,这样一来,你甚至可以使用其他框架驱动 Weex,打造三端一致的 native 应用。

weex的语法借鉴了vue.js的语法,保留了MVVM的结构.具体语法官方链接:语法介绍

这里直接跳过关于weex的官方控件介绍以及绑定原理,具体的细节和实践步骤准可参考上面的链接.

我们直接来看这张图,这张图很清晰地描述了weex的工作原理.

Weex we 文件 ————–前端(we源码)
↓ (转换) ——————前端(构建过程)
JS Bundle —————–前端(JS Bundle代码)
↓ (部署) ——————服务器
在服务器上的JS bundle —-服务器
↓ (编译) —————— 客户端(JS引擎)
虚拟 DOM 树 ————— 客户端(Weex JS Framework)
↓ (渲染) —————— 客户端(渲染引擎)
Native视图 ————— 客户端(渲染引擎)

  • Transformer(转换器): 一个Node JS工具, 转换Weex源码为JS Bundle
  • Weex JS Framework(JS框架):运行于客户端的的JS框架,管理着Weex实例的运行。Weex实例由JS Bundle创建并构建起虚拟DOM树. 另外,它发送/接受 Native 渲染层产生的系统调用,从而间接的响应用户交互。
  • Native引擎: 在不同的端上,有着不同的实现: iOS/Android/HTML5. 他们有着共同的组件设计, 模块API 和渲染效果. 所以他们能配合同样的 JS Framework 和 JS Bundle工作。

JS Bundle是由.we文件编译而成的js文件,weex js framenwork是基于chrome v8的js引擎,负责把js bundle解析成Vm Dom(json格式描述),作为js和native之间沟通的桥梁,主要的两个方法callJs()callNative().

下面着重讲下android端的渲染过程,以官方的playground 作为demo工程.

weex的使用需要在Application中配置,序列图如下

WXApplication中调用WXSDKEngine初始化

WXSDKEngine.initialize(this,
                           new InitConfig.Builder()
                               //.setImgAdapter(new FrescoImageAdapter())// use fresco adapter
                               .setImgAdapter(new ImageAdapter())
                               .setDebugAdapter(new PlayDebugAdapter())
                               .build()
                          );

WXSDKEngine.initialize()的第二个参数以链式的方式设置一系列自定义的adapter,包括网络请求,图片加载,数据埋点之类,某些adapter,weex提供了基础版本的实现,另外一些默认为空.

initialize紧接着进入 doInitInternal,这里

 private static void doInitInternal(final Application application,final InitConfig config)
    WXEnvironment.sApplication = application;
    WXEnvironment.JsFrameworkInit = false;

    //调用WxBridgeManager post 
    WXBridgeManager.getInstance().post(new Runnable() 
      @Override
      public void run() 
        long start = System.currentTimeMillis();
        WXSDKManager sm = WXSDKManager.getInstance();
        //如果没有自定义Adapter,则初始化为默认实现
        if(config != null ) 
          sm.setIWXHttpAdapter(config.getHttpAdapter());
          sm.setIWXImgLoaderAdapter(config.getImgAdapter());
          sm.setIWXUserTrackAdapter(config.getUtAdapter());
          sm.setIWXDebugAdapter(config.getDebugAdapter());
          sm.setIWXStorageAdapter(config.getStorageAdapter());
          if(config.getDebugAdapter()!=null)
            config.getDebugAdapter().initDebug(application);
          
        
        //加载V8.so库
        WXSoInstallMgrSdk.init(application);
        boolean isSoInitSuccess = WXSoInstallMgrSdk.initSo(V8_SO_NAME, 1, config!=null?config.getUtAdapter():null);
        if (!isSoInitSuccess) 
          return;
        
        //初始化js Framework, weex_sdk下assets/main.js文件
        sm.initScriptsFramework(config!=null?config.getFramework():null);

        WXEnvironment.sSDKInitExecuteTime = System.currentTimeMillis() - start;
        WXLogUtils.renderPerformanceLog("SDKInitExecuteTime", WXEnvironment.sSDKInitExecuteTime);
      
    );
    //注册 componnet, moudle
    register();
  

下面跟一下initScriptsFramework().通过handler post message 到WXBridgeManager中,核心代码如下

framework = WXFileUtils.loadAsset("main.js", WXEnvironment.getApplication());

//调用libs/libweexv8.so中的native方法
if(mWXBridge.initFramework(framework, assembleDefaultOptions())==INIT_FRAMEWORK_OK)
          //省略部分代码
          //init 成功注册DomModule
          registerDomModule();
          commitAlert(IWXUserTrackAdapter.JS_FRAMEWORK,WXErrorCode.WX_SUCCESS);
        

讲到很有必要通过一张图来介绍下weex中几个比较核心的类之间的关系

WXSDKEngine的方法均为static的,WXSDKInstance,WXBridgeManager,WXSDKManager都通过getInstance()获取单例对象.其中的registerComponent(),registerMoudle()等方法均通过WXBridgeManager.getInstance()调用对应的方法.


Application中的初始化工作完成之后,我们直接从IndexActivity的渲染开始

onCreate()中

//index.js为index.we进过编译之后的js bundle
renderPage(WXFileUtils.loadAsset("index.js", this),WEEX_INDEX_URL);

继续跟进

 protected void renderPage(String template,String source,String jsonInitData)
    AssertUtil.throwIfNull(mContainer,new RuntimeException("Can't render page, container is null"));
    Map<String, Object> options = new HashMap<>();
    options.put(WXSDKInstance.BUNDLE_URL, source);
    //mInstace为WXSDKInstance的单例
    mInstance.render(
      getPageName(),
      template,//index.js
      options,
      jsonInitData,
      ScreenUtil.getDisplayWidth(this),
      ScreenUtil.getDisplayHeight(this),
      WXRenderStrategy.APPEND_ASYNC);
  

显然真正的实现在WXSDKInstance中

//生成唯一的一个递增的id,一个id代表一个页面
mInstanceId = WXSDKManager.getInstance().generateInstanceId();
WXSDKManager.getInstance().createInstance(this, template, options, jsonInitData);

跟到WXBridgeManager

private void invokeCreateInstance(String instanceId, String template,
                                    Map<String, Object> options, String data) 

    initFramework("");
    //...省略
      try 
        WXJSObject instanceIdObj = new WXJSObject(WXJSObject.String,
                instanceId);
        WXJSObject instanceObj = new WXJSObject(WXJSObject.String,
                                                template);
        WXJSObject optionsObj = new WXJSObject(WXJSObject.JSON,
                options == null ? ""
                        : WXJsonUtils.fromObjectToJSONString(options));
        WXJSObject dataObj = new WXJSObject(WXJSObject.JSON,
                data == null ? "" : data);
        WXJSObject[] args = instanceIdObj, instanceObj, optionsObj,
                dataObj;
        //通过JNI调用V8中execJs执行index.js
        invokeExecJS(instanceId, null, METHOD_CREATE_INSTANCE, args);
       catch (Throwable e) 
        //...
      
    
  

至此,我们已成功加载index.js到v8引擎中.


weex的js framwork(即assets/main.js)在生成js bundle的虚拟 dom和事件绑定的过程中会在js端调用callNative方法.

/**
   *Dispatch the native task to be executed.
   * @param instanceId @link WXSDKInstance#mInstanceId
   * @param tasks tasks to be executed
   * @param callback next tick id
   */
  public int callNative(String instanceId, String tasks, String callback) 
    //...
    if(mDestroyedInstanceId!=null &&mDestroyedInstanceId.contains(instanceId))
      return IWXBridge.DESTROY_INSTANCE;
    
    //...
    int size = array.size();
    if (size > 0) 
      try 
        JSONObject task;
        for (int i = 0; i < size; ++i) 
          task = (JSONObject) array.get(i);
          if (task != null && WXSDKManager.getInstance().getSDKInstance(instanceId) != null) 
            if (TextUtils.equals(WXDomModule.WXDOM, (String) task.get(MODULE))) 
              //一个instanceId对应一个WXSDKInstace,register时保存在hashMap
              sDomModule = getDomModule(instanceId);
              sDomModule.callDomMethod(task);
              sDomModule.mWXSDKInstance = null;
             else 
              WXModuleManager.callModuleMethod(instanceId, (String) task.get(MODULE),
                      (String) task.get(METHOD), (JSONArray) task.get(ARGS));
            
          
        
       catch (Exception e) 
       //...
    
    //..
  

这里贴上一条log,分别对应上面的三个参数

[WXBridgeManager] callNative >>>> instanceId:1, tasks:["module":"dom","method":"createBody","args":["ref":"_root","type":"list","attr":,"style":]], callback:-1

接着weex开始根据task开始构建原生的组件,跟进WXDomModule的callDomModule()

public void callDomMethod(JSONObject task) 
    if (task == null) 
      return;
    

    String method = (String) task.get(WXBridgeManager.METHOD);
    JSONArray args = (JSONArray) task.get(WXBridgeManager.ARGS);

    if (method == null) 
      return;
    

    try 
      switch (method) 
        case CREATE_BODY:
          if (args == null) 
            return;
          
          createBody((JSONObject) args.get(0));
          break;
        case UPDATE_ATTRS:
          if (args == null) 
            return;
          
          updateAttrs((String) args.get(0), (JSONObject) args.get(1));
          break;
        case UPDATE_STYLE:
          if (args == null) 
            return;
          
          updateStyle((String) args.get(0), (JSONObject) args.get(1));
          break;
        case REMOVE_ELEMENT:
          if (args == null) 
            return;
          
          removeElement((String) args.get(0));
          break;
        case ADD_ELEMENT:
          if (args == null) 
            return;
          
          addElement((String) args.get(0), (JSONObject) args.get(1), (Integer) args.get(2));
          break;
        case MOVE_ELEMENT:
          if (args == null) 
            return;
          
          moveElement((String) args.get(0), (String) args.get(1), (Integer) args.get(2));
          break;
        case ADD_EVENT:
          if (args == null) 
            return;
          
          addEvent((String) args.get(0), (String) args.get(1));
          break;
        case REMOVE_EVENT:
          if (args == null) 
            return;
          
          removeEvent((String) args.get(0), (String) args.get(1));
          break;
        case CREATE_FINISH:
          createFinish();
          break;
        case REFRESH_FINISH:
          refreshFinish();
          break;
        case UPDATE_FINISH:
          updateFinish();
          break;
        case SCROLL_TO_ELEMENT:
          if (args == null) 
            return;
          
          scrollToElement((String) args.get(0), (JSONObject) args.get(1));
          break;
        case ADD_RULE:
          if (args == null) 
            return;
          
          addRule((String) args.get(0), (JSONObject) args.get(1));
      

     catch (IndexOutOfBoundsException e) 
      // no enougn args
      e.printStackTrace();
      WXLogUtils.e("Dom module call miss arguments.");
     catch (ClassCastException cce) 
      WXLogUtils.e("Dom module call arguments format error!!");
    
  

跟到这里,weex的从.we文件渲染成android原生组件的逻辑就很清楚了.
最终的调用在WXDomStatement的createBody方法

void createBody(JSONObject element) 
    //...
    //创建WXDomObject并且遍历保存json中定义的style,attribute,event到WXDomObject
    WXDomObject domObject = parseInner(element);
    if(domObject==null)
      return;
    
    //如果没有定义.初始化这五种样式
    Map<String, Object> style = new HashMap<>(5);
    if (!domObject.getStyles().containsKey(Constants.Name.FLEX_DIRECTION)) 
      style.put(Constants.Name.FLEX_DIRECTION, "column");
    
    if (!domObject.getStyles().containsKey(Constants.Name.BACKGROUND_COLOR)) 
      style.put(Constants.Name.BACKGROUND_COLOR, "#ffffff");
    
    //If there is height or width in JS, then that value will override value here.
    if ( !domObject.getStyles().containsKey(Constants.Name.WIDTH)) 
      style.put(Constants.Name.WIDTH, WXViewUtils.getWebPxByWidth(WXViewUtils.getWeexWidth(mInstanceId)));
      domObject.setModifyWidth(true);
    
    if ( !domObject.getStyles().containsKey(Constants.Name.HEIGHT)) 
      style.put(Constants.Name.HEIGHT, WXViewUtils.getWebPxByWidth(WXViewUtils.getWeexHeight(mInstanceId)));
      domObject.setModifyHeight(true);
    
    WXDomObject.prepareRoot(domObject);
    domObject.updateStyle(style);
    //存储css样式到WXDomObject
    transformStyle(domObject, true);

    try 
      //真正开始由dom替换成native控件,==>1
      final WXComponent component = mWXRenderManager.createBodyOnDomThread(mInstanceId, domObject);
      AddDomInfo addDomInfo = new AddDomInfo();
      addDomInfo.component = component;
      mAddDom.put(domObject.getRef(), addDomInfo);

      mNormalTasks.add(new IWXRenderTask() 

        @Override
        public void execute() 
          WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(mInstanceId);
          if (instance == null || instance.getContext() == null) 
            WXLogUtils.e("instance is null or instance is destroy!");
            return;
          
          try 
            mWXRenderManager.createBody(mInstanceId, component);
           catch (Exception e) 
            WXLogUtils.e("create body failed.", e);
          
        

        @Override
        public String toString() 
          return "createBody";
        
      );
    //...
  

1.

WXComponent createBodyOnDomThread(WXDomObject dom) 
    if (mWXSDKInstance == null) 
      return null;
    
    WXDomObject domObject = new WXDomObject();
    WXDomObject.prepareGod(domObject);
    //初始化根节点,默认为container类型
    mGodComponent = (WXVContainer) WXComponentFactory.newInstance(mWXSDKInstance, domObject, null);
    mGodComponent.createView(null, -1);
    //对应android的根节点是FrameLayout
    FrameLayout frameLayout = (FrameLayout) mGodComponent.getHostView();
    ViewGroup.LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    frameLayout.setLayoutParams(layoutParams);
    frameLayout.setBackgroundColor(Color.TRANSPARENT);
    //根据dom生成对应的native控件
    WXComponent component = generateComponentTree(dom, mGodComponent);
    mGodComponent.addChild(component);
    mRegistry.put(component.getRef(), component);
    return component;
  
private WXComponent generateComponentTree(WXDomObject dom, WXVContainer parent) 
    //这一句话很重要,调用工厂方法获得实例
    WXComponent component = WXComponentFactory.newInstance(mWXSDKInstance, dom,
                                                           parent, parent.isLazy());


    mRegistry.put(dom.getRef(), component);
    //如果是viewgroup,递归
    if (component instanceof WXVContainer) 
      WXVContainer parentC = (WXVContainer) component;
      int count = dom.childCount();
      WXDomObject child = null;
      for (int i = 0; i < count; ++i) 
        child = dom.getChild(i);
        if (child != null) 
          parentC.addChild(generateComponentTree(child, parentC));
        
      
    

    return component;
  

newInstance的核心部分

//从sTypeComponentMap的hashmap中获得对应的控件(registerComponent时会保存到这个hashmap)
IFComponentHolder holder = WXComponentRegistry.getComponent(node.getType());
//反射获取控件实例或者直接调用对应控件的createInstance
return holder.createInstance(instance, node, parent, lazy);

这里以WXImage标签为例,最终转换为android的ImageView

public static class Ceator implements ComponentCreator 
        public WXComponent createInstance(WXSDKInstance instance, WXDomObject node, WXVContainer parent, boolean lazy) throws IllegalAccessException, InvocationTargetException, InstantiationException 
            return new WXImage(instance,node,parent,lazy);
        
    

至此,.we文件从jsBundle到虚拟Dom再到android端的原生实现就贯通了,代码分析部分省略了部分容错判断和跳过部分过渡的调用关系,请结合源码阅读.

感谢阅读完本篇分析,因能力有限,如有错误恳请斧正.

weex之android端的浅析(代码片段)

...手,出于强烈好奇和业务预研的需要,分析了其Android端的WeexSdk一些源码.先从WXSDKManager入手后,画出其结构图如图:IWXUserTrackAdapter:用来处理日志信息接口,常常拿来做一些用户埋点统计.IWXImgLoaderAda... 查看详情

view绘制源码浅析(一)布局的加载(代码片段)

...很难吃透。近期我专门抽了一周多的时间读了绘制相关的源码,这里准备用三篇博客做一个系统的讲述,目录如下。View绘制源码浅析(一)布局的加载View绘制源码浅析(二)布局的测量、布局、绘制View绘制源码浅... 查看详情

android源码精编手册,浅析阅读android源码的术与道

想要在Android领域有所成就,就要在源码上下功夫。首先我们铭记一点:“要深刻领会而非单纯知道;要学习思考而非无脑记诵”。在阅读源码之前,我们要先掌握必备的基础知识点,包括:信息检索能力... 查看详情

android源码精编手册,浅析阅读android源码的术与道

想要在Android领域有所成就,就要在源码上下功夫。首先我们铭记一点:“要深刻领会而非单纯知道;要学习思考而非无脑记诵”。在阅读源码之前,我们要先掌握必备的基础知识点,包括:信息检索能力... 查看详情

浅析android开源框架imageloader的用法

一、前言在Android开发中,会经常涉及到显示图片的相关操作,在网上查阅资料,ImageLoader得到大家广泛的使用,本篇文章针对初使用者的一个向导,同时也是自己使用该框架的一个总结,主要包含:##源码浅析####使用教程####用... 查看详情

reentrantlock获取锁释放锁源码浅析(代码片段)

JUC包下的ReentrantLock是基于Aqs模板实现的,它区分公平锁和非公平锁,内部实现了两个同步器,本文关注非公平锁部分。伪代码我们先看两个伪代码:1、获取锁1if(获取锁成功)2return3else4加入等待队列5for(死循环)6if(获取到锁)7return8... 查看详情

mini-webpack源码浅析(代码片段)

...单分析了一官方文档中提到的一个mini-webpackminipack项目的源码,以此深入了解下什么是打包?以及打包的原理是什么?前置知识首先 查看详情

requests源码框架浅析

本文主要是对requests的forhuman结构的部分进行简单分析,对于里面具体的功能实现(比如cookies如何存储,http相关对接)没有深入研究。1对于requests主要模块说明:1.1__init__.py:写入了requests的各种方法,可以直接调用1.2api.py:定义了... 查看详情

arraylist源码浅析(代码片段)

...的集合,作为极高频次使用的类,我们不妨阅读源码一谈究竟。前言ArrayList作为我们开发中最常用的集合,作为极高频次使用的类,我们不妨阅读源码一谈究竟。介绍ArrayList继承关系如下AaaryList主要实现了List接口... 查看详情

jdbc源码浅析

我们以JDBC的常见的使用示例作为切入口,如下所示:jdbc是属于一种插件式的设计,意思是jdbc定义了一组接口,程序开发者将面向这组接口去使用它jdbc,而接口的具体实现则由数据库服务商来提供。这样,jdbc良好地把实现进行... 查看详情

weex与weexpack环境搭建

weex与weexpackweexinit会生成一个weex项目,里面有weex,vue模板。如果要开发Android,需要建一个Android项目。weexpack,可以帮助搭建Android和iOS项目 ----------------------weex------------------------------------------------------------一、安装依 查看详情

weex开发weex官方源码

公司目前使用版本:weex_sdk:0.10.0介绍地址:https://bintray.com/alibabaweex/maven/weex_sdk/0.18.0weex最新版本:weex_sdk:0.20.3.0-beta介绍地址:https://bintray.com/alibabaweex/maven/weex_sdk/0.20.3.0-beta 查看详情

concurrenthashmap源码浅析1.8(代码片段)

...介前面的一篇文章我们介绍了ConcurrentHashMap1.7版本版本的源码介绍,我们知道1.7版本的ConcurrentHashMap采用的是分段锁的思想,提高了锁的数量,提高了并发的特性,但是也有其局限性,例如就是并发的数量也就是锁的数量是不可... 查看详情

源码浅析-listviewanimations

1.前言ListViewAnimations开源库可以让Android开发者根据项目实际需求,很方便为ListView(严格来说AbsListView更准确些,因为还包含GridView。但为了方便统称ListView,下同)添加动画效果。ListViewAnimations提供Alpha,Swing-RightIn&#x... 查看详情

mybatis源码浅析

前言最近准备看一看mybatis的源码,虽说使用了很久,但是里面的一些细节还是不算很了解,今天整理一个简单的文档。我们首先需要理解一件事,mybatis的底层使用的还是jdbc,所以如果对jdbc很熟悉的话,源码看起来就会很轻松... 查看详情

androidhandler源码浅析(代码片段)

前言Android开发的小伙伴对于Handler一定不陌生了,基本面试必问的东西,但是很多人都是死记硬背不了解原理,这样面试很容易就丢分了,所以本文将会简单带大家了解一下Hander源码的实现。因为是浅析,所以... 查看详情

jquery源码浅析

...ipt无疑可以称为世界上最飘逸的语言,最近看了下jQuery的源码,实现了一个简陋的jQuery。我觉得要看懂jQuery整体结构,需要搞懂js作用域链,闭包,jsprototype继承,关于闭包网络上的定义实在太多了,这里参照了js权威指南里的定... 查看详情

学习笔记-集合hashmap源码浅析

/** *HashMap主要方法解析,jdk1.7版本的HashMap *一、构造 *4个构造相对之前的jdk版本功能基本不变,但是代码封装更完善。 *构造前一个参数是容量,相当于数组大小,后一个是负载因子 */publicHashMap(intinitialCapacity,... 查看详情