深刻Weex系列(九)Weex SDK可借鉴细节总结

一、前言

通过前面五篇文章的源码分析及总结,咱们对Weex的总体架构及核心源码都有了清晰的认识。本篇文章主要总结我在Weex SDK源码阅读时以为能够借鉴的细节。java

备注:本文侧重讲Weex SDK源码级别的可借鉴细节,对大方向上的可借鉴点好比动态化+Native思路、一项技术完整的生态等方面能够参考上一篇文章《深刻Weex系列(八)之Weex SDK架构分析》android

二、建造者模式

在使用Weex以前咱们都会进行Weex SDK的初始化,对于Weex SDK它的辅助配置类就使用到了建造者模式。git

建造者模式主要解决:一个模块各个部分子对象的构建算法可能变化,可是各个部分子对象相互结合在一块儿的算法确实稳定的。一句话总结就是:模块总体构建过程稳定,可是构建的每一步可能有出入。github

咱们结合Weex的场景来具体分析下:Weex配置模块的构建过程是稳定的(都须要提供一样的能力),可是构建的每一步则可能有出入(每一个配置的能力提供却能够多样)。算法

举例说明:例如Weex须要提供网络请求的基础能力(这个构建过程稳定),可是网络请求能够有不一样的实现方式(具体的构建算法可能变化)。性能优化

InitConfig config = new InitConfig.Builder().
        setImgAdapter(new WeexImageAdapter()).
        setHttpAdapter(new WeexHttpAdapter).
        setSoLoader(new WeexSoLoaderAdapter).
        build();
复制代码

好处:调用者无需知道构建模块如何组装,也不会忘记组装某一部分,同时也提供给了开发者定制的能力。bash

三、So的加载

So的成功加载对Weex的运行相当重要,毕竟Weex须要V8引擎执行Js与Native的交互,源码中也能够看出So没有加载成功则Weex的各个模块不会执行。微信

而在线上Bug收集中咱们会遇到UnsatisfiedLinkError错误,虽然不是频发性Bug,可是对于Weex而言一旦出现那么Weex就不可能再运行。因而Weex SDK对So加载这块作了优化,咱们看下So加载的代码逻辑:网络

public static boolean initSo(String libName, int version, IWXUserTrackAdapter utAdapter) {
    String cpuType = _cpuType();
    if (cpuType.equalsIgnoreCase(MIPS) ) {
      return false; // mips架构不支持,直接返回
    }

    boolean InitSuc = false;
    if (checkSoIsValid(libName, BuildConfig.ARMEABI_Size) ||checkSoIsValid(libName, BuildConfig.X86_Size)) {  // 校验So大小是否正常
      /**
       * Load library with {@link System#loadLibrary(String)}
       */
      try {
        // If a library loader adapter exists, use this adapter to load library
        // instead of System.loadLibrary.
        if (mSoLoader != null) {
          mSoLoader.doLoadLibrary(libName);// 自定义SoLoader加载的话本身去加载
        } else {
          System.loadLibrary(libName);// 默认加载的方式
        }
        commit(utAdapter, null, null);

        InitSuc = true;
      } catch (Exception | Error e2) {// So加载失败
        if (cpuType.contains(ARMEABI) || cpuType.contains(X86)) {
          commit(utAdapter, WXErrorCode.WX_ERR_LOAD_SO.getErrorCode(), WXErrorCode.WX_ERR_LOAD_SO.getErrorMsg() + ":" + e2.getMessage());
        }
        InitSuc = false;
      }

      try {
        if (!InitSuc) {
          // 没有加载成功的话则从文件中加载
          //File extracted from apk already exists.
          if (isExist(libName, version)) {
            boolean res = _loadUnzipSo(libName, version, utAdapter);// 从解压包中加载So
            if (res) {
              return res;
            } else {
              //Delete the corrupt so library, and extract it again.
              removeSoIfExit(libName, version);// 解压包也加载失败,删除;
            }
          }

          //Fail for loading file from libs, extract so library from so and load it.
          if (cpuType.equalsIgnoreCase(MIPS)) {
            return false;
          } else {
            try {
              InitSuc = unZipSelectedFiles(libName, version, utAdapter);// 从apk中解压出来So,而后加载;
            } catch (IOException e2) {
              e2.printStackTrace();
            }
          }
        }
      } catch (Exception | Error e) {
        InitSuc = false;
        e.printStackTrace();
      }
    }
    return InitSuc;
}
复制代码

能够看到Weex中有多项保障去保证So的成功加载,总结下流程图:多线程

Weex中So加载流程图

四、Weex的线程模型

各位老司机都知道多线程的好处也知道Android只有主线程才能更新UI,对于Weex来讲它有本身完整的一套工做机制,若是全部任务都在主线程那势必会积压太多任务,致使任务得不到及时执行同时也有卡顿的风险。

Weex SDK也考虑到了这些,分析Weex的机制能够知道任务主要花费在三方面:JSBridge相关、Dom相关、UI相关。因而对这三方面进行了细分,JSBridge相关的操做挪到JSBridge线程执行,Dom相关操做在Dom线程执行,避免了主线程积压太多任务。此处咱们能够想到使用异步线程。同时对于单项的任务例如Dom操做,须要是串行的。若是使用线程池,实际上也发挥不出线程池的威力。

分析到了这里。咱们的需求其实就很明确了:避免异步线程的建立及销毁过程消耗资源,同时支持串行执行。咱们能够设想一种线程能力:有任务的时候则执行,没有任务的时候则等待,是否是完美的符合咱们的需求。

幸运的是Android其实已经为咱们提供了这样的一个类:HandlerThread。你们能够参考我以前的一篇文章《Android性能优化(十一)之正确的异步姿式》

// 贴出Weex中使用的HandlerThread实例
    // JSBridge工做的Thread
    mJSThread = new WXThread("WeexJSBridgeThread", this);
    mJSHandler = mJSThread.getHandler();
    
    // Dom工做的Thread
    mDomThread = new WXThread("WeeXDomThread", new WXDomHandler(this));
    mDomHandler = mDomThread.getHandler();
复制代码

总结下Weex的线程模型:

  • JSBridge在WeexJSBridgeThread负责JS与Native的通讯;
  • 切换具体的Dom指令到WeeXDomThread负责关于Dom的各项如:解析、Rebuild Dom Tree、Layout等操做;
  • 切换到UI线程,负责原生View的建立、布局、事件添加、数据绑定等;

优点:

  • 避免主线程的卡顿风险;
  • 避免了线程的建立与销毁等资源消耗;
  • 同时支持串行操做;

五、交互函数参数类型的处理

对于Weex的RunTime,再怎么强大也少不了与Native的交互(方法调用,使用Native的能力),前面的系列文章也详细分析了Module的交互原理。可是有一个细节问题前面没有说到,就是JS与Native交互的方法签名,参数类型只能是String吗?

回到WXBridge这个通讯的桥梁,调用Native的方法都会走到callNative方法,而后走到WxBridgeManager.callNative方法,会发现函数体内有一行:

JSONArray array = JSON.parseArray(tasks);
复制代码

由此能够判定JS传递给Native的参数首先不只仅是普通String字符串,而是Json格式。实际上不论是断点查看或者翻阅WXStreamModule的代码,均可以发现Json的踪迹。

@JSMethod(uiThread = false)
  public void fetch(String optionsStr, final JSCallback callback, JSCallback progressCallback){
        JSONObject optionsObj = null;
        try {
          optionsObj = JSON.parseObject(optionsStr);
        }catch (JSONException e){
          WXLogUtils.e("", e);
        }
        ......
    }
复制代码

不过以上发现还不足以解决咱们的疑惑:参数类型只能是String吗?那必须不是!

首先回顾下在Module的注册过程当中会有一步是获取Module中被打上注解的方法而后存在mMethodMap中;而在真正调用方法的地方是NativeInvokeHelper的invoke方法:

public Object invoke(final Object target,final Invoker invoker,JSONArray args) throws Exception {
    final Object[] params = prepareArguments(invoker.getParameterTypes(),args);// 解析参数
    if (invoker.isRunOnUIThread()) {// 要求在主线程执行则抛到主线程执行;
      WXSDKManager.getInstance().postOnUiThread(new Runnable() {
        @Override
        public void run() {
          try {
            invoker.invoke(target, params);// 反射调用方法执行
          } catch (Exception e) {
            throw new RuntimeException(e);
          }
        }
      }, 0);
    } else {
      return invoker.invoke(target, params);
    }
    return null;
  }
复制代码

咱们再来详细跟踪下解析参数这步:

private Object[] prepareArguments(Type[] paramClazzs, JSONArray args) throws Exception {
    Object[] params = new Object[paramClazzs.length];
    Object value;
    Type paramClazz;
    for (int i = 0; i < paramClazzs.length; i++) {
      paramClazz = paramClazzs[i];
      if(i>=args.size()){
        if(!paramClazz.getClass().isPrimitive()) {
          params[i] = null;
          continue;
        }else {
          throw new Exception("[prepareArguments] method argument list not match.");
        }
      }
      value = args.get(i);
      // JSONObject与JSCallback类型单独处理
      if (paramClazz == JSONObject.class) {
        params[i] = value;
      } else if(JSCallback.class == paramClazz){
        if(value instanceof String){
          params[i] = new SimpleJSCallback(mInstanceId,(String)value);
        }else{
          throw new Exception("Parameter type not match.");
        }
      } else {
        // 其它类型的参数
        params[i] = WXReflectionUtils.parseArgument(paramClazz,value);
      }
    }
    return params;
  }
复制代码

看下其它参数类型的解析:

public static Object parseArgument(Type paramClazz, Object value) {
    if (paramClazz == String.class) {
      return value instanceof String ? value : JSON.toJSONString(value);
    } else if (paramClazz == int.class) {
      return value.getClass().isAssignableFrom(int.class) ? value : WXUtils.getInt(value);
    } else if (paramClazz == long.class) {
      return value.getClass().isAssignableFrom(long.class) ? value : WXUtils.getLong(value);
    } else if (paramClazz == double.class) {
      return value.getClass().isAssignableFrom(double.class) ? value : WXUtils.getDouble(value);
    } else if (paramClazz == float.class) {
      return value.getClass().isAssignableFrom(float.class) ? value : WXUtils.getFloat(value);
    } else {
      return JSON.parseObject(value instanceof String ? (String) value : JSON.toJSONString(value), paramClazz);
    }
  }
复制代码

跟踪到此处就显而易见:JS与Native的交互参数不只仅支持String。

咱们再来总结下Weex是如何实现不一样方法签名的交互的:

  • Module注册阶段保存下来Method;
  • JS发送指令调用Module方法传递的原始参数是Json格式;
  • 真正反射调用方法的时候从Method中拿到参数的具体类型,而后从Json中读到相应的值,再进行转换。

六、后记

本文主要记录了我在Weex源码阅读过程当中以为不错能够借鉴的细节,限于文章篇幅不能面面俱到。实际上不只Weex的总体思路,Weex SDK的代码也很是优秀,很是建议你们仔细阅读,学习优秀的源码对本身的编码能力会有必定程度的提高!

欢迎持续关注Weex源码分析项目:Weex-Analysis-Project

欢迎关注微信公众号:按期分享Java、Android干货!

欢迎关注
相关文章
相关标签/搜索