0%

滴滴Hummer原理

使用(HelloWorld)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RootView extends View {
constructor() {
super();

this.style = {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FFFFFF',
}

let textView = new Text();
textView.text = "~ Hello Hummer ~";
textView.style = {
fontSize: 20,
color: '#000000',
}

this.appendChild(textView);
}
}

Hummer.render(new RootView());

Android目录结构

  • hermes-debugger hermes引擎调试代码
  • hummer 对外报漏的sdk依赖
  • hummer-annotation apt 注解
  • hummer-compiler apt processor,主要是根据代码生成js,js执行过程中反调用java代码
  • hummer-component js 映射的 端侧组件
  • hummer-core js引擎
  • hummer-demo-app demo
  • hummer-dev-tools 调试
  • hummer-plugin-interface 调试
  • hummer-sdk hummer除core之外的核心代码

JS引擎

  • JavaScriptCore
  • QuickJS
  • hermes facebook提供的js引擎,目前没有了解过
  • v8 代码中有提到,但是c代码未集成

编译

tip:COMPILE_JS_ENGINE=NAPI 替换为QuickJS之后,打包成功但无法启动

  • 编译会启动一个hummer服务,用于拉取js代码,这里直接使用assets的helloworld.js

  • 下载N-Api

  • apt

    • compoent ,生成组件的createInstance和invoke,用于js回调创建组件和设置属性
    • register,生成组件组册,和组件的js代码,以及生成用于运行js的代码

compoent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Image$$Invoker extends BaseInvoker<Image> {
@Override
public String getName() {
return "Image";
}

@Override
protected Image createInstance(JSValue jsValue, Object[] params) {
String param0 = params.length > 0 && params[0] != null ? String.valueOf(params[0]) : null;
return new Image(mHummerContext, jsValue, param0);
}

@Override
protected Object invoke(Image instance, String methodName, Object[] params) {
Object jsRet = null;
switch (methodName) {
case "setSrc": {
String param0 = params.length > 0 && params[0] != null ? String.valueOf(params[0]) : null;
instance.setSrc(param0);
} break;
case "setGifSrc": {
String param0 = params.length > 0 && params[0] != null ? String.valueOf(params[0]) : null;
instance.setGifSrc(param0);
} break;
case "setGifRepeatCount": {
int param0 = params.length > 0 && params[0] != null ? ((Number) params[0]).intValue() : 0;
instance.setGifRepeatCount(param0);
} break;
case "load": {
Object param0 = params.length > 0 ? ((params[0] instanceof String && (HMGsonUtil.isJsonObject((String) params[0]) || HMGsonUtil.isJsonArray((String) params[0]))) ? HMGsonUtil.fromJson((String) params[0], new TypeToken<Object>(){}.getType()) : (Object) params[0]) : null;
JSCallback param1 = params.length > 1 && params[1] != null ? (JSCallback) params[1] : null;
instance.load(param0,param1);
} break;
}
return jsRet;
}
}

register

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class HummerRegister$$hummer_component {
public static final String JS_CODE = "var Image = class Image extends Base {\n"
+ " constructor(...args) {\n"
+ " super('Image', ...args);\n"
+ " }\n"
+ " set src(arg) {\n"
+ " this._src = arg;\n"
+ " arg = transSingleArg(arg);\n"
+ " invoke('Image', this.objID, 'setSrc', arg);\n"
+ " }\n"
+ " get src() {\n"
+ " return this._src;\n"
+ " }\n"
+ " set gifSrc(arg) {\n"
+ " this._gifSrc = arg;\n"
+ " arg = transSingleArg(arg);\n"
+ " invoke('Image', this.objID, 'setGifSrc', arg);\n"
+ " }\n"
+ " get gifSrc() {\n"
+ " return this._gifSrc;\n"
+ " }\n"
+ " set gifRepeatCount(arg) {\n"
+ " this._gifRepeatCount = arg;\n"
+ " arg = transSingleArg(arg);\n"
+ " invoke('Image', this.objID, 'setGifRepeatCount', arg);\n"
+ " }\n"
+ " get gifRepeatCount() {\n"
+ " return this._gifRepeatCount;\n"
+ " }\n"
+ " load(...args) {\n"
+ " let stash = args;\n"
+ " args = transArgs(...args);\n"
+ " invoke('Image', this.objID, 'load', ...args);\n"
+ " }\n"
+ "}\n"
+ "__GLOBAL__.Image = Image;\n";

public static void init(HummerContext hummerContext) {
hummerContext.registerInvoker(new Image$$Invoker());
hummerContext.evaluateJavaScript(JS_CODE, "hummer_component.js");
}
}

运行

流程图

  • NAPIContext 和js交互
  • NAPIHummerContext android上下文包装,包含了js操作,容器相关
  • HummerRender 用于js的加载

javascript注入

  • HummerDefinition.js
  • HummerRegister$$hummer_sdk 中js注入
  • HummerRegister$$hummer_component 注入
  • 自定义属性和方法注入
  • 业务js注入

HelloWorld分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RootView extends View {
constructor() {
super();

this.style = {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FFFFFF',
}

let textView = new Text();
textView.text = "~ Hello Hummer ~";
textView.style = {
fontSize: 20,
color: '#000000',
}

this.appendChild(textView);
}
}

Hummer.render(new RootView());

首先是new RootView ,RootView的继承关系为RootView -> View -> Base

View.js (此代码为自动生成)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class View extends Base {
constructor(...args) {
super('View', ...args);
}
appendChild(...args) {
let stash = args;
args = transArgs(...args);
invoke('View', this.objID, 'appendChild', ...args);
}

removeChild(...args) {
let stash = args;
args = transArgs(...args);
invoke('View', this.objID, 'removeChild', ...args);
}
}

__GLOBAL__.View = View;

Base.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

class Base {
constructor(className, ...args) {
this.className = className;
this.objID = idGenerator();
this.recycler = new Recycler(this.objID);

let params = transArgs(...args);
invoke(this.className, this.objID, "constructor", this, ...params);

// 已弃用
this.initialize(...args);

// 此方法只用于调试,为了统计组件树和函数调用树
if (__IS_DEBUG__) {
invoke(this.className, this.objID, "constructor_end", this);
}
}
...
}
__GLOBAL__.Base = Base;

RootView 会调用 Base中的构造方法,className= View,最终调用invoke方法回调端上。

1
2
3
4
5
6
7
8
9
10
11
12
public class View$$Invoker extends BaseInvoker<View> {
@Override
public String getName() {
return "View";
}

@Override
protected View createInstance(JSValue jsValue, Object[] params) {
String param0 = params.length > 0 && params[0] != null ? String.valueOf(params[0]) : null;
return new View(mHummerContext, jsValue, param0);
}
}

invoke方法会回调View$$Invoker的createInstance方法创建一个View,此View为自定义View

1
2
3
4
5
6
7
8
9
10
@Component("View")
public class View extends HummerLayoutExtendView {

private static final String OVERFLOW_VISIBLE = "visible";
private static final String OVERFLOW_HIDDEN = "hidden";

public View(HummerContext context, JSValue jsValue, String viewID) {
super(context, jsValue, viewID);
}
}

RootView中创建的Text原理也类似,最终会调用端上的Text,接下来我们分析Hummer的render方法

HummerDefinition.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Hummer = {
setBasicWidth: (width) => {
invoke("Hummer", 0, "setBasicWidth", width);
},
render: (view) => {
invoke("Hummer", 0, "render", view.objID);
},
loadScript: (script) => {
return invoke("Hummer", 0, "loadScript", script);
},
loadScriptWithUrl: (url, callback) => {
invoke("Hummer", 0, "loadScriptWithUrl", url, callback);
},
postException: (err) => {
err = transSingleArg(err);
invoke("Hummer", 0, "postException", err);
},
notifyCenter: NotifyCenter,
}
__GLOBAL__.Hummer = Hummer;

hummer也是回调到了端上的render方法,这里直接给出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class HummerInvoker extends BaseInvoker<HMBase> {

@Override
public String getName() {
return "Hummer";
}

@Override
protected HMBase createInstance(JSValue jsValue, Object... params) {
return null;
}

@Override
protected Object invoke(HMBase instance, String methodName, Object... params) {
Object jsRet = null;
switch (methodName) {
case "setBasicWidth":
RemUtil.BASE_WIDTH = ((Number) params[0]).floatValue();
break;
case "render":
long objId = ((Number) params[0]).longValue();
HMBase v = mInstanceManager.get(objId);
mHummerContext.render(v);
break;
}
return jsRet;
}
}

mHummerContext为上面提到的NAPIHummerContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HummerContext extends ContextWrapper {
public void render(HMBase base) {
if (base != null) {
mJSRootView = base;
mJsPage = base.getJSValue();
mJsPage.protect();
create();

if (mContent != null) {
mContent.removeAllViews();
mContent.addView(base);
}

startIfNeed();
resumeIfNeed();
}
}
}

最终将base也就是上面提到的RootView添加到了端上容器中