开发者须知

在您阅读此文档时,我们假定您已经具备了相应Android应用开发经验,使用Android Studio开发过Android原生。学习过 weex 知识并能够理解相关概念,也应该对HTML,JavaScript,CSS等有一定的了解, 并且熟悉在JavaScript和JAVA环境下的JSON格式数据操作等。

开发环境

项目创建

Tips:如果已经创建了项目可以跳过创建步骤

打开Android studio新建一个空白项目

Android 插件开发教程 - 图1

选择 Empty Activity 点击 Next。

Android 插件开发教程 - 图2

Minimum API Level 建议选19及以上

package name作为应用标志,涉及申请第三方平台账号,一般情况下不建议修改,包名一般设置反向域名,如:io.dcloud.HBuilder

点击 Finish 完成创建。

注意:暂时不支持Kotlin

导入uni插件原生项目

  • UniPlugin-Hello-AS工程请在App离线SDK中查找
  • 点击Android Studio菜单选项File—->New—->Import Project。

Android 插件开发教程 - 图3

  • 导入选择UniPlugin-Hello-AS工程,点击OK! 等待工程导入完毕。

Android 插件开发教程 - 图4

  • 如果出现Android SDK路径不对问题,请在Android Studio中鼠标右键UniPlugin-Hello-AS选择Open Module Settings, 在SDK Location 中设置相关环境路径

Android 插件开发教程 - 图5

Android 插件开发教程 - 图6

Tips

工程gradle配置的gradle-4.6-all版本!使用的是新版本的依赖方式,如果您使用的是老版本的gradle。可根据以下链接进行修改依赖方式.gradle新依赖方式与旧依赖方式的不同

插件开发

Tips

  • 学习Weex扩展 Android 知识。目前集成了Weex 0.26.0版本!!! 如果你之前开发的是老版本代码需要作升级代码操作。 weex扩展API for android
  • 特别注意uni-app插件目前仅支持Module扩展和Component扩展,暂时不支持Adapter扩展!!!

扩展 module

下面以TestModule为例,源码请查看 UniPlugin-Hello-AS(2.6.16+)工程中的uniplugin_module模块;

创建Android Studio的Module模块

  • 在现有Android项目中创建library的Module。例如uniplugin_module
  • 配置刚创建的Module的build.gradle信息。

示例:

  1. //导入aar需要的配置
  2. repositories {
  3. flatDir {
  4. dirs 'libs'
  5. }
  6. }
  7. dependencies {
  8. //必须添加的依赖
  9. compileOnly 'com.android.support:recyclerview-v7:27.1.0'
  10. compileOnly 'com.android.support:support-v4:27.1.0'
  11. compileOnly 'com.android.support:appcompat-v7:27.1.0'
  12. compileOnly 'com.alibaba:fastjson:1.1.46.android'
  13. compileOnly fileTree(include: ['uniapp-release.aar'], dir: '../app/libs')
  14. }

Tips:

uniapp-release.aar是扩展module主要依赖库,必须导入此依赖库!

创建TestModule类

  • Module 扩展必须继承 WXModule 类

示例:

  1. public class TestModule extends WXModule
  • 扩展方法必须加上@JSMethod (uiThread = false or true) 注解。Weex 会根据注解来判断当前方法是否要运行在 UI 线程,和当前方法是否是扩展方法。
  • Weex是根据反射来进行调用 Module 扩展方法,所以Module中的扩展方法必须是 public 类型。

示例:

  1. //run ui thread
  2. @JSMethod(uiThread = true)
  3. public void testAsyncFunc(JSONObject options, JSCallback callback) {
  4. Log.e(TAG, "testAsyncFunc--"+options);
  5. if(callback != null) {
  6. JSONObject data = new JSONObject();
  7. data.put("code", "success");
  8. callback.invoke(data);
  9. }
  10. }
  11. //run JS thread
  12. @JSMethod (uiThread = false)
  13. public JSONObject testSyncFunc(){
  14. JSONObject data = new JSONObject();
  15. data.put("code", "success");
  16. return data;
  17. }
  • 同样因为是通过反射调用,Module 不能被混淆。请在混淆文件中添加代码:
  1. -keep public class * extends com.taobao.weex.common.WXModule{*;}
  • Module 扩展的方法可以使用 int, double, float, String, Map, List 类型的参数

扩展组件 component

下面以TestComponent为例,源码请查看 UniPlugin-Hello-AS(2.6.16+)工程中的uniplugin_component模块;

创建Android Studio的Module模块

请参考 扩展 Module

创建TestComponent类

  • Component 扩展类必须继承 WXComponent

示例:

  1. public class TestText extends WXComponent<TextView>
  • WXComponent的initComponentHostView回调函数。构建Component的view时会触发此回调函数。

示例:

  1. @Override
  2. protected TextView initComponentHostView(@NonNull Context context) {
  3. TextView textView = new TextView(context);
  4. textView.setTextSize(20);
  5. textView.setTextColor(Color.BLACK);
  6. return textView;
  7. }
  • Component 对应的设置属性的方法必须添加注解 @WXComponentProp(name=value(value is attr or style of dsl))

示例:

  1. @WXComponentProp(name = "tel")
  2. public void setTel(String telNumber) {
  3. getHostView().setText("tel: " + telNumber);
  4. }
  • Weex sdk 通过反射调用对应的方法,所以 Component 对应的属性方法必须是 public,并且不能被混淆。请在混淆文件中添加代码

    1. -keep public class * extends com.taobao.weex.ui.component.WXComponent{*;}
  • Component 扩展的方法可以使用 int, double, float, String, Map, List 类型的参数

  • Component定义组件方法.

    示例:

    • 在组件中如下声明一个组件方法

      1. @JSMethod
      2. public void clearTel() {
      3. getHostView().setText("");
      4. }
    • 注册组之后,你可以在weex 文件中调用

    1. <template>
    2. <div>
    3. <myText ref="telText" tel="12305" style="width:200;height:100" @onTel="onTel" @click="myTextClick"></myText>
    4. </div>
    5. </template>
    6. <script>
    7. export default {
    8. methods: {
    9. myTextClick(e) {
    10. this.$refs.telText.clearTel();
    11. }
    12. }
    13. }
    14. </script>

component 自定义发送事件

向JS环境发送一些事件,比如click事件

  1. void fireEvent(elementRef,type)
  2. void fireEvent(elementRef,type, data)
  3. void fireEvent(elementRef,type,data,domChanges)
  • elementRef(String):产生事件的组件id
  • type(String): 事件名称,weex默认事件名称格式为”onXXX”,比如OnPullDown
  • data(Map): 需要发送的一些额外数据,比如click时,view大小,点击坐标等等。
  • domChanges(Map): 目标组件的属性和样式发生的修改内容

示例:

以myText标签为例, 通过 @事件名=”方法名” 添加事件,如下添加onTel事件,源码请查看 UniPlugin-Hello-AS工程中的uniplugin_component模块

  1. //原生触发fireEvent 自定义事件onTel
  2. Map<String, Object> params = new HashMap<>();
  3. Map<String, Object> number = new HashMap<>();
  4. number.put("tel", telNumber);
  5. //目前uni限制 参数需要放入到"detail"中 否则会被清理
  6. params.put("detail", number);
  7. fireEvent("onTel", params);
  1. //标签注册接收onTel事件
  2. <myText tel="12305" style="width:200;height:100" @onTel="onTel"></myText>
  3. //事件回调
  4. methods: {
  5. onTel: (e)=> {
  6. console.log("onTel="+e.detail.tel);
  7. }
  8. }

JSCallback结果回调

JS调用时,有的场景需要返回一些数据,比如以下例子,返回x、y坐标

  1. void invoke(Object data);
  2. void invokeAndKeepAlive(Object data);
  • invoke调用javascript回调方法,此方法将在调用后被销毁。
  • invokeAndKeepAlive 调用javascript回调方法并保持回调活动以备以后使用。

示例:

  1. @JSMethod(uiThread = true)
  2. public void testAsyncFunc(JSONObject options, JSCallback callback) {
  3. Log.e(TAG, "testAsyncFunc--"+options);
  4. if(callback != null) {
  5. JSONObject data = new JSONObject();
  6. data.put("code", "success");
  7. callback.invoke(data);
  8. }
  9. }

注意

执行自定义事件fireEvent时params的数据资源都要放入到”detail”中。如果没有将你得返回的数据放入”detail”中将可能丢失。请注意!!!

插件示例—RichAlert

封装了一个 RichAlertWXModule, 富文本alert弹窗Module

代码可参考UniPlugin-Hello-AS工程中的uniplugin_richalert模块。(UniPlugin-Hello-AS工程请在App离线SDK中查找)

  1. public class RichAlertWXModule extends WXSDKEngine.DestroyableModule {
  2. ...
  3. @JSMethod(uiThread = true)
  4. public void show(JSONObject options, JSCallback jsCallback) {
  5. if (mWXSDKInstance.getContext() instanceof Activity) {
  6. ...
  7. RichAlert richAlert = new RichAlert(mWXSDKInstance.getContext());
  8. ...
  9. richAlert.show();
  10. ...
  11. }
  12. }
  13. ...
  14. ...
  15. @JSMethod(uiThread = true)
  16. public void dismiss() {
  17. destroy();
  18. }
  19. @Override
  20. public void destroy() {
  21. if (alert != null && alert.isShowing()) {
  22. WXLogUtils.w("Dismiss the active dialog");
  23. alert.dismiss();
  24. }
  25. }
  26. }

HBuilderX 项目中使用RichAlert示例

  1. // require插件名称
  2. const dcRichAlert = uni.requireNativePlugin('DCloud-RichAlert');
  3. // 使用插件
  4. dcRichAlert.show({
  5. position: 'bottom',
  6. title: "提示信息",
  7. titleColor: '#FF0000',
  8. content: "<a href='https://uniapp.dcloud.io/' value='Hello uni-app'>uni-app</a> 是一个使用 Vue.js 开发跨平台应用的前端框架!\n免费的\n免费的\n免费的\n重要的事情说三遍",
  9. contentAlign: 'left',
  10. checkBox: {
  11. title: '不再提示',
  12. isSelected: true
  13. },
  14. buttons: [{
  15. title: '取消'
  16. },
  17. {
  18. title: '否'
  19. },
  20. {
  21. title: '确认',
  22. titleColor: '#3F51B5'
  23. }
  24. ]
  25. }, result => {
  26. switch (result.type) {
  27. case 'button':
  28. console.log("callback---button--" + result.index);
  29. break;
  30. case 'checkBox':
  31. console.log("callback---checkBox--" + result.isSelected);
  32. break;
  33. case 'a':
  34. console.log("callback---a--" + JSON.stringify(result));
  35. break;
  36. case 'backCancel':
  37. console.log("callback---backCancel--");
  38. break;
  39. }
  40. });

插件调试

本地注册插件

以上两种方式选一即可

  • 第一种方式

    • 在UniPlugin-Hello-AS工程下 “app” Module根目录assets/dcloud_uniplugins.json文件。 在moudles节点下 添加你要注册的Module 或 Component
  • 第二种方式

    • 创建一个实体类并实现AppHookProxy接口,在onCreate函数中添加weex注册相关参数 或 填写插件需要在启动时初始化的逻辑。
    • 在UniPlugin-Hello-AS工程下 “app” Module根目录assets/dcloud_uniplugins.json文件,在hooksClass节点添加你创建实现AppHookProxy接口的实体类完整名称填入其中即可 (有些需要初始化操作的需求可以在此处添加逻辑,无特殊操作仅使用第一种方式注册即可无需集成AppHookProxy接口)
    1. public class RichAlert_AppProxy implements AppHookProxy {
    2. @Override
    3. public void onCreate(Application application) {
    4. //可写初始化触发逻辑
    5. }
    6. }

关于dcloud_uniplugins.json说明:

  • nativePlugins: 插件跟节点 可存放多个插件
  • hooksClass: 生命周期代理(实现AppHookProxy接口类)格式(完整包名加类名)
  • name : 注册名称,
  • class : module 或 component 实体类完整名称
  • type : module 或 component类型。
  1. {
  2. "nativePlugins": [
  3. {
  4. "hooksClass": "uni.dcloud.io.uniplugin_richalert.apphooks",
  5. "plugins": [
  6. {
  7. "type": "module",
  8. "name": "DCloud-RichAlert",
  9. "class": "uni.dcloud.io.uniplugin_richalert.RichAlertWXModule"
  10. }
  11. ]
  12. }
  13. ]
  14. }

集成uni-app项目测试插件

  • 安装最新HbuilderX 大于等于1.4.0+

  • 创建uni-app工程或在已有的uni-app工程编写相关的.nvue 和.vue文件。使用uni-app插件中的module 或 component。

  • xxx.vue 示例代码(源码请参考UniPlugin-Hello-AS项目中uniapp示例工程源码文件夹的unipluginDemo工程)

Android 插件开发教程 - 图7

  • 选择 发行—->原生APP-本地打包—->生成本地打包App资源 等待资源生成!

Android 插件开发教程 - 图8

  • 在控制台会输出编译日志,编译成功会给出App资源路径

Android 插件开发教程 - 图9

  • 把APP资源文件放入到UniPlugin-Hello-AS工程下 app Module根目录assets/apps/测试工程appid/www对应目录下,再修改assets/data/dcloud_control.xml!修改其中appid=“测试工程appid”!,测试工程UniPlugin-Hello-AS 已有相关配置可参考。具体可查看App离线打包

  • appid注意 一定要统一否则会导致应用无法正常运行!

Android 插件开发教程 - 图10

  • 配置”app”Module下的 build.gradle. 在dependencies节点添加插件project引用 (以uniplugin_richalert为例)
  1. // 添加uni-app插件
  2. implementation project(':uniplugin_richalert')
  • 运行测试。测试运行时一切要以真机运行为主。

生成uni-app插件

  • 完整的android 插件包.ZIP包含:
    • android文件 里面存放XXX.aar 、libs文件夹。
      • .aar文件 插件包
      • libs文件夹 存放插件包依赖的第三方 .jar文件和.so文件
    • package.json 插件信息

准备相关文件

  • 创建已插件id命名的文件夹
  • 创建android文件夹。并将该文件夹放入到新建的插件id命名的文件夹中

示例:

  1. |-- DCloud-RichAlert --->插件id命名的文件夹
  2. |-- android --->安卓插件目录
  • 生成插件的aar并放入到android目录下

    • 选择Gradle—->插件module—->Tasks—->build—->assembleRelease编译module的aar文件

      注意:新版本Android studio将assembleRelease放入other中了

      Android 插件开发教程 - 图11

  • 创建package.json文件并填写必要的信息。放入到android目录下

  • 创建libs文件夹。并放入到android目录下

    • 将插件依赖的jar文件放入到libs文件夹中
    • 将插件依赖的.so文件放入到libs文件夹中
  • 将插件依赖的aar文件放入到插件android目录下

生成uni插件压缩包

压缩插件id命名的文件夹为zip即可。具体目录机构如下:

  • 一级目录以插件id命名,对应package.json中的id字段! 存放android文件夹和package.json文件。

Android 插件开发教程 - 图12

  • 二级目录 android 存放安卓插件 .aar 文件 .jar .so放入到libs下

Android 插件开发教程 - 图13

注意:.os文件需要注意 armeabi-v7a、x86 、arm64-v8a以上三种类型的.so必须要有,如果没有无法正常使用!!

提交插件市场

登录注册DCloud插件市场 按提示步骤提交插件(需要编写对应插件的说明文档,md(markdown) 格式)

本地插件提交云端打包

插件注意事项

目前对weex支持的问题

  • Activity的获取方式。通过mWXSDKInstance.getContext()强转Activity。建议先instanceof Activity判断一下再强转
  • .vue暂时只能使用module形式。component还不支持在.vue下使用
  • component、module的生命周回调,暂时只支持onActivityDestroy 、onActivityPause、onActivityResult其他暂时不支持

Tips onActivityResume事件存在缺陷。应用第一次启动无法正常收到onActivityResume事件,后台切换到前台是可以收到的。

示例:

  1. @Override
  2. public void onActivityPause() {
  3. WXLogUtils.e(TAG, "onActivityPause");
  4. }
  5. @Override
  6. public void onActivityResume() {
  7. WXLogUtils.e(TAG, "onActivityResume");
  8. }

第三方依赖库

  • 均要使用compileOnly依赖方式,打包时需要配置或挪动文件到相关文件夹中。 打包插件介绍时会有相关的具体描述!
  • 请参考android平台所有依赖库列表, 编写自己插件时需要查看是否与编译的程序依赖有冲突,防止审核失败或编译失败等问题。
  • 对有些插件需要引用到.so文件,需要特殊配置一下.请参考Android studio添加第三方库和so
  • 代码中用到的JSONObject、JSONArray 要使用com.alibaba.fastjson.JSONArray;com.alibaba.fastjson.JSONObject; 不要使用org.json.JSONObject;org.json.JSONArray 否则造成参数无法正常传递使用等问题。
  • 尽量去下载相关的aar或jar,然后配置到插件包相应文件夹下。aar放到android目录下。jar放到libs目录下。如果不下载也可以。可使用compileOnly修饰,然后将相应的依赖库名称配置到package.json中的dependencies节点下。
  • 第三方库依赖冲突。一种是主app已完整集成相关第三方库。可使用用compileOnly修饰即可。如果主app仅集成了部分第三方库。可参考https://blog.csdn.net/wapchief/article/details/80514880
  • .os文件需要注意 armeabi-v7a、x86 、arm64-v8a以上三种类型的.so必须要有,如果没有无法正常使用!!
  • 插件中包含FileProvider云打包冲突,可通过http://ask.dcloud.net.cn/article/36105此贴配置绕过。
  • 插件中有资源路径返回时,请使用绝对路径file://开头防止不必要的路径转换问题。
  • androidx暂时不支持。请使用v4、v7实现插件。

插件编写命名规范

  • 源代码的package中一定要作者标识防止与其他插件冲突导致插件审核失败,无法上传。 如示例中插件类的“package uni.dcloud.io.uniplugin_richalert;” “dcloud”就是作者标识!
  • Module扩展和Component扩展在引用中的name, 需要前缀加入你自己的标识,防止与其他插件名称冲突。 如示例中的插件“DCloud-RichAlert”!“DCloud”就是标识!

广告插件说明

  • 由于官方 UniAD 广告组件集成了“广点通”和“穿山甲”SDK,目前不支持自行开发包含这两个SDK的原生插件,云打包会导致冲突;

常见问题

Q:云打包后提示”XXX”插件不存在?
A:先确认打包时是否勾选了”XXX”插件。如果勾选了依然报错提示”XXX”插件不存在请联系客服沟通。

Q:插件中怎么跳转原生Activity页面
A:获取WXSDKInstance对象。该对象中可以获取到上下文.通过startActivity跳转

示例

  1. @JSMethod (uiThread = true)
  2. public void gotoNativePage(){
  3. if(mWXSDKInstance != null) {
  4. Intent intent = new Intent(mWXSDKInstance.getContext(), NativePageActivity.class);
  5. mWXSDKInstance.getContext().startActivity(intent);
  6. }
  7. }

Q:插件跳转Activity页面后。Activity页面关闭后有数据需要返回。怎么能实现?
A:可以按以下步骤操作实现:

  • 在插件的WXModule/WXComponent实现onActivityResult方法。通过标识code和参数KEY去区分当前的Result是你需要的返回值

    示例

    1. public static int REQUEST_CODE = 1000; //数据返回标识code
    2. @Override
    3. public void onActivityResult(int requestCode, int resultCode, Intent data) {
    4. if(requestCode == REQUEST_CODE && data.hasExtra("respond")) {
    5. Log.e("TestModule", "原生页面返回----"+data.getStringExtra("respond"));
    6. } else {
    7. super.onActivityResult(requestCode, resultCode, data);
    8. }
    9. }
  • 通过startActivityForResult加上返回标识code跳转其他Activity页面。

    示例

    1. @JSMethod (uiThread = true)
    2. public void gotoNativePage(){
    3. if(mWXSDKInstance != null && mWXSDKInstance.getContext() instanceof Activity) {
    4. Intent intent = new Intent(mWXSDKInstance.getContext(), NativePageActivity.class);
    5. ((Activity)mWXSDKInstance.getContext()).startActivityForResult(intent, REQUEST_CODE);
    6. }
    7. }
  • Activity页面在关闭前调用setResult设置标识code将要返回的参数放进Intent中。

    示例

    1. Intent intent = new Intent();
    2. intent.putExtra("respond", "我是原生页面");
    3. setResult(TestModule.REQUEST_CODE, intent);
    4. finish();

    Q:插件开发支持Androidx吗?
    A:目前插件开发还不支持Androidx.请使用请使用v4、v7实现相关开发。


发现错误?想参与编辑?在 GitHub 上编辑此页面!