Android 进阶
一、Hummer SDK 初始化配置
可以在 Hummer SDK 初始化时,通过 HummerConfig 配置各种自定义能力,这些配置项都是可选的。
HummerConfig 设置
HummerConfig config = new HummerConfig.Builder()
// 自定义namespace(用于业务线隔离,需和Hummer容器中的namespace配合使用,可选)
.setNamespace("test_namespace")
// JS日志回调(可选)
.setJSLogger((level, msg) -> {})
// JS异常回调(可选)
.setExceptionCallback(e -> {})
// RTL支持(可选)
.setSupportRTL(false)
// 字节码支持(可选)
.setSupportBytecode(true)
// 自定义上报SDK内部埋点和性能统计数据(可选)
.setTrackerAdapter(new EmptyTrackerAdapter())
// 自定义字体库(可选)
.setFontAdapter(new TestFontAdapter())
// 自定义路由(可在这里指定自定义Hummer容器,可选)
.setNavigatorAdapter(new DefaultNavigatorAdapter(new DefaultIntentCreator()))
// 自定义图片库(可选)
.setImageLoaderAdapter(new DefaultImageLoaderAdapter())
// 自定义网络库(可选)
.setHttpAdapter(new DefaultHttpAdapter())
// 自定义持久化存储(可选)
.setStorageAdapter(new DefaultStorageAdapter())
// 自定义Hummer.loadScriptWithUrl()时加载脚本的方式(可选)
.setScriptLoaderAdapter(new DefaultScriptLoaderAdapter())
// 构造HummerConfig
.builder();
Hummer.init(this, config);
二、Hummer 容器高级特性
Hummer 容器分为单页面容器和嵌入式页面容器
1. 单页面容器
/**
* 继承至 HummerActivity
*/
public class HummerSinglePageActivity extends HummerActivity {
/**
* 设置该页面对应的namespace,用于做业务隔离(需和HummerConfig中的namespace配合使用,可选)
*/
@Override
protected String getNamespace() {
return "test_namespace";
}
/**
* 渲染方式一:通过URL来源构造PageInfo信息,来渲染JS页面(推荐)
*/
@Override
protected NavPage getPageInfo() {
// URL来源一:通过Intent传入
return super.getPageInfo();
// URL来源二:assets文件路径
// return new NavPage("HelloWorld.js");
// URL来源三:手机设备文件路径
// return new NavPage("/sdcard/HelloWorld.js");
// URL来源四:网络url
// return new NavPage("http://x.x.x.x:8000/index.js");
}
/**
* 渲染方式二:直接通过JS来源渲染JS页面(此方式会丢失页面的PageInfo,不推荐)
*/
@Override
protected void renderHummer() {
// JS来源一:通过Intent传入url
super.renderHummer();
// JS来源二:assets文件
// hmRender.renderWithAssets("HelloWorld.js");
// JS来源三:手机设备文件
// hmRender.renderWithFile("/sdcard/HelloWorld.js");
// JS来源四:网络url
// hmRender.renderWithUrl("http://x.x.x.x:8000/index.js");
// JS来源五:JS文本内容
// String jsContent = "let a = 1;";
// hmRender.render(jsContent);
}
/**
* 页面渲染成功的回调
*/
@Override
protected void onPageRenderSucceed(@NonNull HummerContext hmContext, @NonNull JSValue jsPage) {}
/**
* 页面渲染失败的回调
*/
@Override
protected void onPageRenderFailed(@NonNull Exception e) {}
}
2. 嵌入式页面容器
/**
* 继承至 AppCompatActivity
*/
public class HummerEmbeddedPageActivity extends AppCompatActivity {
// Hummer渲染器1
private HummerRender hmRender1;
// Hummer渲染器2
private HummerRender hmRender2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hummer_embedded_page);
renderLayout1();
renderLayout2();
}
/**
* 渲染第一个嵌入式Hummer容器
*/
private void renderLayout1() {
HummerLayout layout1 = findViewById(R.id.layout_hummer_1);
// 构造函数中的namespace需和HummerConfig中的namespace配合使用,可不传
hmRender1 = new HummerRender(layout1, "test_namespace");
// 设置页面加载回调(可选)
hmRender1.setRenderCallback(new HummerRender.HummerRenderCallback() {
@Override
public void onSucceed(HummerContext hmContext, JSValue jsPage) {}
@Override
public void onFailed(Exception e) {}
});
// 方式一:通过assets文件渲染JS页面
hmRender1.renderWithAssets("HelloWorld.js");
// 方式二:通过手机设备文件渲染JS页面
// hmRender1.renderWithFile("/sdcard/HelloWorld.js");
// 方式三:通过url渲染JS页面
// hmRender1.renderWithUrl("http://x.x.x.x:8000/index.js");
// 方式四:通过JS内容渲染JS页面
// String jsContent = "let a = 1;";
// hmRender1.render(jsContent);
}
/**
* 渲染第二个嵌入式Hummer容器
*/
private void renderLayout2() {
HummerLayout layout2 = findViewById(R.id.layout_hummer_2);
// 构造函数中的namespace需和HummerConfig中的namespace配合使用,可不传
hmRender2 = new HummerRender(layout2, "test_namespace");
// 设置页面加载回调(可选)
hmRender2.setRenderCallback(new HummerRender.HummerRenderCallback() {
@Override
public void onSucceed(HummerContext hmContext, JSValue jsPage) {}
@Override
public void onFailed(Exception e) {}
});
// 方式一:通过assets文件渲染JS页面
hmRender2.renderWithAssets("HelloWorld.js");
// 方式二:通过手机设备文件渲染JS页面
// hmRender2.renderWithFile("/sdcard/HelloWorld.js");
// 方式三:通过url渲染JS页面
// hmRender2.renderWithUrl("http://x.x.x.x:8001/index.js");
// 方式四:通过JS内容渲染JS页面
// String jsContent = "let a = 1;";
// hmRender2.render(jsContent);
}
@Override
protected void onResume() {
super.onResume();
if (hmRender1 != null) {
hmRender1.onResume();
}
if (hmRender2 != null) {
hmRender2.onResume();
}
}
@Override
protected void onPause() {
super.onPause();
if (hmRender1 != null) {
hmRender1.onPause();
}
if (hmRender2 != null) {
hmRender2.onPause();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (hmRender1 != null) {
hmRender1.onDestroy();
}
if (hmRender2 != null) {
hmRender2.onDestroy();
}
}
@Override
public void onBackPressed() {
if (hmRender1 != null && hmRender1.onBack()) {
return;
}
if (hmRender2 != null && hmRender2.onBack()) {
return;
}
super.onBackPressed();
}
}
三、实现自定义导出类
1. 在 Module 的 build.gradle 中加上 ModuleName 配置
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [HUMMER_MODULE_NAME: project.getName()]
}
}
}
}
2. 在 Module 的 build.gradle 中添加编译时注解依赖
Java项目集成
dependencies {
annotationProcessor 'io.github.didi.hummer:hummer-compiler:0.2.17'
}
Kotlin项目集成
apply plugin: 'kotlin-kapt'
dependencies {
kapt 'io.github.didi.hummer:hummer-compiler:0.2.17'
}
3. 编写自定义导出类
自定义导出组件分为 静态功能组件、普通功能组件 和 视图组件 三大类,主要会用到以下几个注解:
// 类注解,用于导出该类,导出类的名字由注解中的 [ClassName] 指定
@Component("[ClassName]")
// 变量注解,用于导出该属性,导出属性的名字由注解中的 [PropertyName] 指定
@JsProperty("[PropertyName]")
// 变量注解,用于导出该样式(此注解目前尚未启用,需要手动处理调用)
@JsAttribute("[AttrName]")
// 方法注解,用于导出该方法,导出方法的名字由注解中的 [FuncName] 指定
@JsMethod("[FuncName]")
静态功能组件
@Component("TestExportStaticModel")
public class TestExportStaticModel {
// 导出静态变量(可以有get/set方法,也可以没有)
@JsProperty("msg")
private static String msg;
public static String getMsg() {
return msg;
}
public static void setMsg(String msg) {
TestExportStaticModel.msg = msg;
}
// 导出静态方法
@JsMethod("log")
public static void log(String msg) {
Log.v("zdf", "log: " + msg);
}
}
普通功能组件
@Component("TestExportModel")
public class TestExportModel {
/**
* 构造函数(可选)
*
* 参数支持基本类型、Map、List、自定义对象、JSCallback 这几种类型。
* 其中前两个参数比较特殊,用于接收保留参数:
* 第一个参数用于接收 Context 或 HummerContext,其中 Context 是页面上下文,HummerContext 是 Hummer 执行上下文,可以不接收。
* 第二个参数用于接收 本对象对应的 JSValue,可以不接收。
*
*/
public TestExportModel(Context context, JSValue jsValue, int i, long l, float f, boolean b, Map<String, Object> map, List<Object> list, Model model, JSCallback callback) {
Log.v("zdf", "TestExportModel, context = " + context + ", jsValue = " + jsValue);
Log.v("zdf", "TestExportModel, i = " + i + ", l = " + l + ", f = " + f + ", b = " + b + ", map = " + map + ", list = " + list + ", model = " + model);
callback.call(11, 22, 12.34, true);
}
// 导出基本类型的属性(可以有get/set方法,也可以没有)
@JsProperty("floatValue")
public float floatValue;
public void setFloatValue(float value) {
floatValue = value;
}
public float getFloatValue() {
return floatValue;
}
// 导出Map类型的属性(对应JS侧的Object)
@JsProperty("mapValue")
private Map<String, Object> mapValue;
public void setMapValue(Map<String, Object> value) {
mapValue = value;
}
public Map<String, Object> getMapValue() {
return mapValue;
}
// 导出List类型的属性(对应JS侧的Array)
@JsProperty("listValue")
private List<String> listValue;
public void setListValue(List<String> value) {
listValue = value;
}
public List<String> getListValue() {
return listValue;
}
// 导出自定义类的属性(对应JS侧的Object)
@JsProperty("modelValue")
private Model modelValue;
public void setModelValue(Model value) {
modelValue = value;
}
public Model getModelValue() {
return modelValue;
}
// 导出方法(参数支持同构造方法,第一个参数也是保留参数,用于接收 Context 或 HummerContext,可以不接收)
@JsMethod("doFunc")
public String doFunc(HummerContext context, int i, long l, float f, boolean b, Map<String, Object> map, List<Object> list, Model model, JSCallback callback) {
return "[doFunc] i = " + i + ", l = " + l + ", f = " + f + ", b = " + b + ", map = " + map + ", list = " + list + ", model = " + model + ", callback.call = " + callback.call();
}
public static class Model {
public int v1;
public float v2;
public String v3;
@Override
public String toString() {
return "Model{" +
"v1=" + v1 +
", v2=" + v2 +
", v3='" + v3 + '\'' +
'}';
}
}
}
视图组件
视图组件和普通功能组件基本类似,有以下几点区别:
- 视图组件需要继承至
HMBase
类,在泛型中设置真实的视图类型; - 实现
createViewInstance
类,返回真真实视图对象; - 视图组件比普通功能组件多一个注解:
JsAttribute
,用于导出设置在style中的样式,不过目前该注解还未被启用,需要通过重写setStyle
方法来手动调用设置样式的方法;
其他更多高级用法,可以参考 Hummer SDK 中的各种内置组件的实现。
@Component("TestView")
public class TestView extends HMBase<View> {
public TestView(HummerContext context, JSValue jsValue, String viewID) {
super(context, jsValue, viewID);
}
@Override
protected View createViewInstance(Context context) {
return new View(context);
}
@JsProperty("text")
private String text;
public void setText(String text) {
this.text = text;
}
@JsAttribute("color")
public void setColor(int color) {
}
@JsMethod("layout")
public void layout() {
getView().requestLayout();
}
@Override
public boolean setStyle(String key, Object value) {
switch (key) {
case HummerStyleUtils.Hummer.COLOR:
setColor((int) value);
break;
default:
return false;
}
return true;
}
}
保留参数
- 所有导出组件的 构造函数 中的前两个参数为保留参数。
- 第一个参数用于接收
Context
或HummerContext
,其中Context
是页面上下文,HummerContext
是 Hummer 执行上下文。 - 第二个参数用于接收 本对象对应的 JSValue。
- 第一个参数用于接收
- 所有导出组件的 导出方法 中的第一个参数是保留参数。
- 用于接收
Context
或HummerContext
,其中Context
是页面上下文,HummerContext
是 Hummer 执行上下文。 示例:
- 用于接收
@Component("TestExportModel")
public class TestExportModel {
public TestExportModel(Context context) {}
public TestExportModel(HummerContext context) {}
public TestExportModel(Context context, JSValue jsValue) {}
public TestExportModel(HummerContext context, JSValue jsValue) {}
public TestExportModel(JSValue jsValue) {}
@JsMethod("doFunc")
public String doFunc(Context context) {}
@JsMethod("doFunc")
public String doFunc(HummerContext context) {}
组件生命周期
所有导出组件,都有两个生命周期接口类可以实现,分别对应原生页面的各个生命周期方法。
- ILifeCycle (精简版生命周期)
@Component("TestExportModel")
public class TestExportModel implements ILifeCycle {
@Override
public void onCreate() {}
@Override
public void onDestroy() {}
}
- IFullLifeCycle (完整版生命周期)
@Component("TestExportModel")
public class TestExportModel implements IFullLifeCycle {
@Override
public void onCreate() {}
@Override
public void onDestroy() {}
@Override
public void onStart() {}
@Override
public void onResume() {}
@Override
public void onPause() {}
@Override
public void onStop() {}
}
4. 注册组件
在 Hummer 页面初始化时调用注册组件的方法。其中 hummer_demo_app
是导出组件所在的当前 module 名字,需要自行修改。
@Override
protected void initHummerRegister(HummerContext context) {
HummerRegister$$hummer_demo_app.init(context);
}
四、Bridge 用法
1. Native 向 JS 静态类注册方法
HummerContext hmContext = hmRender.getHummerContext();
hmContext.registerJSFunction("Test.nativeFunc", new ICallback() {
@Override
public Object call(Object... params) {
return "result";
}
});
INFO
JS中用法:Test.nativeFunc(111, 222);
2. Native 向 JS 对象注册方法(只能在渲染页面结束后才能使用)
HummerContext hmContext = hmRender.getHummerContext();
JSValue jsPage = hmContext.getJsPage();
if (jsPage != null) {
hmContext.registerJSFunction(jsPage, "nativeFunc", new ICallback() {
@Override
public Object call(Object... params) {
return "result";
}
});
}
INFO
JS 中用法:在 RootView 中调用 this.nativeFunc(111, 222);
3. Native 调用 JS 的全局方法(只能在渲染页面结束后才能使用)
HummerContext hmContext = hmRender.getHummerContext();
hmContext.getJsContext().callFunction("onTest", 111, 222.22, true, "ttt");
INFO
JS 中定义全局方法:function onTest(a, b, c, d) {};
4. Native 调用 JS 的 Hummer 域下的方法(只能在渲染页面结束后才能使用)
HummerContext hmContext = hmRender.getHummerContext();
hmContext.getJsContext().callFunction("Hummer.onTest", 111, 222.22, true, "ttt");
INFO
JS 中定义 Hummer 域下的方法:Hummer.onTest = (a, b, c, d) => {};
5. Native 调用 JS 某个对象的方法(只能在渲染页面结束后才能使用)
HummerContext hmContext = hmRender.getHummerContext();
JSValue jsPage = hmContext.getJsPage();
if (jsPage != null) {
jsPage.callFunction("onTest", 111, 222.22, true, "ttt");
}
INFO
JS中在RootView中定义方法:onTest(a, b, c, d) {};
五、页面跳转
默认跳转逻辑
目前页面跳转只支持页面间的跳转,通过 Navigator 组件 来完成。Navigator 组件默认是通过 Activity 的方式来进行跳转的,并且框架提供了默认的 Hummer 容器 HummerActivity。默认跳转逻辑可参考 DefaultNavigatorAdapter 和 DefaultIntentCreator 这两个实现类。
自定义 Hummer 容器
如果在 Hummer 页面跳转时,不想使用这个默认 HummerActivity 容器,可以在 Hummer.init()
时通过 HummerConfig
来重写 DefaultIntentCreator
中的 createHummerIntent
方法,替换成你自己的 Hummer Activity 容器:
HummerConfig config = new HummerConfig.Builder()
.setNavigatorAdapter(new DefaultNavigatorAdapter(new DefaultIntentCreator() {
@Override
public Intent createHummerIntent(Context context, NavPage page) {
Intent intent = new Intent(context, MyHummerActivity.class);
appendBaseIntentParams(intent, page);
return intent;
}
})
.builder();
Hummer.init(this, config);
如果不想使用 Activity 作为 Hummer 容器,而是想用 Fragment 来作为 Hummer 容器,则可以直接重写 DefaultNavigatorAdapter
中的 openHummerPage
方法:
HummerConfig config = new HummerConfig.Builder()
.setNavigatorAdapter(new DefaultNavigatorAdapter() {
@Override
protected void openHummerPage(Context context, NavPage page, NavCallback callback) {
// 自定义通过 Fragment 跳转的逻辑
}
})
.builder();
Hummer.init(this, config);
或者完全自己实现 INavigatorAdapter:
HummerConfig config = new HummerConfig.Builder()
.setNavigatorAdapter(new MyNavigatorAdapter())
.builder();
Hummer.init(this, config);
其他类型页面跳转
如果想实现 Hummer 页面跳转至其他类型的页面,如 H5 或 Native 页面,需要重写 DefaultIntentCreator
中的 createWebIntent
和 createCustomIntent
方法,框架默认都是空实现:
HummerConfig config = new HummerConfig.Builder()
.setNavigatorAdapter(new DefaultNavigatorAdapter(new DefaultIntentCreator() {
@Override
public Intent createWebIntent(Context context, NavPage page) {
return Intent intent = new Intent(context, MyWebActivity.class);
}
@Override
public Intent createCustomIntent(Context context, NavPage page) {
return Intent intent = new Intent(context, MyCustomActivity.class);
}
})
.builder();
Hummer.init(this, config);
六、数据传递
目前有三种方式进行页面间的数据传递:
Navigator 方式
在页面跳转时,可以通过 Navigator 组件 来做页面间的数据传递,详见:页面间数据传递 文档。
NotifyCenter
在没有发生页面跳转时,可以通过 NotifyCenter API 来实现页面间的数据传递。
JSValue
如果是在同一个 Hummer 页面中,需要从原生侧传递数据给 JS 侧,可以通过在原生侧给 JS 侧设置 JSValue 的方式来实现数据传递,详见:Native容器页面传递数据给JS页面 文档。