Weex是如何在Android客户端上跑起来的
u=633704361,1790938583&fm=11&gp=0.jpg
目录
- 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这个缓存类。再看相应的注册方法。这个类的默认实现是SimpleComponentHolderpublic 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; }
-
registerJsComponentprivate 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养成的惯例,还是先来看看默认实现类
TypeModuleFactorypublic 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持有一个androidview的实例和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(...)
execJsnative方法,会反射调用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的布局操作,在这编文章内就暂时不深究了。
总结
最后,先看一下注册过程的类结构图
注册过程中的类图.jpg
虽然看了很多源码,但是形成的印象还是很笼统。
上面这个例子中,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代码的,中间有哪些优化?……