目录
- Weex概述
- Weex工作原理
- Weex在Android上如何跑起来
- 关于Weex,ReactNative
Weex概述
Weex非常轻量,体积小巧,语法简单,方便接入和上手。ReactNative官方只允许将ReactNative基础js库和业务JS一起打成一个JS bundle,没有提供分包的功能,所以如果想节约流量就必须制作分包打包工具。而Weex默认打的JS bundle只包含业务JS代码,体积小很多,基础JS库包含在Weex SDK中,这一点Weex与Facebook的React Native和微软的Cordova相比,Weex更加轻量,体积小巧。把Weex生成的JS bundle轻松部署到服务器端,然后Push到客户端,或者客户端请求新的资源即可完成发布。如此快速的迭代就解决了前言里面说的第一个痛点,发布无法控制时间,
Weex中Native组件和API都可以横向扩展,业务方可去中心化横向灵活化定制组件和功能模块。并且还可以直接复用Web前端的工程化管理和监控性能等工具。
知乎上有一个关于Weex 和 ReactNative很好的对比文章weex&ReactNative对比,推荐大家阅读。
Weex在2017年2月17日正式发布v0.10.0,这个里程碑的版本开始完美的兼容Vue.js开发Weex界面。
Weex又于2017年2月24 迁移至 Apache 基金会,阿里巴巴会基于 Apache 的基础设施继续迭代。并启用了全新的 GitHub 仓库:https://github.com/apache/incubator-weex
故以下源码分析都基于v0.17.0这个版本。
Weex工作原理
Weex可以通过自己设计的DSL,书写.we文件或者.vue文件来开发界面,整个页面书写分成了3段,template、style、script,借鉴了成熟的MVVM的思想。
Weex的页面结构
DOM模型
Weex页面通过类似的HTML DOM的方式管理界面。首先,页面会被分解成一个DOM树。每个DOM节点都代表了一个相对独立的native视图单元。然后不同的视图单元通过树状结构组织在一起,构成完成的页面。
Weex 在 JS 引擎中,为每个页面都提供了一套 Native DOM APIs,这套接口和 HTML DOM APIs 非常接近,利用这套接口我们可以通过 JavaScript 控制 native 的渲染逻辑。而且 Weex 上层的 Vue 2.0 也是基于这套接口进行适配的。
绝大多数情况下 JS 框架会把 Native DOM APIs 都封装好,开发者不需要直接对 Native DOM 进行操作。
Document
类,整个页面文档。Node
类,结点的基础类。Element
类,元素结点,继承自Node
,单个视图单元。Comment
类,注释结点,继承自Node
,无实际意义,通常用作占位符。每个 Weex 页面都有一个 weex.document 对象,该对象就是一个 Document 类的实例,也是下面所有接口调用的起点。
组件
Weex 的界面就是由这些组件以 DOM 树的方式构建出来的。这些组件,原生view(Weex中的Component)与weex标签的映射。自带的组件都是通过这样的方式创建出来的。
<div></div> 对应 WXDiv
布局系统
Weex 页面中的组件会按照一定的布局规范来进行排布,我们这里提供了 CSS 中的盒模型、flexbox 和 绝对/相对/固定/吸附布局这三大块布局模型。
- 盒模型:通过宽、高、边框、内外边距、边框等 CSS 属性描述一个组件本身的尺寸。
- flexbox:通过 CSS 3 Flexbox 布局规范定义和描述组件之间的空间分布情况。
- position:支持 CSS position 属性中的
absolute
,relative
,fixed
,sticky
位置类型,其中relative
是默认值。
功能
Weex 提供了非常丰富的系统功能 API,包括弹出存储、网络、导航、弹对话框和 toast 等,开发者可以在 Weex 页面通过获取一个 **native module **的方式引入并调用这些客户端功能 API。
上文可以知道所有的功能,都是通过module来实现的。在Js中调用。
WXStorageModule->Storage Api
生命周期
每个 Weex 页面都有其自身的生命周期,页面从开始被创建到最后被销毁,会经历到整个过程。这是通过对 Weex 页面的创建和销毁,在路由中通过 SDK 自行定义并实现的。
Weex在Android中是如何跑起来的
从.we或.vue文件到JS bundle这部分前端的代码。本文暂不涉及。
可以先通过看看.we
编译后的js文件,先看看结构。更加具体的后面陆续学习后补充。
简单的.we
编译后的js
//第一行,表明了编译前的文件。vue的话,则为vue
// { "framework": "Weex" }
//开始 webpackBootstrap,应该是初始化脚手架的部分。
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
//从0这里开始,就是我们写的代码了。在0这里,获取通用的style和template
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
var __weex_template__ = __webpack_require__(1)
var __weex_style__ = __webpack_require__(2)
__weex_define__('@weex-component/22de37c9919eb3fd01a7758b3c5f2baf', [], function(__weex_require__, __weex_exports__, __weex_module__) {
__weex_module__.exports.template = __weex_template__
__weex_module__.exports.style = __weex_style__
})
__weex_bootstrap__('@weex-component/22de37c9919eb3fd01a7758b3c5f2baf',undefined,undefined)
/***/ }),
/*从1开始,就是我们自己定义的对象了。看起来像是dom 模型的json文件
type为div的话,它对应的classlist 为container。children:标记它的子节点。
我们看到,一个节点,对应的属性可能有 type,classlist,attr,event
*/
/* 1 */
/***/ (function(module, exports) {
module.exports = {
"type": "div",
"classList": [
"container"
],
"children": [
{
"type": "div",
"classList": [
"cell"
],
"children": [
{
"type": "image",
"classList": [
"thumb"
],
"attr": {
"src": ""
}
},
{
"type": "text",
"classList": [
"title"
],
"attr": {
"value": "Hello Weex"
}
}
]
}
]
}
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = {
"cell": {
"marginTop": 10,
"marginLeft": 10
},
"thumb": {
"width": 200,
"height": 200
},
"title": {
"textAlign": "center",
"flex": 1,
"color": "#808080"
}
}
/***/ })
/******/ ]);
-
从1开始,就是我们自己定义的对象了。看起来像是dom 模型的json文件。
对于
div
这样的容器型组件,它可能有children
属性。我们看到,一个节点,对应的属性可能有
type
,classlist
,attr
,event
。
主要还是围绕Weex SDK的源码来进行了解。
Weex SDK初始化
先来看看playground App Android中是如何初始化的吧。
初始化是在Application中。
文件位置:incubator-weex-master/android/playground/app/src/main/java/com/alibaba/weex/WxApplication.java
public class WXApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
/**
* Set up for fresco usage.
* Set<RequestListener> requestListeners = new HashSet<>();
* requestListeners.add(new RequestLoggingListener());
* ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
* .setRequestListeners(requestListeners)
* .build();
* Fresco.initialize(this,config);
**/
// initDebugEnvironment(true, false, "DEBUG_SERVER_HOST");
WXSDKEngine.addCustomOptions("appName", "WXSample");
WXSDKEngine.addCustomOptions("appGroup", "WXApp");
WXSDKEngine.initialize(this,
new InitConfig.Builder()
//.setImgAdapter(new FrescoImageAdapter())// use fresco adapter
.setImgAdapter(new ImageAdapter())
.setWebSocketAdapterFactory(new DefaultWebSocketAdapterFactory())
.setJSExceptionAdapter(new JSExceptionAdapter())
.setHttpAdapter(new InterceptWXHttpAdapter())
.build()
);
WXSDKManager.getInstance().setAccessibilityRoleAdapter(new DefaultAccessibilityRoleAdapter());
try {
Fresco.initialize(this);
WXSDKEngine.registerComponent("synccomponent", WXComponentSyncTest.class);
WXSDKEngine.registerComponent(WXParallax.PARALLAX, WXParallax.class);
WXSDKEngine.registerComponent("richtext", RichText.class);
WXSDKEngine.registerModule("render", RenderModule.class);
WXSDKEngine.registerModule("event", WXEventModule.class);
WXSDKEngine.registerModule("syncTest", SyncTestModule.class);
WXSDKEngine.registerComponent("mask",WXMask.class);
WXSDKEngine.registerDomObject("mask", WXMaskDomObject.class);
WXSDKEngine.registerModule("myModule", MyModule.class);
WXSDKEngine.registerModule("geolocation", GeolocationModule.class);
/**
* override default image tag
* WXSDKEngine.registerComponent("image", FrescoImageComponent.class);
*/
} catch (WXException e) {
e.printStackTrace();
}
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
// The demo code of calling 'notifyTrimMemory()'
if (false) {
// We assume that the application is on an idle time.
WXSDKManager.getInstance().notifyTrimMemory();
}
// The demo code of calling 'notifySerializeCodeCache()'
if (false) {
WXSDKManager.getInstance().notifySerializeCodeCache();
}
}
});
}
...
}
初始化的代码很长,主要可以分成下面几个部分来看:
-
WXSDKEngine.addCustomOptions("appName", "WXSample")
等方法。这些个方法可以注册一些APP中的常量到js内方便调用。JS可以通过weex.config.env.appName
这样的方式来调用。 - 通过建造者模式来创造InitConfig来初始化WxJsFramework
- 通过
WXSDKEngine.registerComponent("richtext", RichText.class);
和WXSDKEngine.registerModule("render", RenderModule.class);registerDomObject()等
方法来注册自定义的Component和Module。Component可以看出是Android中native控件和Wx的绑定。而Module则可以看出是非UI功能的组件和Wx的绑定。具体的这两者,放到后面再细谈。 - 通过
registerActivityLifecycleCallbacks
方法来注册得到生命周期的回调
1. 初始化配置
其实初始化的这些配置是添加到WXEnvironment
类中进行管理。会在初始化JsFramework的过程中,传递给到其中。通过weex.config.env.appName
这样的方式来获取。
public class WXEnvironment {
//android os的信息
//...
/*********************
* Global config
***************************/
//js lib的版本
public static String JS_LIB_SDK_VERSION = BuildConfig.buildJavascriptFrameworkVersion;
public static String WXSDK_VERSION = BuildConfig.buildVersion;
//保存全局的Application,会在WXSDKEngine中进行初始化
public static Application sApplication;
//简单的获得设备的devId 通过TELEPHONY_SERVICE
public static final String DEV_Id = getDevId();
//虽然标注了过时的,但是默认的宽度还是750
@Deprecated
public static int sDefaultWidth = 750;
//是否标记初始化完成
public volatile static boolean JsFrameworkInit = false;
public static final String SETTING_EXCLUDE_X86SUPPORT = "env_exclude_x86";
public static boolean SETTING_FORCE_VERTICAL_SCREEN = false;
/**
* Debug model
*/
public static boolean sDebugMode = false;
public static boolean sForceEnableDevTool = false;
public static String sDebugWsUrl = "";
public static boolean sDebugServerConnectable = false;
public static boolean sRemoteDebugMode = false;
public static String sRemoteDebugProxyUrl = "";
public static boolean sDebugNetworkEventReporterEnable = false;//debugtool network switch
public static long sJSLibInitTime = 0;
public static long sSDKInitStart = 0;// init start timestamp
public static long sSDKInitInvokeTime = 0;//time cost to invoke init method
public static long sSDKInitExecuteTime = 0;//time cost to execute init job
/** from init to sdk-ready **/
public static long sSDKInitTime =0;
public static LogLevel sLogLevel = LogLevel.DEBUG;
private static boolean isApkDebug = true;
public static boolean isPerf = false;
private static boolean openDebugLog = false;
private static String sGlobalFontFamily;
//自定义的一些属性是添加到这个map当中
private static Map<String, String> options = new HashMap<>();
static {
options.put(WXConfig.os, OS);
options.put(WXConfig.osName, OS);
}
/**
* dynamic
*/
public static boolean sDynamicMode = false;
public static String sDynamicUrl = "";
/**
* Fetch system information.
* @return map contains system information.
*/
public static Map<String, String> getConfig() {
Map<String, String> configs = new HashMap<>();
configs.put(WXConfig.os, OS);
configs.put(WXConfig.appVersion, getAppVersionName());
configs.put(WXConfig.cacheDir, getAppCacheFile());
configs.put(WXConfig.devId, DEV_Id);
configs.put(WXConfig.sysVersion, SYS_VERSION);
configs.put(WXConfig.sysModel, SYS_MODEL);
configs.put(WXConfig.weexVersion, String.valueOf(WXSDK_VERSION));
configs.put(WXConfig.logLevel,sLogLevel.getName());
try {
options.put(WXConfig.scale, Float.toString(sApplication.getResources().getDisplayMetrics().density));
}catch (NullPointerException e){
//There is little chance of NullPointerException as sApplication may be null.
WXLogUtils.e("WXEnvironment scale Exception: ", e);
}
configs.putAll(options);
if(configs!=null&&configs.get(WXConfig.appName)==null && sApplication!=null){
configs.put(WXConfig.appName, sApplication.getPackageName());
}
return configs;
}
public static Map<String, String> getCustomOptions() {
return options;
}
public static void addCustomOptions(String key, String value) {
options.put(key, value);
}
//...
}
2. 初始化过程
WXSDKEngine.initialize(this, config)
方法。
public static void initialize(Application application,InitConfig config){
//这里是先同步这个方法,方法未执行完,不会走其他被mLock锁住的方法。
//可以看到mLock其实只是锁了一个查询初始化状态的方法
synchronized (mLock) {
if (mIsInit) {
return;
}
long start = System.currentTimeMillis();
WXEnvironment.sSDKInitStart = start;
//是否可调试。可以的话。在控制台输出log
if(WXEnvironment.isApkDebugable()){
WXEnvironment.sLogLevel = LogLevel.DEBUG;
}else{
if(WXEnvironment.sApplication != null){
WXEnvironment.sLogLevel = LogLevel.WARN;
}else {
WXLogUtils.e(TAG,"WXEnvironment.sApplication is " + WXEnvironment.sApplication);
}
}
//进行初始化
doInitInternal(application,config);
//log 初始化耗时
WXEnvironment.sSDKInitInvokeTime = System.currentTimeMillis()-start;
WXLogUtils.renderPerformanceLog("SDKInitInvokeTime", WXEnvironment.sSDKInitInvokeTime);
mIsInit = true;
}
}
进一步看,doInitInternal
做了什么
private static void doInitInternal(final Application application,final InitConfig config){
//校验Application,通过这个全局变量将application context保存到WXEnvironment中。如上面的分析所述
WXEnvironment.sApplication = application;
if(application == null){
WXLogUtils.e(TAG, " doInitInternal application is null");
}
WXEnvironment.JsFrameworkInit = false;
//我们这里先知道,WxBridgeManager就如他的名字一样,是js和native进行通信的一个管理者。扶着协调两者之间的通行的作用。
//WxBridgeManager运行在一个HandlerThread(JsThread&JsHandler)中。这里就进行了异步的初始化。
WXBridgeManager.getInstance().post(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
//这里SDK manager是一个管理weex context的对象
WXSDKManager sm = WXSDKManager.getInstance();
//调用一个全局的回调
sm.onSDKEngineInitialize();
if(config != null ) {
//将配置的参数,保存在SDK manager中
sm.setInitConfig(config);
}
//初始化Android的JS引擎.
WXSoInstallMgrSdk.init(application,
sm.getIWXSoLoaderAdapter(),
sm.getWXStatisticsListener());
//进入这个方法,能够看到对so文件的初始化做了版本的适配。而且做了失败的重试
boolean isSoInitSuccess = WXSoInstallMgrSdk.initSo(V8_SO_NAME, 1, config!=null?config.getUtAdapter():null);
if (!isSoInitSuccess) {
return;
}
//初始化JSFrameWork.其实就是像JSThread发送一个 INIT_FRAMEWORK的消息。然后开始初始化。初始化的过程就在JSThread中
sm.initScriptsFramework(config!=null?config.getFramework():null);
//最后统计时间
WXEnvironment.sSDKInitExecuteTime = System.currentTimeMillis() - start;
WXLogUtils.renderPerformanceLog("SDKInitExecuteTime", WXEnvironment.sSDKInitExecuteTime);
}
});
//这里注册公共的组件部分
register();
}
从doInitInternal()
方法中,可以看到,初始化,其实就是干了两件事情。
- 初始化JSFramework
- 注册对应的native组件到JSFramework当中。
初始化JSFramework
上文调用initScriptsFramework
方法,其实就是通过JsThread
的handler
发送WXJSBridgeMsgType.INIT_FRAMEWORK
的message
。
之后会转到WxBridgeManager
中的initFramework
方法。
private void initFramework(String framework) {
//这第一次一定是去加载main.js文件了。main.js文件到底是何方神圣?
if (!isJSFrameworkInit()) {
if (TextUtils.isEmpty(framework)) {
// if (WXEnvironment.isApkDebugable()) {
WXLogUtils.d("weex JS framework from assets");
// }
framework = WXFileUtils.loadAsset("main.js", WXEnvironment.getApplication());
}
//如果为空,则直接设置失败
if (TextUtils.isEmpty(framework)) {
setJSFrameworkInit(false);
commitJSFrameworkAlarmMonitor(IWXUserTrackAdapter.JS_FRAMEWORK, WXErrorCode.WX_ERR_JS_FRAMEWORK, "JS Framework is empty!");
return;
}
try {
if (WXSDKManager.getInstance().getWXStatisticsListener() != null) {
WXSDKManager.getInstance().getWXStatisticsListener().onJsFrameworkStart();
}
//下面这段是去获取crash文件
long start = System.currentTimeMillis();
String crashFile = "";
try {
crashFile = WXEnvironment.getApplication().getApplicationContext().getCacheDir().getPath();
} catch (Exception e) {
e.printStackTrace();
}
boolean pieSupport = true;
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
pieSupport = false;
}
} catch (Exception e) {
e.printStackTrace();
}
WXLogUtils.d("[WXBridgeManager] initFrameworkEnv crashFile:" + crashFile + " pieSupport:" + pieSupport);
//看注释,知道开始扩展frameworkenv
// extends initFramework.这里这个是进入native方法中
//最新版本使用了多进程的方式来进行初始化。同样也是在native方法中完成。这儿就暂且不看了
//这里的WxBridge就是js和native通信通道。
//这里的assembleDefaultOptions会将WXEnvironment中的配置注入到js当中
if (mWXBridge.initFrameworkEnv(framework, assembleDefaultOptions(), crashFile, pieSupport) == INIT_FRAMEWORK_OK) {
WXEnvironment.sJSLibInitTime = System.currentTimeMillis() - start;
WXLogUtils.renderPerformanceLog("initFramework", WXEnvironment.sJSLibInitTime);
WXEnvironment.sSDKInitTime = System.currentTimeMillis() - WXEnvironment.sSDKInitStart;
WXLogUtils.renderPerformanceLog("SDKInitTime", WXEnvironment.sSDKInitTime);
//这里还没报错,那就算是初始化完成了。
setJSFrameworkInit(true);
if (WXSDKManager.getInstance().getWXStatisticsListener() != null) {
WXSDKManager.getInstance().getWXStatisticsListener().onJsFrameworkReady();
}
//这里先将失败的任务重新添加回来
execRegisterFailTask();
WXEnvironment.JsFrameworkInit = true;
//重新注册。最后通过jsThread中JsBridge通过execJS系列native方法重新注册
registerDomModule();
String reinitInfo = "";
if (reInitCount > 1) {
reinitInfo = "reinit Framework:";
}
commitJSFrameworkAlarmMonitor(IWXUserTrackAdapter.JS_FRAMEWORK, WXErrorCode.WX_SUCCESS, reinitInfo + "success");
} else {
if (reInitCount > 1) {
WXLogUtils.e("[WXBridgeManager] invokeReInitFramework ExecuteJavaScript fail");
String err = "[WXBridgeManager] invokeReInitFramework ExecuteJavaScript fail reinit FrameWork";
commitJSFrameworkAlarmMonitor(IWXUserTrackAdapter.JS_FRAMEWORK, WXErrorCode.WX_ERR_JS_REINIT_FRAMEWORK, err);
} else {
WXLogUtils.e("[WXBridgeManager] invokeInitFramework ExecuteJavaScript fail");
String err = "[WXBridgeManager] invokeInitFramework ExecuteJavaScript fail";
commitJSFrameworkAlarmMonitor(IWXUserTrackAdapter.JS_FRAMEWORK, WXErrorCode.WX_ERR_JS_FRAMEWORK, err);
}
}
} catch (Throwable e) {
if (reInitCount > 1) {
WXLogUtils.e("[WXBridgeManager] invokeInitFramework ", e);
String err = "[WXBridgeManager] invokeInitFramework reinit FrameWork exception!#" + e.toString();
commitJSFrameworkAlarmMonitor(IWXUserTrackAdapter.JS_FRAMEWORK, WXErrorCode.WX_ERR_JS_REINIT_FRAMEWORK, err);
} else {
WXLogUtils.e("[WXBridgeManager] invokeInitFramework ", e);
String err = "[WXBridgeManager] invokeInitFramework exception!#" + e.toString();
commitJSFrameworkAlarmMonitor(IWXUserTrackAdapter.JS_FRAMEWORK, WXErrorCode.WX_ERR_JS_FRAMEWORK, err);
}
}
}
}
加载本地的main.js
第一次下载完源码。搜索全局。会发现发现找不到main.js
这样的文件。其实他是SDK自带的。它是由gradle从中,加载 incubator-weex\pre-build\native-bundle-main.js
文件生成的。直接打开这个文件会看到一堆经过webpack压缩之后的文件
下面这个源文件的路径。已经在github上找不到了。
这个文件的源文件在https://github.com/apache/incubator-weex/tree/master/html5目录下。对应的入口文件是 html5/render/native/index.js
简单的看一下gradle文件的配置
incubator-weex\android\sdk\build.gradle
android{
...
//将文件从prebuild目录中复制到assets目录下,并重命名
copy {
from '../../pre-build'
into new File(projectDir,"assets")
include 'native-bundle-main.js'
rename('native-bundle-main.js','main.js')
}
//从这个文件中,得到 buildJavascriptFrameworkVersion 的版本号
def line
new File(projectDir,"assets/main.js").withReader { line = it.readLine() }
def m = line =~ /[A-Z\s]+\s+([0-9\.]+),\s+Build\s+[0-9]+/;
def jsfmVersion = m[0][1]
println jsfmVersion
...
}
assembleDefaultOptions()
方法
private WXParams assembleDefaultOptions() {
//通过Environment的getConfig方法,就对应的配置输入到config的map中
Map<String, String> config = WXEnvironment.getConfig();
//再通过WXParams这个对象,传递给JsFrameworks当中
WXParams wxParams = new WXParams();
wxParams.setPlatform(config.get(WXConfig.os));
wxParams.setCacheDir(config.get(WXConfig.cacheDir));
wxParams.setOsVersion(config.get(WXConfig.sysVersion));
wxParams.setAppVersion(config.get(WXConfig.appVersion));
wxParams.setWeexVersion(config.get(WXConfig.weexVersion));
wxParams.setDeviceModel(config.get(WXConfig.sysModel));
wxParams.setShouldInfoCollect(config.get("infoCollect"));
wxParams.setLogLevel(config.get(WXConfig.logLevel));
String appName = config.get(WXConfig.appName);
if (!TextUtils.isEmpty(appName)) {
wxParams.setAppName(appName);
}
//这里指的注意的是设置了deivceWidth。这里需要注意的是,如果没有手动设置这个值,其实是会同个这个utils中去获取。而这个WXViewUtils其实得到整个屏幕宽度
wxParams.setDeviceWidth(TextUtils.isEmpty(config.get("deviceWidth")) ? String.valueOf(WXViewUtils.getScreenWidth(WXEnvironment.sApplication)) : config.get("deviceWidth"));
wxParams.setDeviceHeight(TextUtils.isEmpty(config.get("deviceHeight")) ? String.valueOf(WXViewUtils.getScreenHeight(WXEnvironment.sApplication)) : config.get("deviceHeight"));
//这里讲所有自定额的Options设置
wxParams.setOptions(WXEnvironment.getCustomOptions());
//是否需要初始化v8
wxParams.setNeedInitV8(WXSDKManager.getInstance().needInitV8());
mInitParams = wxParams;
return wxParams;
}
WXViewUtils
中
public static int getScreenWidth(Context ctx) {
if(ctx!=null){
Resources res = ctx.getResources();
//获取屏幕的宽度像素
mScreenWidth = res.getDisplayMetrics().widthPixels;
if(WXEnvironment.SETTING_FORCE_VERTICAL_SCREEN){
mScreenHeight = res
.getDisplayMetrics()
.heightPixels;
mScreenWidth = mScreenHeight > mScreenWidth ? mScreenWidth : mScreenHeight;
}
} else if(WXEnvironment.isApkDebugable()){
throw new WXRuntimeException("Error Context is null When getScreenHeight");
}
return mScreenWidth;
}
注册对应的native组件到JSFramework当中
回到WXSDKEngine
中的doInitInternal()
方法的最后的register()
方法。由这个方法进行注册相应的组件。
private static void register() {
//这个batchOperationHelper是一个注册组件的intercepter
//创建这个类。之后registerXX就会添加到注册队列中。最后再通过flush()将其一次性执行
BatchOperationHelper batchHelper = new BatchOperationHelper(WXBridgeManager.getInstance());
//下面开始批量注册SDK自带的组件
try {
registerComponent(
new SimpleComponentHolder(
WXText.class,
new WXText.Creator()
),
false,
WXBasicComponentType.TEXT
);
registerComponent(
new SimpleComponentHolder(
WXDiv.class,
new WXDiv.Ceator()
),
false,
WXBasicComponentType.CONTAINER,
WXBasicComponentType.DIV,
WXBasicComponentType.HEADER,
WXBasicComponentType.FOOTER
);
registerComponent(
new SimpleComponentHolder(
WXImage.class,
new WXImage.Ceator()
),
false,
WXBasicComponentType.IMAGE,
WXBasicComponentType.IMG
);
registerComponent(
new SimpleComponentHolder(
WXScroller.class,
new WXScroller.Creator()
),
false,
WXBasicComponentType.SCROLLER
);
registerComponent(
new SimpleComponentHolder(
WXSlider.class,
new WXSlider.Creator()
),
true,
WXBasicComponentType.SLIDER,
WXBasicComponentType.CYCLE_SLIDER
);
registerComponent(
new SimpleComponentHolder(
WXSliderNeighbor.class,
new WXSliderNeighbor.Creator()
),
true,
WXBasicComponentType.SLIDER_NEIGHBOR
);
String simpleList = "simplelist";
registerComponent(SimpleListComponent.class,false,simpleList);
registerComponent(WXListComponent.class, false,WXBasicComponentType.LIST,WXBasicComponentType.VLIST,WXBasicComponentType.RECYCLER,WXBasicComponentType.WATERFALL);
registerComponent(WXRecyclerTemplateList.class, false,WXBasicComponentType.RECYCLE_LIST);
registerComponent(HorizontalListComponent.class,false,WXBasicComponentType.HLIST);
registerComponent(WXBasicComponentType.CELL, WXCell.class, true);
registerComponent(WXBasicComponentType.CELL_SLOT, 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);
registerComponent(WXBasicComponentType.A, WXA.class, false);
registerComponent(WXBasicComponentType.EMBED, WXEmbed.class, true);
registerComponent(WXBasicComponentType.WEB, WXWeb.class);
registerComponent(WXBasicComponentType.REFRESH, WXRefresh.class);
registerComponent(WXBasicComponentType.LOADING, WXLoading.class);
registerComponent(WXBasicComponentType.LOADING_INDICATOR, WXLoadingIndicator.class);
registerComponent(WXBasicComponentType.HEADER, WXHeader.class);
registerModule("modal", WXModalUIModule.class, false);
registerModule("instanceWrap", WXInstanceWrap.class, true);
registerModule("animation", WXAnimationModule.class, true);
registerModule("webview", WXWebViewModule.class, true);
registerModule("navigator", WXNavigatorModule.class);
registerModule("stream", WXStreamModule.class);
registerModule("timer", WXTimerModule.class, false);
registerModule("storage", WXStorageModule.class, true);
registerModule("clipboard", WXClipboardModule.class, true);
registerModule("globalEvent",WXGlobalEventModule.class);
registerModule("picker", WXPickersModule.class);
registerModule("meta", WXMetaModule.class,true);
registerModule("webSocket", WebSocketModule.class);
registerModule("locale", WXLocaleModule.class);
registerDomObject(simpleList, WXListDomObject.class);
registerDomObject(WXBasicComponentType.INDICATOR, WXIndicator.IndicatorDomNode.class);
registerDomObject(WXBasicComponentType.TEXT, WXTextDomObject.class);
registerDomObject(WXBasicComponentType.HEADER, WXCellDomObject.class);
registerDomObject(WXBasicComponentType.CELL, WXCellDomObject.class);
registerDomObject(WXBasicComponentType.CELL_SLOT, WXCellDomObject.class);
registerDomObject(WXBasicComponentType.INPUT, BasicEditTextDomObject.class);
registerDomObject(WXBasicComponentType.TEXTAREA, TextAreaEditTextDomObject.class);
registerDomObject(WXBasicComponentType.SWITCH, WXSwitchDomObject.class);
registerDomObject(WXBasicComponentType.LIST, WXListDomObject.class);
registerDomObject(WXBasicComponentType.RECYCLE_LIST, WXRecyclerDomObject.class);
registerDomObject(WXBasicComponentType.VLIST, WXListDomObject.class);
registerDomObject(WXBasicComponentType.HLIST, WXListDomObject.class);
registerDomObject(WXBasicComponentType.SCROLLER, WXScrollerDomObject.class);
registerDomObject(WXBasicComponentType.RECYCLER, WXRecyclerDomObject.class);
registerDomObject(WXBasicComponentType.WATERFALL, WXRecyclerDomObject.class);
} catch (WXException e) {
WXLogUtils.e("[WXSDKEngine] register:", e);
}
batchHelper.flush();
}
在WXSDKEngine初始化的时候就分别注册了这三样东西,Components,Modules,DomObjects。
Components的注册过程
registerComponent(
new SimpleComponentHolder(
WXText.class,
new WXText.Creator()
),
false,
WXBasicComponentType.TEXT
);
registerComponent(
new SimpleComponentHolder(
WXDiv.class,
new WXDiv.Ceator()
),
false,
WXBasicComponentType.CONTAINER,
WXBasicComponentType.DIV,
WXBasicComponentType.HEADER,
WXBasicComponentType.FOOTER
);
registerComponent(
new SimpleComponentHolder(
WXImage.class,
new WXImage.Ceator()
),
false,
WXBasicComponentType.IMAGE,
WXBasicComponentType.IMG
);
registerComponent(
new SimpleComponentHolder(
WXScroller.class,
new WXScroller.Creator()
),
false,
WXBasicComponentType.SCROLLER
);
registerComponent(
new SimpleComponentHolder(
WXSlider.class,
new WXSlider.Creator()
),
true,
WXBasicComponentType.SLIDER,
WXBasicComponentType.CYCLE_SLIDER
);
registerComponent(
new SimpleComponentHolder(
WXSliderNeighbor.class,
new WXSliderNeighbor.Creator()
),
true,
WXBasicComponentType.SLIDER_NEIGHBOR
);
String simpleList = "simplelist";
registerComponent(SimpleListComponent.class,false,simpleList);
registerComponent(WXListComponent.class, false,WXBasicComponentType.LIST,WXBasicComponentType.VLIST,WXBasicComponentType.RECYCLER,WXBasicComponentType.WATERFALL);
registerComponent(WXRecyclerTemplateList.class, false,WXBasicComponentType.RECYCLE_LIST);
registerComponent(HorizontalListComponent.class,false,WXBasicComponentType.HLIST);
registerComponent(WXBasicComponentType.CELL, WXCell.class, true);
registerComponent(WXBasicComponentType.CELL_SLOT, 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);
registerComponent(WXBasicComponentType.A, WXA.class, false);
registerComponent(WXBasicComponentType.EMBED, WXEmbed.class, true);
registerComponent(WXBasicComponentType.WEB, WXWeb.class);
registerComponent(WXBasicComponentType.REFRESH, WXRefresh.class);
registerComponent(WXBasicComponentType.LOADING, WXLoading.class);
registerComponent(WXBasicComponentType.LOADING_INDICATOR, WXLoadingIndicator.class);
registerComponent(WXBasicComponentType.HEADER, WXHeader.class);
这里提供了27种默认的基础组件。
这里有两种签名registerComponent()
进行注册,最后会调用的还是WXComponentRegistry
这个类的方法。(这里我们先知道WXComponentRegistry
是管理所有注册组件的类。)
/**
* 从这方法的签名可以看到。通过这个类来注册Component组件
* @param type String类型的字符串。定义这个组件调用的名称
* @param holder IFComponentHolder的缓存类,作用如它的名字,就是Holder。判断是否赖加载。和存储对应的键值对。一般通过默认SimpleComponentHolder来进行设置
* @param componentInfo 存储component的字典。hashMap
* @return
* @throws WXException
*/
public static boolean registerComponent(final String type, final IFComponentHolder holder, final Map<String, Object> componentInfo) throws WXException {
if (holder == null || TextUtils.isEmpty(type)) {
return false;
}
//在jsThread中执行,确保注册的和调用的是在同一个线程?
//execute task in js thread to make sure register order is same as the order invoke register method.
WXBridgeManager.getInstance()
.post(new Runnable() {
@Override
public void run() {
try {
Map<String, Object> registerInfo = componentInfo;
if (registerInfo == null){
registerInfo = new HashMap<>();
}
//这个ComponentInfo的map中存储了type,methods两个变量。
//这里需要记住的是,还存放这之前的 append 属性。
registerInfo.put("type",type);
registerInfo.put("methods",holder.getMethods());
//分别向native和js进行注册
registerNativeComponent(type, holder);
registerJSComponent(registerInfo);
//注册成功后,进行缓存,加入ComponentInfos的map中。
sComponentInfos.add(registerInfo);
} catch (WXException e) {
WXLogUtils.e("register component error:", e);
}
}
});
return true;
}
可以看到,SDK通过WXComponentRegistry
来分别向native和js进行注册,成功后并对其进行缓存。提供效率。
-
IFComponentHolder
还是先看一下
IFComponentHolder
这个缓存类。再看相应的注册方法。这个类的默认实现是SimpleComponentHolder
public class SimpleComponentHolder implements IFComponentHolder{ public static final String TAG = "SimpleComponentHolder"; private final Class<? extends WXComponent> mClz; //缓存有方法和属性的调用map.Invoker是一个方法的接口。它包括三个方法,分别为调用方法,获取方法参数的类型和确定是否在uiThread中调用方法 private Map<String, Invoker> mPropertyInvokers; private Map<String, Invoker> mMethodInvokers; //这个Creator来创建Component对象 private ComponentCreator mCreator; //使用默认实现的classComponentCreator public SimpleComponentHolder(Class<? extends WXComponent> clz) { this(clz,new ClazzComponentCreator(clz)); } public SimpleComponentHolder(Class<? extends WXComponent> clz,ComponentCreator customCreator) { this.mClz = clz; this.mCreator = customCreator; } //通过Component注解来判断是否是懒加载。如果不是,则注册时调用generate直接生成 @Override public void loadIfNonLazy() { Annotation[] annotations = mClz.getDeclaredAnnotations(); for (Annotation annotation : annotations) { //懒加载是通过Component这个注解 if (annotation instanceof Component){ if(!((Component) annotation).lazyload() && mMethodInvokers == null){ generate(); } return; } } } private synchronized void generate(){ if(WXEnvironment.isApkDebugable()) { WXLogUtils.d(TAG, "Generate Component:" + mClz.getSimpleName()); } Pair<Map<String, Invoker>, Map<String, Invoker>> methodPair = getMethods(mClz); mPropertyInvokers = methodPair.first; mMethodInvokers = methodPair.second; } //通过解析类中的注解和方法,返回对应的属性和方法的invoker map static Pair<Map<String,Invoker>,Map<String,Invoker>> getMethods(Class clz){ //methods是存放属性调用的map.invokers是存放方法调用的map Map<String, Invoker> methods = new HashMap<>(); Map<String, Invoker> mInvokers = new HashMap<>(); Annotation[] annotations; Annotation anno; try { //获取所有的方法 for (Method method : clz.getMethods()) { try { //获取方法的注解 annotations = method.getDeclaredAnnotations(); for (int i = 0, annotationsCount = annotations.length; i < annotationsCount; ++i) { anno = annotations[i]; if(anno == null){ continue; } //如果是被 WXComponentProp 注解,则表示这个是属性,则创建调用的MethodInvoker加入map中。 if (anno instanceof WXComponentProp) { String name = ((WXComponentProp) anno).name(); methods.put(name, new MethodInvoker(method,true)); break; }else if(anno instanceof JSMethod){ //如果是JsMethod注解,则表示这个是一个方法。获取是否别名。同样构造方法放入方法的map中 JSMethod methodAnno = (JSMethod)anno; String name = methodAnno.alias(); if(JSMethod.NOT_SET.equals(name)){ name = method.getName(); } mInvokers.put(name, new MethodInvoker(method,methodAnno.uiThread())); break; } } } catch (ArrayIndexOutOfBoundsException | IncompatibleClassChangeError e) { //ignore: getDeclaredAnnotations may throw this } } }catch (IndexOutOfBoundsException e){ e.printStackTrace(); //ignore: getMethods may throw this } return new Pair<>(methods,mInvokers); } /* 构造WXComponent实例,并且绑定当前的holder */ @Override public synchronized WXComponent createInstance(WXSDKInstance instance, WXDomObject node, WXVContainer parent) throws IllegalAccessException, InvocationTargetException, InstantiationException { WXComponent component = mCreator.createInstance(instance,node,parent); component.bindHolder(this); return component; } /* 获取property invoker,如果是懒加载的话,则开始生成 */ @Override public synchronized Invoker getPropertyInvoker(String name){ if (mPropertyInvokers == null) { generate(); } return mPropertyInvokers.get(name); } /* 获取method invoker,如果是懒加载的话,则开始生成 */ @Override public Invoker getMethodInvoker(String name) { if(mMethodInvokers == null){ generate(); } return mMethodInvokers.get(name); } @Override public String[] getMethods() { if(mMethodInvokers == null){ generate(); } Set<String> keys = mMethodInvokers.keySet(); return keys.toArray(new String[keys.size()]); } }
其中包括 了一个默认实现的Creator
/* 默认实现由一个ClaszzComponentCreator。这个Creator是用来给遵循sdk自带的构造方法的Component提供的默认实现。其中兼容了3-5的构造方法 */ static class ClazzComponentCreator implements ComponentCreator{ private Constructor<? extends WXComponent> mConstructor; private final Class<? extends WXComponent> mCompClz; ClazzComponentCreator(Class<? extends WXComponent> c){ mCompClz = c; } private void loadConstructor(){ Class<? extends WXComponent> c = mCompClz; Constructor<? extends WXComponent> constructor; try { constructor = c.getConstructor(WXSDKInstance.class, WXDomObject.class, WXVContainer.class); } catch (NoSuchMethodException e) { WXLogUtils.d("ClazzComponentCreator","Use deprecated component constructor"); try { //compatible deprecated constructor with 4 args constructor = c.getConstructor(WXSDKInstance.class, WXDomObject.class, WXVContainer.class, boolean.class); } catch (NoSuchMethodException e1) { try { //compatible deprecated constructor with 5 args constructor = c.getConstructor(WXSDKInstance.class, WXDomObject.class, WXVContainer.class,String.class, boolean.class); } catch (NoSuchMethodException e2) { throw new WXRuntimeException("Can't find constructor of component."); } } } mConstructor = constructor; } @Override public WXComponent createInstance(WXSDKInstance instance, WXDomObject node, WXVContainer parent) throws IllegalAccessException, InvocationTargetException, InstantiationException { if(mConstructor == null){ loadConstructor(); } int parameters = mConstructor.getParameterTypes().length; WXComponent component; if(parameters == 3){ //这个构造方法是自动生成instanceId和默认懒加载false.懒加载的flag判断,定义在了自定义注解里面了。 component = mConstructor.newInstance(instance,node,parent); }else if(parameters == 4){ //这个是自动生成instanceId component = mConstructor.newInstance(instance,node,parent,false); }else{ //compatible deprecated constructor //这个是兼容过时的构造器。在构造方法中定义instanceId和默认懒加载flag component = mConstructor.newInstance(instance,node,parent,instance.getInstanceId(),parent.isLazy()); } return component; } }
-
registerNativeComponent
接下来看看,向native是如何注册这个Component的
private static boolean registerNativeComponent(String type, IFComponentHolder holder) throws WXException { try { //判断是否是懒加载,如上面方法分析过,如果不是懒加载,则直接反射生成属性和方法的map holder.loadIfNonLazy(); //放入TypeComponentMap中 sTypeComponentMap.put(type, holder); }catch (ArrayStoreException e){ e.printStackTrace(); //ignore: ArrayStoreException: java.lang.String cannot be stored in an array of type java.util.HashMap$HashMapEntry[] } return true; }
-
registerJsComponent
private static boolean registerJSComponent(Map<String, Object> componentInfo) throws WXException { ArrayList<Map<String, Object>> coms = new ArrayList<>(); coms.add(componentInfo); //通过WxSDKManager注册这个Components WXSDKManager.getInstance().registerComponents(coms); return true; }
通过
WXSDKManager
,最后转发到了WxBridgeManager
中的invokeRegisterComponents
方法private void invokeRegisterComponents(List<Map<String, Object>> components, List<Map<String, Object>> failReceiver) { //错误的列表不能等于源列表 if (components == failReceiver) { throw new RuntimeException("Fail receiver should not use source."); } //如果注册时js还未初始化,则会加入错误的列表中,等待初始化后进行加载 if (!isJSFrameworkInit()) { WXLogUtils.e("[WXBridgeManager] invokeRegisterComponents: framework.js uninitialized."); for (Map<String, Object> comp : components) { failReceiver.add(comp); } return; } if (components == null) { return; } //转成WXJSObject对象数组。这个WXJsObject就是与JsFramework通行的实体。 //WxJsonUtils中通过fastJson将map转成json结构 WXJSObject[] args = {new WXJSObject(WXJSObject.JSON, WXJsonUtils.fromObjectToJSONString(components))}; try { //通过WxBridge进行通行,执行js代码。注入到js框架中。 mWXBridge.execJS("", null, METHOD_REGISTER_COMPONENTS, args); } catch (Throwable e) { WXLogUtils.e("[WXBridgeManager] invokeRegisterComponents ", e); WXExceptionUtils.commitCriticalExceptionRT(null, WXErrorCode.WX_KEY_EXCEPTION_INVOKE_REGISTER_CONTENT_FAILED.getErrorCode(), METHOD_REGISTER_COMPONENTS, WXErrorCode.WX_KEY_EXCEPTION_INVOKE_REGISTER_CONTENT_FAILED.getErrorMsg() + args.toString() + WXLogUtils.getStackTrace(e), null); } }
这样这个Components就在
WXComponentRegistry
中注册完成了。等待调用。
modules的注册过程
和Component的注册过程类似。modules的注册,最后是转到了WXModuleManager
中的registerModule
方法中进行。
/**
*
* @param moduleName module的名称
* @param factory 生成ModuleFactory 其中提供了TypeModuleFactory这个默认实现的工厂类
* @param global 是否全局的module。就会去放入全局的缓存当中
* @return
* @throws WXException
*/
public static boolean registerModule(final String moduleName, final ModuleFactory factory, final boolean global) throws WXException {
if (moduleName == null || factory == null) {
return false;
}
//这里需要注意的module的名字是dom的字样。应该是和内置的dom冲突了?
if (TextUtils.equals(moduleName,WXDomModule.WXDOM)) {
WXLogUtils.e("Cannot registered module with name 'dom'.");
return false;
}
//和Component相同的套路。
//execute task in js thread to make sure register order is same as the order invoke register method.
WXBridgeManager.getInstance()
.post(new Runnable() {
@Override
public void run() {
if (sModuleFactoryMap.containsKey(moduleName)) {
WXLogUtils.w("WXComponentRegistry Duplicate the Module name: " + moduleName);
}
//可以看到这个global flag的作用。就是是否在全局缓存这个module.
if (global) {
//如果是全局缓存的话,则马上构造,并且放入map当中
try {
WXModule wxModule = factory.buildInstance();
wxModule.setModuleName(moduleName);
sGlobalModuleMap.put(moduleName, wxModule);
} catch (Exception e) {
WXLogUtils.e(moduleName + " class must have a default constructor without params. ", e);
}
}
//同样需要向native和js中分别注册module
try {
registerNativeModule(moduleName, factory);
} catch (WXException e) {
WXLogUtils.e("", e);
}
registerJSModule(moduleName, factory);
}
});
return true;
}
-
ModuleFactory
按照Component养成的惯例,还是先来看看默认实现类
TypeModuleFactory
public class TypeModuleFactory<T extends WXModule> implements ModuleFactory<T> { public static final String TAG = "TypeModuleFactory"; //对比Component这里就直接缓存Class类就可以了。应该没有对应的构造方法需要规定 Class<T> mClazz; //缓存这个类的方法 Map<String, Invoker> mMethodMap; public TypeModuleFactory(Class<T> clz) { mClazz = clz; } //生成方法 private void generateMethodMap() { if(WXEnvironment.isApkDebugable()) { WXLogUtils.d(TAG, "extractMethodNames:" + mClazz.getSimpleName()); } HashMap<String, Invoker> methodMap = new HashMap<>(); try { //同样是开始遍历 for (Method method : mClazz.getMethods()) { // iterates all the annotations available in the method for (Annotation anno : method.getDeclaredAnnotations()) { if (anno != null) { //这里是同样是通过JsMethod这个注解来标记方法。同时设置是否uiThread执行的 if(anno instanceof JSMethod) { JSMethod methodAnnotation = (JSMethod) anno; String name = JSMethod.NOT_SET.equals(methodAnnotation.alias())? method.getName():methodAnnotation.alias(); methodMap.put(name, new MethodInvoker(method, methodAnnotation.uiThread())); break; }else if(anno instanceof WXModuleAnno) { //WxModuleAnno这个注解来标注。这个注解等同于JsMethod.现在已经过时了。 WXModuleAnno methodAnnotation = (WXModuleAnno)anno; methodMap.put(method.getName(), new MethodInvoker(method,methodAnnotation.runOnUIThread())); break; } } } } } catch (Throwable e) { WXLogUtils.e("[WXModuleManager] extractMethodNames:", e); } mMethodMap = methodMap; } //构造实例 @Override public T buildInstance() throws IllegalAccessException, InstantiationException { return mClazz.newInstance(); } //如果不是global的方法,则从这儿开始反射得到所有的方法 @Override public String[] getMethods() { if (mMethodMap == null) { generateMethodMap(); } Set<String> keys = mMethodMap.keySet(); return keys.toArray(new String[keys.size()]); } @Override public Invoker getMethodInvoker(String name) { if (mMethodMap == null) { generateMethodMap(); } return mMethodMap.get(name); } }
-
registerNativeModule()
//相同的套路。native注册,就是moduleFactory缓存到全局的map当中 static boolean registerNativeModule(String moduleName, ModuleFactory factory) throws WXException { if (factory == null) { return false; } try { sModuleFactoryMap.put(moduleName, factory); }catch (ArrayStoreException e){ e.printStackTrace(); //ignore: //may throw this exception: //java.lang.String cannot be stored in an array of type java.util.HashMap$HashMapEntry[] } return true; }
-
registerJsModule()
//js注册,同样是通过WxSDKManager转发到WxBridgeManager中进行注册 static boolean registerJSModule(String moduleName, ModuleFactory factory) { Map<String, Object> modules = new HashMap<>(); modules.put(moduleName, factory.getMethods()); WXSDKManager.getInstance().registerModules(modules); return true; }
在
WXBridgeManager
中进行注册private void invokeRegisterModules(Map<String, Object> modules, List<Map<String, Object>> failReceiver) { if (modules == null || !isJSFrameworkInit()) { if (!isJSFrameworkInit()) { WXLogUtils.d("[WXinvokeRegisterModulesBridgeManager] invokeRegisterModules: framework.js uninitialized."); } failReceiver.add(modules); return; } WXJSObject[] args = {new WXJSObject(WXJSObject.JSON, WXJsonUtils.fromObjectToJSONString(modules))}; try { mWXBridge.execJS("", null, METHOD_REGISTER_MODULES, args); } catch (Throwable e) { WXExceptionUtils.commitCriticalExceptionRT(null, WXErrorCode.WX_KEY_EXCEPTION_INVOKE_REGISTER_MODULES.getErrorCode(), "invokeRegisterModules", WXErrorCode.WX_KEY_EXCEPTION_INVOKE_REGISTER_MODULES.getErrorMsg() + " \n " + e.getMessage() + modules.entrySet().toString(), null ); WXLogUtils.e("[WXBridgeManager] invokeRegisterModules:", e); } }
DomObject
的注册过程
-
什么是
WXDomObject
更具注释的介绍,我们知道这类只包含所有所有节点的信息。包括style, attribute and event。它只包含了dom的信息,不包括如何去渲染。
实际上,每一个
Component
持有一个android
view的实例和WXDomObject
的实例。
基本上是相同的套路。最后是在WXDomRegistry
注册。注册的过程更为简单,只是将class换成起来。
public class WXDomRegistry {
public static Class<? extends WXDomObject> mDefaultClass = WXDomObject.class;
private static Map<String, Class<? extends WXDomObject>> sDom = new HashMap<>();
public static boolean registerDomObject(String type, Class<? extends WXDomObject> clazz) throws WXException {
if (clazz == null || TextUtils.isEmpty(type)) {
return false;
}
if (sDom.containsKey(type)) {
if (WXEnvironment.isApkDebugable()) {
throw new WXException("WXDomRegistry had duplicate Dom:" + type);
} else {
WXLogUtils.e("WXDomRegistry had duplicate Dom: " + type);
return false;
}
}
sDom.put(type, clazz);
return true;
}
public static Class<? extends WXDomObject> getDomObjectClass(String type) {
if (TextUtils.isEmpty(type)) {
return mDefaultClass;
}
Class<? extends WXDomObject> clazz = sDom.get(type);
return clazz == null ? mDefaultClass : clazz;
}
}
到这里,就完成了WXSDKEngine
的默认的初始化过程了
3. 初始化自定义组件
通过和上述相同的方式,自定义组件的注册的方式和上面相同。
Weex 是如何让JS调起原生View
上一章节我们分析了WXSDKEngine
是如何初始化的,那么初始化完成之后,Android Native客户端是如何接收到JS的页面并生成View的呢?这一章节我们来分析分析。
进入到IndexActivity
中,在onCreate()
方法中调用super.onCreate()
方法中进行初始化,并且render()
这样的方法。其实是来到了WXSDKInstance
中的对应方法。
WXSDKInstance
初始化
protected void createWeexInstance(){
destoryWeexInstance();
//这两行代码其实没啥用
Rect outRect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect);
//new一个instance 并且监听render时间是否完成
mInstance = new WXSDKInstance(this);
mInstance.registerRenderListener(this);
}
//render view创建成功的回调。
//playground中,将对应点的行为解耦出来成为mWxAnalyzerDelegate,来处理打点分析
@Override
public void onViewCreated(WXSDKInstance wxsdkInstance, View view) {
View wrappedView = null;
if(mWxAnalyzerDelegate != null){
wrappedView = mWxAnalyzerDelegate.onWeexViewCreated(wxsdkInstance,view);
}
if(wrappedView != null){
view = wrappedView;
}
if (mContainer != null) {
mContainer.removeAllViews();
mContainer.addView(view);
}
}
@Override
public void onRefreshSuccess(WXSDKInstance wxsdkInstance, int i, int i1) {
}
//render成功的回调
@Override
@CallSuper
public void onRenderSuccess(WXSDKInstance instance, int width, int height) {
if(mWxAnalyzerDelegate != null){
mWxAnalyzerDelegate.onWeexRenderSuccess(instance);
}
}
//出错的回调。
@Override
@CallSuper
public void onException(WXSDKInstance instance, String errCode, String msg) {
if(mWxAnalyzerDelegate != null){
mWxAnalyzerDelegate.onException(instance,errCode,msg);
}
}
WXSDKInstance
中的构造方法
public WXSDKInstance(Context context) {
//自动生成一个instanceId
mInstanceId = WXSDKManager.getInstance().generateInstanceId();
//初始化这个实例
init(context);
}
public void init(Context context) {
mContext = context;
//通过这个helper来调用原生的方法
mNativeInvokeHelper = new NativeInvokeHelper(mInstanceId);
//生成对应的性能的log
mWXPerformance = new WXPerformance();
mWXPerformance.WXSDKVersion = WXEnvironment.WXSDK_VERSION;
mWXPerformance.JSLibInitTime = WXEnvironment.sJSLibInitTime;
//用户信息打点
mUserTrackAdapter=WXSDKManager.getInstance().getIWXUserTrackAdapter();
}
Render方法
在IndexActivity
的onCreate()
中,需要设置局域网的IP地址才能连接到本地开发服务器。如果没有的话,会通过加载本地Assets目录下的Js文件进行渲染。
if (TextUtils.equals(sCurrentIp, DEFAULT_IP)) {
//如果没有配置,则默认会相同,走到这个位置来加载这个 landing.weex.js文件
renderPage(WXFileUtils.loadAsset("landing.weex.js", this), getIndexUrl());
} else {
renderPageByURL(getIndexUrl());
}
//注册一个广播。如果有地方发送了这个广播,则本地会刷新这个文件
mReloadReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
createWeexInstance();
if (TextUtils.equals(sCurrentIp, DEFAULT_IP)) {
renderPage(WXFileUtils.loadAsset("landing.weex.js", getApplicationContext()), getIndexUrl());
} else {
renderPageByURL(getIndexUrl());
}
mProgressBar.setVisibility(View.VISIBLE);
}
};
//注册一个本地广播。比全局广播更加有效率。只在自己的app内能够使用
LocalBroadcastManager.getInstance(this).registerReceiver(mReloadReceiver, new IntentFilter(WXSDKEngine.JS_FRAMEWORK_RELOAD));
渲染的代码,最终会来到WXSDKInstance
的render
方法中
/**
* Render template asynchronously
* 异步的方式来渲染我们的js模板
* @param pageName, used for performance log. 页面的名称,用在log中
* @param template bundle js js的模板
* @param options os iphone/android/ipad 设置options的参数。也是用来打点的
* weexversion Weex version(like 1.0.0)
* appversion App version(like 1.0.0)
* devid Device id(like Aqh9z8dRJNBhmS9drLG5BKCmXhecHUXIZoXOctKwFebH)
* sysversion Device system version(like 5.4.4、7.0.4, should be used with os)
* sysmodel Device model(like iOS:"MGA82J/A", android:"MI NOTE LTE")
* Time UNIX timestamp, UTC+08:00
* TTID(Optional)
* MarkertId
* Appname(Optional) tm,tb,qa
* Bundleurl(Optional) template url
* @param jsonInitData Initial data for rendering
* @param flag RenderStrategy {@link WXRenderStrategy} 加载的策略有异步的方式APPEND_ASYNC("APPEND_ASYNC")和 仅一次加载 APPEND_ONCE("APPEND_ONCE")两种方式可以选择
*/
public void render(String pageName, String template, Map<String, Object> options, String jsonInitData, WXRenderStrategy flag) {
if(WXEnvironment.isApkDebugable() && WXPerformance.DEFAULT.equals(pageName)){
WXLogUtils.e("WXSDKInstance", "Please set your pageName or your js bundle url !!!!!!!");
if (getUIContext() != null) {
new AlertDialog.Builder(getUIContext())
.setTitle("Error: Missing pageName")
.setMessage("We highly recommend you to set pageName. Call" +
"\nWXSDKInstance#render(String pageName, String template, Map<String, Object> options, String jsonInitData, WXRenderStrategy flag)\n" +
"to fix it.")
.show();
}
return;
}
//继续进入下一个方法
renderInternal(pageName,template,options,jsonInitData,flag);
}
private void renderInternal(String pageName,
String template,
Map<String, Object> options,
String jsonInitData,
WXRenderStrategy flag){
//如果已经渲染成功,则停止
if (mRendered || TextUtils.isEmpty(template)) {
return;
}
//设置performance pageName
mWXPerformance.pageName = (TextUtils.isEmpty(pageName) ? "defaultBundleUrl":pageName);
if (TextUtils.isEmpty(mBundleUrl)) {
mBundleUrl = mWXPerformance.pageName;
}
WXLogUtils.d("WXSDKInstance", "Start render page: " + pageName);
//监控数据监控
if (WXTracing.isAvailable()) {
WXTracing.TraceEvent traceEvent = WXTracing.newEvent("executeBundleJS", mInstanceId, -1);
traceEvent.traceId = mExecJSTraceId;
traceEvent.iid = mInstanceId;
traceEvent.tname = "JSThread";
traceEvent.ph = "B";
traceEvent.submit();
mRenderStartNanos = System.nanoTime();
}
//创建一个render出来view的锚点容器。一个持有改instance弱应用的FrameLayout-->mRenderContainer
ensureRenderArchor();
Map<String, Object> renderOptions = options;
if (renderOptions == null) {
renderOptions = new HashMap<>();
}
//如果是动态模式,并且动态的url不为空,则会去加载url
if (WXEnvironment.sDynamicMode && !TextUtils.isEmpty(WXEnvironment.sDynamicUrl) && renderOptions.get("dynamicMode") == null) {
renderOptions.put("dynamicMode", "true");
renderByUrl(pageName, WXEnvironment.sDynamicUrl, renderOptions, jsonInitData, flag);
return;
}
//数据统计
mWXPerformance.JSTemplateSize = template.length() / 1024f;
mRenderStartTime = System.currentTimeMillis();
mRenderStrategy = flag;
WXSDKManager.getInstance().setCrashInfo(WXEnvironment.WEEX_CURRENT_KEY,pageName);
//这里通过WXSDKManager来创建instance
WXSDKManager.getInstance().createInstance(this, template, renderOptions, jsonInitData);
mRendered = true;
}
WXSDKManager
的createInstance()
中
void createInstance(WXSDKInstance instance, String code, Map<String, Object> options, String jsonInitData) {
//注册到renderManager中
mWXRenderManager.registerInstance(instance);
//通过WXBridgeManager createInstance向jsf通信
mBridgeManager.createInstance(instance.getInstanceId(), code, options, jsonInitData);
//回调
if (mLifeCycleCallbacks != null) {
for (InstanceLifeCycleCallbacks callbacks : mLifeCycleCallbacks) {
callbacks.onInstanceCreated(instance.getInstanceId());
}
}
}
下面兵分两路,分别来看看都做了什么事情
WXRenderManager
WXRenderManager
是一个render操作的管理类,并且是线程安全的。主要用来管理RenderActionContextImpl
public class WXRenderManager {
//线程安全的map.存放RenderActionContextImpl
private ConcurrentHashMap<String, RenderActionContextImpl> mRegistries;
//其实就是一个ui线程的handler
private WXRenderHandler mWXRenderHandler;
public WXRenderManager() {
mRegistries = new ConcurrentHashMap<>();
mWXRenderHandler = new WXRenderHandler();
}
public RenderActionContext getRenderContext(String instanceId) {
return mRegistries.get(instanceId);
}
//上面来注册,就是创建个RenderActionContextImpl实例,放到map当中。
//这个RenderActionContextImpl就如它的名字一样,是instance内保存Component信息的类。
//通过这个类,来完成Component的layout
public void registerInstance(WXSDKInstance instance) {
mRegistries.put(instance.getInstanceId(), new RenderActionContextImpl(instance));
}
//先省略若干方法
}
/**
* 再来看一下RenderActionContextImpl到底是什么
* 这个类就是用来renderingView的。而且和WXDomStatement很相似
*/
class RenderActionContextImpl implements RenderActionContext {
private Map<String, WXComponent> mRegistry;
private WXSDKInstance mWXSDKInstance;
/**
* The container for weex root view.
*/
public RenderActionContextImpl(WXSDKInstance instance) {
mWXSDKInstance = instance;
mRegistry = new HashMap<>();
}
/**
* @see com.taobao.weex.dom.WXDomStatement#destroy()
*/
public void destroy() {
mWXSDKInstance = null;
mRegistry.clear();
}
public WXSDKInstance getWXSDKInstance() {
return mWXSDKInstance;
}
/**
* set layout information of View
* 设置布局的信息。
* ref 是 改节点的标识
* WXDomObject
*/
void setLayout(String ref, WXDomObject domObject) {
WXComponent component = mRegistry.get(ref);
if (component == null) {
return;
}
component.setLayout(domObject);
}
/**
* set extra information of View
*/
void setExtra(String ref, Object extra) {
WXComponent component = mRegistry.get(ref);
if (component == null) {
return;
}
component.updateExtra(extra);
}
@Override
public WXSDKInstance getInstance() {
return mWXSDKInstance;
}
@Override
public WXComponent getComponent(String ref) {
return mRegistry.get(ref);
}
//注册
public void registerComponent(String ref, WXComponent comp) {
mRegistry.put(ref,comp);
}
//清除
public WXComponent unregisterComponent(String ref) {
return mRegistry.remove(ref);
}
}
WXBridgeManager
/**
* Create instance.
*/
public void createInstance(final String instanceId, final String template,
final Map<String, Object> options, final String data) {
final WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId);
if (instance == null) {
WXLogUtils.e("WXBridgeManager", "createInstance failed, SDKInstance is not exist");
return;
}
//如果任一个为空,则直接报错
if (TextUtils.isEmpty(instanceId) || TextUtils.isEmpty(template) || mJSHandler == null) {
instance.onRenderError(
WXRenderErrorCode.DegradPassivityCode.WX_DEGRAD_ERR_INSTANCE_CREATE_FAILED.getDegradErrorCode(),
WXRenderErrorCode.DegradPassivityCode.WX_DEGRAD_ERR_INSTANCE_CREATE_FAILED.getDegradErrorMsg() +
" instanceId==" + instanceId + " template ==" + template + " mJSHandler== " + mJSHandler.toString()
);
return;
}
//报错检查
if (!isJSFrameworkInit() && reInitCount == 1 && !WXEnvironment.sDebugServerConnectable) {
instance.onRenderError(
WXRenderErrorCode.DegradPassivityCode.WX_DEGRAD_ERR_INSTANCE_CREATE_FAILED.getDegradErrorCode(),
WXRenderErrorCode.DegradPassivityCode.WX_DEGRAD_ERR_INSTANCE_CREATE_FAILED.getDegradErrorMsg() +
" isJSFrameworkInit==" + isJSFrameworkInit() + " reInitCount == 1" );
post(new Runnable() {
@Override
public void run() {
initFramework("");
}
}, instanceId);
return;
}
//创建一个ModuleManager。由上一章知道,moduleManager是缓存和管理module的类
WXModuleManager.createDomModule(instance);
//通过JSThread发送
post(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
//执行在JSThread中真实的方法
invokeCreateInstance(instance, template, options, data);
final long totalTime = System.currentTimeMillis() - start;
//回调到ui线程的创建结束的方法
WXSDKManager.getInstance().postOnUiThread(new Runnable() {
@Override
public void run() {
//这里的回调,在playground app中,只是为了打点
instance.createInstanceFinished(totalTime);
}
}, 0);
}
}, instanceId);
}
private void invokeCreateInstance(@NonNull WXSDKInstance instance, String template,
Map<String, Object> options, String data) {
//如果未初始化jsf,则报错
initFramework("");
if (mMock) {
mock(instance.getInstanceId());
} else {
if (!isJSFrameworkInit()) {
String err = "[WXBridgeManager] invokeCreateInstance: framework.js uninitialized.";
instance.onRenderError(
WXRenderErrorCode.DegradPassivityCode.WX_DEGRAD_ERR_INSTANCE_CREATE_FAILED.getDegradErrorCode(),
WXRenderErrorCode.DegradPassivityCode.WX_DEGRAD_ERR_INSTANCE_CREATE_FAILED.getDegradErrorMsg()
);
WXLogUtils.e(err);
return;
}
try {
if (WXEnvironment.isOpenDebugLog()) {
WXLogUtils.d("createInstance >>>> instanceId:" + instance.getInstanceId()
+ ", options:"
+ WXJsonUtils.fromObjectToJSONString(options)
+ ", data:" + data);
}
//创建通信的WXJSObject对象数组进行同时
WXJSObject instanceIdObj = new WXJSObject(WXJSObject.String,
instance.getInstanceId());
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};
instance.setTemplate(template);
//将上面的命令,转成了Js的function,调用执行JS的命令,进行创建!
invokeExecJS(instance.getInstanceId(), null, METHOD_CREATE_INSTANCE, args, false);
} catch (Throwable e) {
String err = "[WXBridgeManager] invokeCreateInstance " + e.getCause()
+ instance.getTemplateInfo();
instance.onRenderError(
WXRenderErrorCode.DegradPassivityCode.WX_DEGRAD_ERR_INSTANCE_CREATE_FAILED.getDegradErrorCode(),
WXRenderErrorCode.DegradPassivityCode.WX_DEGRAD_ERR_INSTANCE_CREATE_FAILED.getDegradErrorMsg() + err);
WXLogUtils.e(err);
}
}
}
//这个方法其实就是为了输出一个log.然后同执行execJs
public void invokeExecJS(String instanceId, String namespace, String function,
WXJSObject[] args, boolean logTaskDetail) {
if (WXEnvironment.isOpenDebugLog()) {
mLodBuilder.append("callJS >>>> instanceId:").append(instanceId)
.append("function:").append(function);
if (logTaskDetail) {
mLodBuilder.append(" tasks:").append(argsToJSON(args));
}
WXLogUtils.d(mLodBuilder.substring(0));
mLodBuilder.setLength(0);
}
//Execute JavaScript function
mWXBridge.execJS(instanceId, namespace, function, args);
}
这里可以看到,最后是用我们的参数,调用了mWXBridge.execJS(instanceId, namespace, function, args);
native方法传递给JSF
callNative(...)
execJs
native方法,会反射调用callNative(...)
方法。通过这样的方式来回调native方法。
/**
* JavaScript uses this methods to call Android code
*
* @param instanceId
* @param tasks
* @param callback
*/
public int callNative(String instanceId, byte [] tasks, String callback) {
try {
return callNative(instanceId,(JSONArray)WXJsonUtils.parseWson(tasks),callback);
} catch (Throwable e) {
//catch everything during call native.
// if(WXEnvironment.isApkDebugable()){
WXLogUtils.e(TAG,"callNative throw exception:"+e.getMessage());
// }
return 0;
}
}
public int callNative(String instanceId, JSONArray tasks, String callback) {
long start = System.currentTimeMillis();
WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId);
if(instance != null) {
instance.firstScreenCreateInstanceTime(start);
}
int errorCode = IWXBridge.INSTANCE_RENDERING;
try {
//再到WXBridgeManager中进行调用
errorCode = WXBridgeManager.getInstance().callNative(instanceId, tasks, callback);
}catch (Throwable e){
//catch everything during call native.
// if(WXEnvironment.isApkDebugable()){
WXLogUtils.e(TAG,"callNative throw exception:"+e.getMessage());
// }
}
if(instance != null) {
instance.callNativeTime(System.currentTimeMillis() - start);
}
if(WXEnvironment.isApkDebugable()){
if(errorCode == IWXBridge.DESTROY_INSTANCE){
WXLogUtils.w("destroyInstance :"+instanceId+" JSF must stop callNative");
}
}
return errorCode;
}
接下来会进行一些native的布局操作,在这编文章内就暂时不深究了。
总结
最后,先看一下注册过程的类结构图
虽然看了很多源码,但是形成的印象还是很笼统。
上面这个例子中,JSFramework的工作原理基本就展现出来了。大体流程如下图:
接下来详细总结一下JSFramework在整个Native端是如何工作的。
- Weex本身是由JSFramework和Native Render、和虚拟的Dom,这三大部分组成。本文覆盖的范围还主要在jsf的初始化和native render调用的开始。
- 首先,JSFramework是全局单例,但是WXSDKInstance是每个页面自己的实例。
- Weex内,我们看到的线程就存在了JSThread、UiThread的两种线程相互工作
- 渲染的核心最根本的在于native通过execJs的native方法,调用js的function进行工作。然后再通过callNative的方法进行回调native对应的代码
更多
本篇文章只大概讲述了Weex是如何在Android Native端跑起来的原理,但是关于Weex其实还有很多很多疑问没有弄清。
Weex内的线程模型,线程内相互是如何通信的?
比如说在Vue.js页面更改了一个页面元素,是怎么能让Native页面及时的变更?
Weex的页面是怎么通过FlexBox算法进行渲染的?
前端页面是如何打包成JS bundle的?
.we和.vue文件是怎么通过DSL被翻译的?
如何利用JS的Runtime写一些强大的JSService?
webpackBootstrap和weex-loader是如何生成最终的JS代码的,中间有哪些优化?……