Uniapp 自制 Android 原生插件(详细流程,包含打包 aar、本地使用、上传云端)

2025-03-08 20:12:18

一、简介

  • Uniapp 实现有些特殊需求时,可能需要调用原生的一些 API,或者一些原生第三方库等,但是这些库或 API 不支持 Uniapp 能够直接调用到,这个时候可以通过封装一个 Uniapp 可以使用的原生插件,将一些第三方原生库或 API 封装到插件内,并以 Uniapp 的方式暴露出来,使在 Uniapp 开发中能正常调用或使用到这些库或 API

  • 本文会以一个简单的插件需求,整体的走一遍封装 Uniapp 能够使用的原生插件。

  • 原生插件包内支持内部引入 Kotlin 第三方包。

  • 附:Android 原生插件官方文档

  • 附:Uniapp 原生插件的详细使用步骤(本地插件、云端插件)

  • 插件注意事项

    • Activity 的获取方式。通过 mUniSDKInstance.getContext() 强转 Activity 。建议先instanceof Activity 判断一下再强转。

    • .vue 文件内暂时只能使用 module 形式的插件。component 还不支持在 .vue 下使用,需要放到 .nvue 文件中使用。

    • component、module 的生命周回调,暂时只支持 onActivityDestroyonActivityPauseonActivityResult,其他暂时不支持。

    • 插件代码中用到的 JSONObjectJSONArray 要使用com.alibaba.fastjson.JSONArraycom.alibaba.fastjson.JSONObject; 不要使用 org.json.JSONObjectorg.json.JSONArray 否则造成参数无法正常传递使用等问题。

    • 更多的自行看文档吧,列几个常见问题。

二、准备工作

  • 必须走通 Uniapp Android 本地离线打包(详细流程),在这个基础上会更容易理解插件的本地调试与配置,开发工具与环境配置也都在这个文章内包含了。而且只是在这个流程的基础上稍微做下调整,然后再加上导入开发的插件引入与使用步骤。

  • 重要:目前无论是本地插件还是云端插件,都只支持 云打包的基座 运行,离线打包基座也是无法运行出来效果的,只能走 云打包 流程,运行后才能看到插件的效果。

    但有一种情况可以使用离线基座,那就是在开发原生插件的时候使用官方提供的 UniPlugin-Hello-AS 工程,它打包的离线基座可以运行出来插件的效果,但仅限于开发中的,也就是有源码在的这个插件工程里面的。如果这都不能运行,插件都没法开发了。而离线打包的工程 HBuilder-Integrate-AS 则不行,只能走云打包基座。

    上面这种情况说的就是现在这种,咱们准备进行开发插件的工程就是 UniPlugin-Hello-AS 工程。

  • 下载的 Android 原生插件工程目录结构介绍

    image.png

  • UniPlugin-Hello-AS 拖入 Android Studio,遇到运行问题可以参考下面的文章:

    UniPlugin-Hello-AS 工程中:

  • 插件需要本地调试,流程跟离线打包流程差不多,都需要每次调整好 uniapp 代码后再编译出来导入到项目中进行调试。

    image.png

三、开发中的插件如何调试

  • DCloud-RichAlert(uniplugin_richalert) 为例,它现在是个现成的插件,举例一下插件开发完成或开发中,如何进行本地调试。

  • 找到 uniapp 测试项目,打开一个页面文件,添加上插件使用代码:

    
     

    js

    代码解读

    复制代码

  • 然后按离线打包一样,生成本地打包资源

    image.png

    image.png

    image.png

  • 按照 Uniapp Android 离线生成自定义基座(详细流程) 配置一下 dcloud_control.xml,并添加 debug-server-release.aar 到 libs 目录下,配置、添加了即可,不需要往后走流程。

  • 然后按 Uniapp Android 本地离线打包(详细流程) 中的 四、离线工程的调整 进行配置好项目,其实到这里就是离线打包的本地工程配置了,流程一样的,配置好打包运行即可。

    唯独在配置 AppKey 的时候,发现顶部并没有 包名 需要配置,这里可以不需要配置也没问题,只需要将下面的 AppKey 配置好即可。

    image.png

  • 然后运行项目到模拟器即可,如果没有打开后没有生效,没有弹出窗口,可以将设备上安装的 app 删除重新运行,一般这样就解决了。

    image.png

四、Java 开发一个简单的 module 扩展(无界面原生插件)

  • 第一步:创建 Android StudioModule 模块

    image.png

    image.png

    Module name:app:mylibrary 的意思就是在 app 目录下新建一个 mylibrary 模块,如果要跟官方结构保持一致在 app 目录外面,直接保留 mylibrary 即可,为了跟官方保持命名统一,可以改成 uniplugin_mylibrary

    image.png

    image.png

    image.png

    创建好 Module 后需要配置一下它的 build.gradle 文件,最简单的就是拷贝一下其他插件的,推荐拷贝 uniplugin_richalert 的,看起来它的依赖比较干净。注意备份新建插件 build.gradlenamespace,拷贝覆盖后,在修改为它自己的。

    image.png

    image.png

    然后修改 namespace 为这个插件新建时候的包名

    image.png

  • 第二步:在 app 目录的 build.gradle 中引入新建的插件

    image.png

    然后编译代码是否报错,没有报错不用管,我看其他插件也没配置,如果有报错 Failed to calculate the value of property 'namespace',可以添加配置一下插件的命名空间:

    image.png

    细节:每次修改配置后,如何快速生效,点一下工具顶部显示的 Sync Now 然后等待即可:

    image.png

  • 第三步:创建插件入口文件

    需要给插件创建一个入口文件,文件名随便起,也可以参考其他插件的 XXXXModule(例如:RichAlertModule)

    image.png

    回车即可创建:

    image.png

    然后继承 UniModule

    
     

    java

    代码解读

    复制代码

    package com.example.uniplugin_mylibrary; import io.dcloud.feature.uniapp.common.UniModule; public class MylibraryModule extends UniModule { }

    image.png

  • 第四步:到 app 目录 dcloud_uniplugins.json 中配置插件信息

    首先拷贝入口文件的 class

    image.png

    image.png

    找到 app 目录下的 dcloud_uniplugins.json 进行配置插件:

    image.png

    到这插件配置相关的就完成了,现在主要就是实现代码了。

  • 第五步:添加一份简单的调试代码

    
     

    java

    代码解读

    复制代码

    package com.example.uniplugin_mylibrary; import android.util.Log; // JSONObject 需要使用 com.alibaba.fastjson 这个 import com.alibaba.fastjson.JSONObject; import io.dcloud.feature.uniapp.annotation.UniJSMethod; import io.dcloud.feature.uniapp.bridge.UniJSCallback; import io.dcloud.feature.uniapp.common.UniModule; public class MylibraryModule extends UniModule { // 日志标识,后续可以通过日志表示快速排查问题 private String Tag = "MylibraryModule"; // 私有方法 private int add(int a, int b) { return a + b; } // 对外暴露的方法需要添加 @UniJSMethod (uiThread = true/false),并且设置为public 。 // uiThread: 是否在 UI 线程执行。 // JSONObject options: 可以接收 UniApp 传过来的数据,也可以为空。 // callback: 回调的时候,可以用 JSONObject 存放数据,回调 json 数据,但是某些时候回调不到,可以切换成 Map。 @UniJSMethod(uiThread = false) public void testAdd (JSONObject options, UniJSCallback callback) { Log.e(Tag, "TestAddFunc:" + options); int sum = add(10, 8); if (callback != null) { JSONObject data = new JSONObject(); data.put("code", "success"); data.put("sum", sum); // 组装好的数据通过回调外抛给 uniapp callback.invoke(data); } } }
  • @UniJSMethod(uiThread = false) 使用分析:

    UniApp 的自定义封装组件中,@UniJSMethod 注解用于标记方法,使其可以被 JavaScript 调用。注解中的 uiThread 参数决定了方法运行的线程,具体作用如下:

    
     

    java

    代码解读

    复制代码

    @UniJSMethod(uiThread = true) public void updateUI() { // 更新UI操作,需要运行在主线程 } @UniJSMethod(uiThread = false) public String performBackgroundTask() { // 耗时操作,比如读取文件或网络请求 return "Task Done"; }
    • uiThread = true(默认)

      • 运行线程:在主线程(UI 线程)执行。

      • 适用场景:如果方法需要直接操作与 UI 相关的内容(如更新视图或触发动画),需要在 UI 线程中运行。

      • 优点:确保与 UI 的操作线程安全。

      • 缺点:如果方法执行耗时操作,可能导致 UI 卡顿,影响用户体验。

    • uiThread = false

      • 运行线程:在工作线程中执行。

      • 适用场景:用于执行耗时的任务,例如网络请求或复杂计算,避免阻塞主线程。

      • 优点:不会影响 UI 响应速度。

      • 注意事项:如果方法需要操作 UI,必须切换到主线程,否则可能会出现线程冲突或崩溃。

    • 总结:

      • uiThread = true 适合与 UI 相关的任务。

      • uiThread = false 适合耗时任务,可以避免阻塞 UI

  • 没遇到可跳过,导入 Uni 相关插件报错找不到解决方案,细节问题:

    需要把代码改不会报错的情况下,点这个 Sync Now 按钮,要不然依然不会生效,也可以通过修改插件的 build.gradle 中的 namespace 'com.example.uniplugin_mylibrary' 然后保存,重新唤起配置按钮点击生效。

    image.png

  • 第六步:使用插件

    
     

    js

    代码解读

    复制代码

    使用后,重新按离线打包一样,记得先通过工具栏的 Build -> 先 Clean Project -> 再 Rebuild Project -> 最后在打包或运行,生成基座或者测试离线包都适用这套流程。

    生成本地打包资源,导入到 Android Studroid 重新运行到手机上,如果没生效,可以将手机上之前安装的 app 删除再运行一次。

    如果遇到这种 Test 文件报错的,直接删掉,留着主文件夹就行了,如果需要用它可以自己修好,这两个测试文件夹本来就是可有可无的。

    image.png

  • 调试细节:

    • 方式一:直接用 Android Studio 运行打包好的 UniApp 离线代码。

    • 方式二:按照自定义基座一样,将 Android Studio 编译好的 apk 丢到 UniApp 项目中运行基座到手机或模拟器,这样可以两边都能看到之前输出的调试日志,效果图:

      image.png

五、Java 开发一个简单的 componet 扩展(有界面原生插件)

  • 可以了解下 Uniapp .vue 与 .nvue 区别与书写区别;混搭使用与场景注意事项;.nvue 对原生界面支持比较好,如果想要在 .vue 文件中引入安卓原生界面组件的话,推荐放在 .nvue 文件中引用,不建议放在 .vue 文件内引用使用,可能会出别的兼容问题。

  • 所以这里准备做一个 componet 扩展(有界面原生插件),那么则需要放到 .nvue 文件中使用,因此需要修改一下当前页面的文件后缀。

  • 第一步:创建 Android StudioModule 模块

    同第 四、Java 开发一个简单的 module 扩展(无界面原生插件) 一致。

  • 第二步:在 app 目录的 build.gradle 中引入新建的插件

    同第 四、Java 开发一个简单的 module 扩展(无界面原生插件) 一致。

  • 第三步:创建插件入口文件

    同第 四、Java 开发一个简单的 module 扩展(无界面原生插件) 一致,只是继承对象不同。

    需要给插件创建一个入口文件,文件名随便起,也可以参考其他插件的 XXXXComponent(例如:MylibraryComponent),然后继承 UniComponent

    
     

    java

    代码解读

    复制代码

    package com.example.uniplugin_mylibrary; import android.widget.TextView; import io.dcloud.feature.uniapp.UniSDKInstance; import io.dcloud.feature.uniapp.ui.action.AbsComponentData; import io.dcloud.feature.uniapp.ui.component.AbsVContainer; import io.dcloud.feature.uniapp.ui.component.UniComponent; public class MylibraryComponent extends UniComponent { // 构造函数 // 在 Android Studio 中,快捷添加 构造函数 (Constructor) 非常简单,快捷键: // Windows/Linux:Alt + Insert // macOS:Command + N public MylibraryComponent(UniSDKInstance uniSDKInstance, AbsVContainer absVContainer, AbsComponentData absComponentData) { super(uniSDKInstance, absVContainer, absComponentData); } }

    image.png

    image.png

  • 第四步:到 app 目录 dcloud_uniplugins.json 中配置插件信息

    同第 四、Java 开发一个简单的 module 扩展(无界面原生插件) 一致。

    image.png

    到这插件配置相关的就完成了,现在主要就是实现代码了。

  • 第五步:添加一份简单的调试代码

    
     

    java

    代码解读

    复制代码

    package com.example.uniplugin_mylibrary; import android.content.Context; import android.graphics.Color; import android.widget.TextView; import androidx.annotation.NonNull; import io.dcloud.feature.uniapp.UniSDKInstance; import io.dcloud.feature.uniapp.annotation.UniJSMethod; import io.dcloud.feature.uniapp.ui.action.AbsComponentData; import io.dcloud.feature.uniapp.ui.component.AbsVContainer; import io.dcloud.feature.uniapp.ui.component.UniComponent; import io.dcloud.feature.uniapp.ui.component.UniComponentProp; public class MylibraryComponent extends UniComponent { // 构造函数 // 在 Android Studio 中,快捷添加 构造函数 (Constructor) 非常简单,快捷键: // Windows/Linux:Alt + Insert // macOS:Command + N public MylibraryComponent(UniSDKInstance uniSDKInstance, AbsVContainer absVContainer, AbsComponentData absComponentData) { super(uniSDKInstance, absVContainer, absComponentData); } // 创建UI组件,直接输入 init 会自动出来初始化函数 @Override protected TextView initComponentHostView(@NonNull Context context) { TextView textView = new TextView(context); textView.setTextSize(30); textView.setTextColor(Color.RED); return textView; } // 给UI组件添加可以设置的属性 tel @UniComponentProp(name = "tel") public void setTel(String telNumber) { getHostView().setText("tel:" + telNumber); } // 给UI组件添加可以调用的方法,默认 uiThread = true ,所以不写就是默认 @UniJSMethod public void clearTel () { getHostView().setText(""); } }
  • 第六步:使用插件

    UI 插件 不需要引入,直接使用即可:

    
     

    js

    代码解读

    复制代码

    编译细节同第 四、Java 开发一个简单的 module 扩展(无界面原生插件) 一致。

  • 调试细节:

    同第 四、Java 开发一个简单的 module 扩展(无界面原生插件) 一致,效果图:

    image.png

六、Android Studio 删除插件

  • 新建好的插件,需要移除,右键没找到 Delete 按钮,因为组件文件不能直接删除,需要先移除组件,变成普通文件夹才可以删除。

  • 先移除配置,这样避免删除后报错:

    image.png

  • 然后移除组件,记得顶部切换到 Project 展示模式,不要使用 Android 展示进行移除。

    image.png

    image.png

  • 按上面操作之后,文件夹还在,但是变成了普通文件夹。看下面两个文件夹的图标不一样了,这就变成了普通文件夹,然后右键就有删除按钮了,删除即可。

    如果没有生效,可以看下 工具上面顶部弹出的蓝色提示条上的按钮,点一点

    image.png

七、插件混合开发

  • 一个插件包下面,是可以存在多个 模块(Module)或组件(Component)的,上面的案例是拆开单个讲解而已,其实放一起也没问题的,无非多配置一个。

    image.png

  • 配置好后重新打个包导入运行效果是一样的,而且两个插件模块都会生效。

    
     

    html

    代码解读

    复制代码

八、打包插件(导入本地插件使用)

  • 找到 Android Studio 右侧菜单栏上的 Gradle,打开需要打包的插件目录,找到打包按钮。

    image.png

    在打开这个菜单后,一般是在 Gradle -> uniplugin_mylibrary -> Tasks -> other 里面有 assembleDebugassembleRelease 按钮的,点一下可以直接构建测试与正式版本的 aar,结果跟下面是一样的。

    尝试了下没找到这两个按钮,那么也没事,其实这两个选项也就是执行了两个命令,找不到不会自己执行一下么。

  • 一般在项目根目录有一个 gradlew 文件,通过它就可以执行 assembleDebugassembleRelease 命令:

    image.png

    然后打开 Android Studio 底部的 Terminal 手动执行命令:

    image.png

    这里会分两种情况:

    1、如果当前工程是一个模块工程,那么可以直接执行命令

    
     

    sh

    代码解读

    复制代码

    # Debug 构建 $ ./gradlew assembleDebug # Release 构建 $ ./gradlew assembleRelease # 其他命令(知道就行) # 清理项目 $ ./gradlew clean # 列出当前模块支持的 Gradle 任务,检查是否有 `assembleDebug` 或 `assembleRelease` $ ./gradlew tasks # 如果 Gradle 配置有问题,运行以下命令强制重新加载: $ ./gradlew --refresh-dependencies assembleRelease

    2、如果当前工程是一个正常项目工程,然后在项目工程内新建了模块,就类似我上面截图的这种,则需要这么执行:

    
     

    sh

    代码解读

    复制代码

    your-project/ ├── gradlew ├── settings.gradle ├── mylibrary/ │ ├── build.gradle ├── app/ │ ├── build.gradle
    
     

    sh

    代码解读

    复制代码

    # Debug 构建 $ ./gradlew :mylibrary:assembleDebug # Release 构建 $ ./gradlew :mylibrary:assembleRelease # :mylibrary 冒号后面的表示模块名称。 # 如果模块名称与你的实际模块不同,请检查模块 settings.gradle 中的模块声明。 # 其他命令(知道就行,使用同理)
  • 执行命令后,发现报错没执行权限 zsh: permission denied: ./gradlew,添加执行权限:

    
     

    sh

    代码解读

    复制代码

    $ chmod +x ./gradlew
  • 添加后再次执行,运行成功:

    image.png

  • 拿到 .aar 文件后,那么安卓的自定义插件也就开发完成了,然后到 uniapp 项目中根目录找到 nativeplugins 文件夹,没则自己手动创建。然后按照官方的文件夹结构创建创建一下,不清楚结构可以下载一个官方的插件 DCloud-RichAlert,下离线包即可,可以参考 package.json 配置。

    image.png

    调整好后,丢入自己的插件包:

    image.png

    拖入 uniapp 项目中 nativeplugins 文件夹内,然后修改 package.json。附 UniApp 官方原生插件开发 package.json 书写规则与插件结构文档,只需添加需要的配置即可,不需要全部配置,可以参考官方上传的插件配置。

    image.png

  • 配置好后,到配置中添加一下本地插件,插件这里显示的名称是 package.json 中的 name 字段,名字随意起。勾选添加一下插件,然后打个自定义基座运行即可,本地插件云插件 都是需要运行在 自定义基座 上面的,标准基座 是会报找不到的插件的。

    插件(包含云插件、本地插件) 只能通过 云打包的基座 运行。

    image.png

    上面显示支持 Android / iOS 是刚才 package.json 配置的,移除 iOS 的配置就只有安卓了。

  • 最后重新打一个包,运行看看效果,

    注意必须使用云基座打包才能看到效果,放到离线打包工程中运行时看不到效果的哦。推荐删除安装好的 app,在用基座运行,免得没生效。

    打包就用公共证书速度走,正式包了再填信息。

    image.png

  • 运行成功:

    image.png

九、上传到云端插件

  • 将刚才的插件文件夹,压缩成 .zip 就行。

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

    登录后,点击自己的昵称,就能进入 我的插件 页面,然后按着填信息,上传插件压缩包。

  • 后面没啥东西了,看着来就行了。


转自:https://juejin.cn/post/7439935260241018930
 

发表评论:

Powered by PHP 学习者(mail:517730729@qq.com)

原百度博客:http://hi.baidu.com/ssfnadn

备案号:闽ICP备17000564号-1

开源中国 PHPCHINA