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. 所有导出组件的 构造函数 中的前两个参数为保留参数。
    • 第一个参数用于接收 Context 或 HummerContext,其中 Context 是页面上下文,HummerContext 是 Hummer 执行上下文。
    • 第二个参数用于接收 本对象对应的 JSValue。
  2. 所有导出组件的 导出方法 中的第一个参数是保留参数。
    • 用于接收 Context 或 HummerContext,其中 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。默认跳转逻辑可参考 DefaultNavigatorAdapter 和 DefaultIntentCreator 这两个实现类。

自定义 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 中的 createWebIntent 和 createCustomIntent 方法,框架默认都是空实现:

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页面 文档。