Skip to content
目录

Android 进阶

一、Hummer SDK 初始化配置

可以在 Hummer SDK 初始化时,通过 HummerConfig 配置各种自定义能力,这些配置项都是可选的。

HummerConfig 设置

java
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. 单页面容器

java
/**
 * 继承至 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. 嵌入式页面容器

java
/**
 * 继承至 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 配置

java
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. 编写自定义导出类

自定义导出组件分为 静态功能组件普通功能组件视图组件 三大类,主要会用到以下几个注解:

java
// 类注解,用于导出该类,导出类的名字由注解中的 [ClassName] 指定
@Component("[ClassName]")

// 变量注解,用于导出该属性,导出属性的名字由注解中的 [PropertyName] 指定
@JsProperty("[PropertyName]")

// 变量注解,用于导出该样式(此注解目前尚未启用,需要手动处理调用)
@JsAttribute("[AttrName]")

// 方法注解,用于导出该方法,导出方法的名字由注解中的 [FuncName] 指定
@JsMethod("[FuncName]")
静态功能组件
java
@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);
    }
}
普通功能组件
java
@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 + '\'' +
                    '}';
        }
    }
}
视图组件

视图组件和普通功能组件基本类似,有以下几点区别:

  1. 视图组件需要继承至 HMBase 类,在泛型中设置真实的视图类型;
  2. 实现 createViewInstance 类,返回真真实视图对象;
  3. 视图组件比普通功能组件多一个注解:JsAttribute,用于导出设置在style中的样式,不过目前该注解还未被启用,需要通过重写 setStyle 方法来手动调用设置样式的方法;

其他更多高级用法,可以参考 Hummer SDK 中的各种内置组件的实现。

java
@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;
    }
}
保留参数
  1. 所有导出组件的 构造函数 中的前两个参数为保留参数。
    • 第一个参数用于接收 ContextHummerContext,其中 Context 是页面上下文,HummerContext 是 Hummer 执行上下文。
    • 第二个参数用于接收 本对象对应的 JSValue。
  2. 所有导出组件的 导出方法 中的第一个参数是保留参数。
    • 用于接收 ContextHummerContext,其中 Context 是页面上下文,HummerContext 是 Hummer 执行上下文。 示例:
java
@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 (精简版生命周期)
java
@Component("TestExportModel")
public class TestExportModel implements ILifeCycle {
    @Override
    public void onCreate() {}

    @Override
    public void onDestroy() {}
}
  • IFullLifeCycle (完整版生命周期)
java
@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 名字,需要自行修改。

java
@Override
protected void initHummerRegister(HummerContext context) {
    HummerRegister$$hummer_demo_app.init(context);
}

四、Bridge 用法


1. Native 向 JS 静态类注册方法

java
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 对象注册方法(只能在渲染页面结束后才能使用)

java
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 的全局方法(只能在渲染页面结束后才能使用)

java
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 域下的方法(只能在渲染页面结束后才能使用)

java
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 某个对象的方法(只能在渲染页面结束后才能使用)

java
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。默认跳转逻辑可参考 DefaultNavigatorAdapterDefaultIntentCreator 这两个实现类。

自定义 Hummer 容器

如果在 Hummer 页面跳转时,不想使用这个默认 HummerActivity 容器,可以在 Hummer.init() 时通过 HummerConfig 来重写 DefaultIntentCreator 中的 createHummerIntent 方法,替换成你自己的 Hummer Activity 容器:

java
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 方法:

java
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:

java
HummerConfig config = new HummerConfig.Builder()
    .setNavigatorAdapter(new MyNavigatorAdapter())
    .builder();
Hummer.init(this, config);

其他类型页面跳转

如果想实现 Hummer 页面跳转至其他类型的页面,如 H5 或 Native 页面,需要重写 DefaultIntentCreator 中的 createWebIntentcreateCustomIntent 方法,框架默认都是空实现:

java
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 组件 来做页面间的数据传递,详见:页面间数据传递 文档。

NotifyCenter

在没有发生页面跳转时,可以通过 NotifyCenter API 来实现页面间的数据传递。

JSValue

如果是在同一个 Hummer 页面中,需要从原生侧传递数据给 JS 侧,可以通过在原生侧给 JS 侧设置 JSValue 的方式来实现数据传递,详见:Native容器页面传递数据给JS页面 文档。