前言
在实际开发中,Weex官方虽然内置了一些基础组件让我们可以快速集成到应用中,但肯定还是无法满足多变的场景和功能需求。因此Weex提供了能力扩展机制,可以根据自己的业务进行定制自己的功能。
Module扩展
Module指非UI的特定功能。例如 sendHttp、openURL等。开发者需要自定义模块继承WXModule,并且按要求注册即可使用。
Module的注册
WXSDKEngine提供了多个注册Module的重载方法,最最常用的是映射模块名称和Class的注册方法:
public static boolean registerModule(String moduleName, Class<? extends WXModule> moduleClass) throws WXException {
return registerModule(moduleName, moduleClass,false);
}
public static <T extends WXModule> boolean registerModule(String moduleName, Class<T> moduleClass,boolean global) throws WXException {
return moduleClass != null && registerModule(moduleName, new TypeModuleFactory<>(moduleClass), global);
}
Module的注册涉及多个类,用时序图表示如下
注册说白了就是定义一个映射关系,使得运行期一方能根据标识找到另一方进行调用。这里的注册又分为Native注册和Js注册
-
Native注册
将自定义的Module Class包装为ModuleFactory,然后在sModuleFactoryMap中保存了Module的Name和ModuleFactory的映射。如果是全局的,则直接反射创建该Module实例,并且保存在sGlobalModuleMap中
-
Js注册
用map将moduleName和自定义Module中标记了JSMethod注解的方法名集合保存起来,这里反射获取Module中的方法用的getMethods,因此提供调用的方法需要定义成public,同时可以调用父类中符合条件的方法。map的数据格式是{'moduleName':['updateAttrs','updateStyle']},Weex内部会包装成自己的数据结构WXJSObject,将其转为byte数组,交给WXBridge处理,最终会执行native方法,方法类型是METHOD_REGISTER_MODULES(registerModules)。记住WXBridge,它承担了Native和Js交互的核心。
-
Native+Runtime
JS注册相比Native要复杂的多,如前所述,map转换为byte数组方便传输,然后由WXBridge调用native方法完成,相应的入口在wx_bridge.cpp中:
auto result = WeexCoreManager::Instance()->getPlatformBridge() ->core_side()->ExecJS(instance_id.getChars(), name_space.getChars(),function.getChars(),params);这里会经过weex_core_manager=>platform_bridge=>core_side_in_platform,一通跳转后最终实际执行的是weex_runtime中的exeJS方法。而提供Weex Runtime的正是
weex-main-jsfm.js(默认weex沙盒,也可以设置为main.js),它和V8及JNI共同构成了Weex的底层引擎。该js文件打包在assets目录下,SDK初始化的时候首先由V8加载。
Module调用
举个例子,前端调用stream组件请求接口数据:
var stream = weex.requireModule('stream')
stream.fetch{...}
这段代码经过js-framework处理,由native开始,经过SystemMessageHandler#handleMessage->nativeRunwork,然后交给WXBridge处理。时序图如下:
- 从WXBridge开始,运行在WeexJsBridgeThread线程中
- 从前面注册时提到的sModuleFactoryMap中,根据module名称取出对应构建该模块的Factory
- 通过findModule找出已注册的Module,是全局的话在开始就创建过了。不是的话如果map中已存在则直接返回,否则反射创建并存入map。因此相同instaceId的情况下多次调用的是同一个Module实例
- 通过method名找到Factory中对应的MethodInvoker,它对方法、参数类型和是否在主线程运行(默认是)做了包装,最后交给NativeInvokeHelper处理。这里做了参数解析、线程切换,并最终通过反射调用目标方法
回调JSCallback
很多时候JS调用了Module之后,我们需要将一些调用的结果返回给对方,这时候就需要借助JSCallback。
- 前面提到NativeInvokeHelper会对参数进行处理,这里Weex使用了FastJson,调用方法的入参用JSONArray表示,而FastJson的JSONArray实现了List接口,JSONObject实现了Map接口,方便扩展。
- 处理参数时,如果该形参类型是JSCallback则会包装成其唯一实现类SimpleJSCallback
- 回调时,WXBridgeManager#callbackJavascript会通过Handler将线程切换到WeexJsBridgeThread中执行
- 最终还是通过WXBridge的native方法回调JS
Component扩展
Component指实现特别功能的Native控件。例如:RichTextview、RefreshListview等。开发者需要自定义组件继承WXComponent(也有WXVContainer等,但这些也都继承于WXComponent),并且按要求注册即可使用。Component也是Weex扩展能力中应用的最为广泛的,因为界面的渲染都是围绕Component展开的。
Component的注册
和Module注册类似,也分为Native注册和JS注册。不同的是Native注册保存的是组件名称和IFComponentHolder的映射,类似于Module的global参数,holder的loadIfNonLazy方法会获取注册的该组件所有注解,若包含@Component并且其lazyLoad==false(默认为true)则立即解析并获取其methods保存到holder中,否则只在初次使用时解析、保存。注意Component提供给前端调用的有两种:属性和方法
Component的调用
属性
Component对应的设置属性的方法必须添加注解@WXComponentProp(name=value(value is attr or style of dsl)),并且方法必须为public。这个也是前端最常调用的方法,常用于在初始化时赋予控件展示所必须的值,比如TextView的text、ImageView的src等,这个过程往往伴随着页面渲染的过程,比起方法调用更为复杂,这里就以最常用的图片控件WXImage为例:
@WXComponentProp(name = "src")
public void setSrc(String src){
...
}
只关注其设置图片来源setSrc方法的调用过程,阅读源码发现从SystemMessageHandler#handleMessage
-> nativeRunwork -> WXBridge#callCreateBody开始。考虑到callCreateBody的作用是创建根节点,而具体添加子元素是由callAddElement完成的,时序图如下:
- 初始化组件里,给属性赋值前有两个关键步骤:callAddElement、callLayout
- Weex里对于UI控件的调用全部封装成一个个Action,callAddElement创建的是GraphicActionAddElement。如果接下来要更新布局则存储在
WXSDKInstance的map中,否则直接切换到主线程中执行Action - callLayout则会从map中取出GraphicActionAddElement,通过WXRenderHandler切换到主线程执行相应任务。这里会调用WXComponent的
bindData方法,最终获取到设置属性值的key-value集合,调用到对应的自定义设置属性的方法 - 连接两个关键步骤的就是Weex的消息循环机制,类似Android的Handler,一个地方放,一个地方取。Weex将这块下沉到底层,具体实现源码在weex_core/Source/base/message_loop下
需要注意的是,callAddElement中创建GraphicActionAddElement虽然只有简单的一句代码,但是创建自定义Component实例所做的工作都在这里
方法
方法的声明和Module一样也是添加@JSMethod注解,调用从WXBridge#callNativeComponent开始,后续和Module中的类似。相比属性,方法在控件中的应用要少一些,因为控件的方法更多是由用户操作触发的而不是前端直接控制。
Adapter扩展
Adapter不是具体的基类,而是一组接口。Weex对一些基础功能提供了统一的接口,开发可以通过实现这些接口来定制自己的业务。可以理解为适配器,为上层Component和Module提供基础能力。
Adapter注册
Adpter的注册比较简单,通过InitConfig的Builder模式将自己的实现类传入,然后将引用保存到WXSDKManager中供Component和Module调用。
Adapter调用
其实就是调用到真正实现类中的方法,比如上面的WXImage,在设置src属性时用到了一个很常用的Adapter:IWXImgLoaderAdapter
private void setRemoteSrc(Uri rewrited, int blurRadius) {
...
IWXImgLoaderAdapter imgLoaderAdapter = getInstance().getImgLoaderAdapter();
if (imgLoaderAdapter != null) {
imgLoaderAdapter.setImage(rewritedStr, getHostView(),getImageQuality(), imageStrategy);
}
}
IWXImgLoaderAdapter就是负责Weex的图片加载,因为图片加载非常重要且第三方类库较多,所以通过这种方式Weex实现了框架和图片库解耦,并且没有提供默认实现,由调用方自己决定是Picasso、Glide、ImageLoader还是其他。接着从上面WXImage的属性调用时序图,从setSrc开始:
除了图片,Weex还提供了许多接口,覆盖了网络、日志、存储、异常上报、打点等,就不一一展开了。
总结
Weex通过Module、Component、Adapter的扩展能力,组合搭建起了一个集成应用的基本框架,使得开发者可以在此基础之上,遵循共同的标准丰富自己的界面,完善自己的业务逻辑。