手把手教你系列- 变色龙SDK使用范例

以一个小demo工程,讲述变色龙SDK的使用方式,引领轻松入门。Demo 工程在根目录 app 目录下,用 Android Studio 导入并 run 起来后,再对照以下说明文档看会好理解。

根目录 assets 目录下的 cml-demo-say.zip 是个简单的示例工程,用来演示native 和 weex容器或web容器的双向通信。app 目录下demo工程加载的 weex bundle 和 h5 页就是这个工程实现的。## 1. compile 依赖添加### 1.1 项目根目录 build.gradle 里添加 maven 仓库地址buildscript { repositories { jcenter() maven { url &#39;https://maven.google.com/&#39; } } ...}allprojects { repositories { maven { url &#39;https://maven.google.com/&#39; } jcenter() mavenCentral() ... }}### 1.2 在 app 模块的 build.gradle 里添加依赖#### 1.2.1 首先添加如下依赖gradledependencies { ... compile &#34;com.android.support:support-v4:$SUPPORT_VER&#34; compile &#34;com.android.support:appcompat-v7:$SUPPORT_VER&#34; compile &#34;com.android.support:recyclerview-v7:$SUPPORT_VER&#34; compile &#34;com.didiglobal.chameleon:cmlsdk:$VERSION&#34; compile &#34;com.didiglobal.chameleon:cmlweb:$VERSION&#34;}#### 1.2.2 添加渲染引擎依赖cml native sdk 采用 weex 作为渲染引擎,当前依赖的weex版本是- weex -> com.taobao.android:weex_sdk:0.20.0.2gradledependencies { ... compile &#34;com.didiglobal.chameleon:cmlweex:$VERSION&#34;}com.android.tools.build:gradle 3.0 以后的版本用 implementation 替换 compile,完整的依赖列表可参考示例工程。## 2. 权限添加及 android 6.0 以上系统授权Chameleon SDK 已经添加了如下权限,android 6.0 以上系统版本需要在调起相关页面后手动授权。gradle <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"><uses-permission android:name="android.permission.READ_PHONE_STATE">## 3. 混淆参考示例工程## 4. 初始化入口实现自己的 Application 类,在应用启动的时候进行初始化调用。### 4.1 Application demojavapublic class MyApplication extends Application implements CmlConfig { @Override public void onCreate() { super.onCreate(); CmlEngine.getInstance().init(this, this); } @Override public void configAdapter() { // 开发阶段可以禁用js bundle缓存 CmlEnvironment.CML_ALLOW_BUNDLE_CACHE = false; // 开发阶段手动降级测试// CmlEnvironment.CML_DEGRADE = false; // 注册降级Adapter CmlEnvironment.setDegradeAdapter(new CmlDegradeDefault());// CmlEnvironment.setToastAdapter(xxx);// CmlEnvironment.setLoggerAdapter(xxx);// CmlEnvironment.setDialogAdapter(xxx);// CmlEnvironment.setNavigatorAdapter(xxx);// CmlEnvironment.setStatisticsAdapter(xxx);// CmlEnvironment.setImageLoaderAdapter(xxx); } @Override public void registerModule() { CmlEngine.getInstance().registerModule(ModuleDemo.class); }}### 4.2 将 Application 添加到 AndroidManifest.xmlxml<application android:name=".MyApplication" android:allowbackup="false" ...="" <="" application="">### 4.3 适配器的实现适配器是暴露给SDK使用者的一组接口,用于提供扩展SDK的能力,其中一部分接口提供了默认实现,未提供默认实现的需要使用者自己实现并注册到SDK中。image- 以下适配器提供了默认实现http / json / log / modal / storage / thread / websocket / imagloader- 以下适配器未提供默认实现navigator / degrade / statistics以降级适配器举例,只需要两个步骤:- 新建类 CmlDegradeDefault,实现接口 ICmlDegradeAdapter- 在 MyApplication 的 configAdapter 方法里注册适配器。### 4.4 module 的实现module 的基本概念可参看这里, SDK层实现在如下位置:imagemodule 的实现主要分两个步骤,一个是 native 侧的代码实现,一个是 js 侧的代码实现。同时,native 和 js 的通信是双向的,及native 可以主动调用 js 侧的方法,js 侧也可以主动调用 native 侧的方法。image#### 4.4.1 native 侧的实现以下内容请在 Chameleon SDK demo 运行起来的前提下阅读,方便理解即将描述的内容。
实现一个module扩展需要如下几个步骤:

  • 根据需求和前端RD确定module/method/args参数
  • native RD根据约定实现module扩展,前端RD根据约定实现js 并打包成jsbundle
  • native RD可以将jsbundle放到assets目录下加载调试,也可以远程加载调试
    demo实现了双向通信,js 调用 native接口约定如下 (module的概念):

  • module名是 moduleDemo

  • method名是 sayHello
  • args名是 {"content":"测试"}
    实现的效果是点击jsbundle里的按钮,会弹 测试 toast

native 调用 js接口约定如下:

  • module名是 moduleDemo
  • method名是 NaTellJS
  • args名是 {"content":"测试"}
    实现的效果是点击 native 侧的按钮,会动态改变 jsbundle里的文字显示。

module 的实现需要先了解3个注解

  • @CmlModule 标注这个类是扩展模块
  • @CmlMethod 标注可供JS侧调用的方法
  • @CmlParam 标注调用的参数
    以下是根据约定实现的 module 扩展示例
  1. @CmlModule(alias = "moduleDemo")
  2. public class ModuleDemo {
  3. @CmlMethod(alias = "sayHello")
  4. public void sayHello(ICmlActivityInstance instance, @CmlParam(name = "content") String content) {
  5. Toast.makeText(instance.getContext(), content, Toast.LENGTH_SHORT);
  6. }
  7. }

将 module 注册到SDK

  1. public class MyApplication extends Application implements CmlConfig {
  2. @Override
  3. public void onCreate() {
  4. super.onCreate();
  5. CmlEngine.getInstance().init(this, this);
  6. CmlEngine.getInstance().registerModule(ModuleDemo.class); // 在这里注册
  7. }
  8. ...
  9. }

从前端RD拿到 JSBundle, 进行渲染,重点关注demo工程里CmlViewActivity

  1. private static final String URL_JS_BUNDLE_OK = "https://www.example.com/degradle.html?cml_addr=http://172.22.138.92:8000/weex/cml-demo-say.js";
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. ....
  5. // cmlView.render(URL_JS_BUNDLE_OK, null); // 加载远程jsbundle
  6. cmlView.render("file://local/cml-demo-say.js", null); // 加载assets目录里的jsbundle
  7. ...
  8. }

js侧的实现以及 jsbundle 打包参看接下来的 4.4.2 小结。

4.4.2 js 侧的实现

同样以上面要实现的module和方法为例,我们来看一下js侧的实现。

参考根目录 assets 目录下的 cml-demo-say.zip
1.初始化前端项目(也可以使用已有的项目)

  1. cml init project
  2. 请输入项目名称 cml-demo-say

2.打开项目下 src/pages/index/index.cml文件,粘贴以下代码。以下代码主要是展示了js和native双向的通信的方法实现。先粘贴,然后我们在下面介绍通信封装的原理。

  1. <template>
  2. <page title="chameleon">
  3. <scroller height="{{winHeight}}">
  4. <view class="scroller-wrap">
  5. <view style="padding: 20cpx;">
  6. <text>1,JS调用Native</text>
  7. <text>callback返回内容: {{callbackRes}}</text>
  8. <button
  9. c-bind:onclick="sayHello"
  10. text="JS调用Native sayHello方法"
  11. style="margin-top: 20cpx;"
  12. ></button>
  13. </view>
  14. <view style="padding: 20cpx;">
  15. <text>2,Native调用JS 的NaTellJS方法已经注册监听,点击Native的改变文字按钮,下发状态文字将改变</text>
  16. <text>状态:{{status}}</text>
  17. </view>
  18. </view>
  19. </scroller>
  20. </page>
  21. </template>
  22. <script>
  23. import cml from "chameleon-api";
  24. import cmlBridge from "chameleon-bridge";
  25. class Index {
  26. data = {
  27. title: "chameleon",
  28. callbackRes: '',
  29. status: '等待Native调用',
  30. winHeight: 0
  31. }
  32. methods = {
  33. // 此处的方法实现你可以封装到其他目录,作为统一扩展的api
  34. sayHello() {
  35. cmlBridge.callNative(
  36. 'moduleDemo', // 模块名
  37. 'sayHello', // 方法名
  38. { content: 'Hello Chameleon!' }, // 参数
  39. res => {
  40. this.callbackRes = res;
  41. } // 回调方法
  42. );
  43. }
  44. }
  45. mounted() {
  46. // 主动监听客户端调用js
  47. cmlBridge.listenNative(
  48. 'moduleDemo', // 模块名
  49. 'NaTellJS', // 方法名
  50. res => {
  51. this.status = res.content;
  52. }
  53. );
  54. cml.getSystemInfo().then((info) => {
  55. this.winHeight = Number(info.viewportHeight)
  56. });
  57. }
  58. }
  59. export default new Index();
  60. </script>
  61. <style scoped>
  62. .scroller-wrap {
  63. display: flex;
  64. flex-direction: column;
  65. align-items: center;
  66. }
  67. </style>
  68. <script cml-type="json">
  69. {
  70. "base": {
  71. "usingComponents": {
  72. }
  73. },
  74. "wx": {
  75. "navigationBarTitleText": "index",
  76. "backgroundTextStyle": "dark",
  77. "backgroundColor": "#E2E2E2"
  78. }
  79. }
  80. </script>

3.你可以运行cml weex dev或者cml weex build命令,将jsbundle文件的在线地址或本地文件给到Native同学,按照上面Native部分提到的集成方式(本地或远程地址)进行联调。点击第一项的按钮你可以看到,客户端弹出了"Hello Chameleon!"的弹框。同时在callback返回内容:后返回了内容。点击Native端的“改变文字”按钮,在页面上的“状态:”后显示了客户端发来的“测试”字样。

4.接下来我们看一下原理:

我们通过chameleon-bridge提供的callNative和listenNative来与端通信,他们的参数及意义如下:

  • callNative(module:String, method:String, args:Object, callback:Function) JS主动调用Native方法
  • listenNative(module:String, method:String, callback:Function) 监听Native调用js
JS主动调用Native方法

首先看methods里我们通过chameleon-bridge提供的callNative方法实现了sayHello方法,让js与native主动通信。

  1. sayHello() {
  2. cmlBridge.callNative(
  3. 'moduleDemo', // 模块名
  4. 'sayHello', // 方法名
  5. { content: 'Hello Chameleon!' }, // 参数
  6. res => {
  7. this.callbackRes = res;
  8. } // 回调方法
  9. );
  10. }

这样就调用到了native的模块名为moduleDemo的模块的sayHello方法,同时native会回调我们。当然你还可以封装到其他目录,作为统一扩展的api来存放并引入到项目文件中使用。

监听Native调用js

mounted里我们通过chameleon-bridge提供的listenNative方法在页面初始化时监听了客户端要调用我们的模块名为moduleDemo的NaTellJS方法。注意,这个模块在前端其实可以理解为一个和Native同学约定的方法命名集合。调用前端chameleon-bridge库的listenNative方法会进行监听的注册,便可以被客户端调用到。

  1. cmlBridge.listenNative(
  2. 'moduleDemo', // 模块名
  3. 'NaTellJS', // 方法名
  4. res => {
  5. this.status = res.content;
  6. }
  7. );

同样你可以将此方法封装到其他api集合位置,进行引入使用,如:

  1. export function onNaTellJS(callback) {
  2. cmlBridge.listenNative(
  3. 'moduleDemo', // 模块名
  4. 'NaTellJS', // 方法名
  5. callback
  6. );
  7. }

如此一来,你便可以封装自己的扩展api了。

4.5 js bundle 缓存的禁用

开发截断为了方便实时预览效果,可以关闭 js bundle 的缓存。

  1. @Override
  2. public void configAdapter() {
  3. // 开发阶段可以禁用js bundle缓存
  4. CmlEnvironment.CML_ALLOW_BUNDLE_CACHE = false;
  5. ...
  6. }

5. 页面调起

5.1 整个页面使用 Chameleon 容器实现

  1. CmlEngine.getInstance().launchPage(activity, url, options);
  2. // 即将支持
  3. CmlEngine.getInstance().launchPage(activity, url, options, requestCode, launchCallback);

5.2 使用CmlView

参考根目录 app 目录下demo
CmlView 用在和原生 Native View 混合布局的场景,

  1. private CmlView cmlView;
  2. @Override
  3. protected void onCreate(@Nullable Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_test_cml_view);
  6. FrameLayout flRoot = findViewById(R.id.fl_root);
  7. cmlView = new CmlView(this);
  8. flRoot.addView(cmlView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
  9. cmlView.onCreate();
  10. cmlView.render(MainActivity.TEST_URL, null);
  11. }
  12. @Override
  13. protected void onResume() {
  14. super.onResume();
  15. if (cmlView != null) {
  16. cmlView.onResume();
  17. }
  18. }
  19. @Override
  20. protected void onPause() {
  21. super.onPause();
  22. if (cmlView != null) {
  23. cmlView.onPause();
  24. }
  25. }
  26. @Override
  27. protected void onDestroy() {
  28. super.onDestroy();
  29. if (cmlView != null) {
  30. cmlView.onDestroy();
  31. }
  32. }

5.3 打开普通URL

如果打开的是普通的URL,则会自动使用 CmlWebEngine 调起 Web Container,渲染 H5 页面

  1. // 演示打开一般的URL
  2. private static final String URL_NORMAL = "https://www.didiglobal.com";
  3. ...
  4. @Override
  5. public void onClick(View view) {
  6. switch (view.getId()) {
  7. case R.id.txt_open_url:
  8. CmlEngine.getInstance().launchPage(this, URL_NORMAL, null);
  9. break;
  10. ...
  11. }
  12. }

5.4 打开 JS Bundle

如果打开的是JS Bundle URL,则会自动使用 native 渲染引擎调起 native Container,渲染 JS Bundle

  1. // 这是一个可以正常打开的 JS_BUNDLE
  2. private static final String URL_JS_BUNDLE_OK = "https://beatles-chameleon.github.io/chameleon-ui-builtin/dist/web/chameleon-ui-builtin.html?cml_addr=https%3A%2F%2Fbeatles-chameleon.github.io%2Fchameleon-ui-builtin%2Fdist%2Fweex%2Fchameleon-ui-builtin.js#/";
  3. ...
  4. @Override
  5. public void onClick(View view) {
  6. switch (view.getId()) {
  7. ...
  8. case R.id.txt_open_js_bundle:
  9. CmlEngine.getInstance().launchPage(this, URL_JS_BUNDLE_OK, null);
  10. break;
  11. ...
  12. }
  13. }

5.5 预加载

如果打开的是一个已经预加载过的 JS Bundle URL,则会忽略下载过程,直接使用 native 渲染引擎渲染界面

  1. // 这是一个测试预加载的 JS_BUNDLE
  2. private static final String URL_JS_BUNDLE_PRELOAD = "https://beatles-chameleon.github.io/chameleon-ui-builtin/dist/web/chameleon-ui-builtin.html?cml_addr=https%3A%2F%2Fbeatles-chameleon.github.io%2Fchameleon-ui-builtin%2Fdist%2Fweex%2Fchameleon-ui-builtin.js#/";
  3. ...
  4. @Override
  5. public void onClick(View view) {
  6. switch (view.getId()) {
  7. ...
  8. case R.id.txt_preload:
  9. CmlEngine.getInstance().launchPage(this, URL_JS_BUNDLE_PRELOAD, null);
  10. break;
  11. ...
  12. }
  13. }

5.6 自动降级

如果打开的是一个错误的JS Bundle URL,则会自动降级,使用 CmlWebEngine 调起 Web Container,渲染前面 H5 地址页面,具体可以查看

  • 工程化 -> Chameleon URL 一节关于,Chameleon URL 的定义
  • Native渲染能力接入 -> 自动降级 一节,关于自动降级的详细说明
  1. // 这是一个错误的 JS_BUNDLE
  2. private static final String URL_JS_BUNDLE_ERR = "https://www.didiglobal.com?cml_addr=xxx.js";
  3. ...
  4. @Override
  5. public void onClick(View view) {
  6. switch (view.getId()) {
  7. ...
  8. case R.id.txt_degrade:
  9. CmlEngine.getInstance().launchPage(this, URL_JS_BUNDLE_ERR, null);
  10. break;
  11. }
  12. }

6. 预加载

如果某些 js bundle 不需要实时下载下来渲染,可以先配置到预加载列表里提前下载下来,提升用户交互体验。

6.1 预加载列表的配置

组装 CmlBundle 列表,并通过 CmlEngine 接口设置到SDK里,具体可以参考 demo。

  1. private List<CmlBundle> getPreloadList() {
  2. CmlJsBundleEnvironment.DEBUG = true;
  3. List<CmlBundle> cmlModels = new ArrayList<>();
  4. CmlBundle model = new CmlBundle();
  5. model.bundle = Util.parseCmlUrl(URL_JS_BUNDLE_PRELOAD);
  6. model.priority = 2;
  7. cmlModels.add(model);
  8. return cmlModels;
  9. }
  10. ...
  11. CmlEngine.getInstance().initPreloadList(getPreloadList());

6.2 开始预加载列表

根据使用方的业务,在适当位置执行预加载

  1. CmlEngine.getInstance().performPreload();