插件开发指南
更新: 2026/11/7 字数: 0 字 时长: 0 分钟
开发环境准备
要开发 Android 插件,推荐使用 Android Studio 作为主要开发工具。请确保你已完成以下准备工作:
- 安装最新版本的 Android Studio
- 正确配置 SDK 和 Gradle 构建环境
- 具备基础操作能力,例如:
- 创建 Android 项目
- 添加依赖和模块
- 编译与打包 APK
如果你是第一次使用 Android Studio,建议先阅读 官方入门文档。
如何开发一个插件
本示例中的包名均为 org.botjs.plugin.sdk.demo,在实际项目中插件包名可能有所不同。
插件SDK集成
新建一个 Android 项目。在 settings.gradle.kts(或项目根 build.gradle.kts)中添加 JitPack 仓库:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}如果项目仍使用 allprojects 方式配置仓库,可改为:
allprojects {
repositories {
// ...
maven { url = uri("https://jitpack.io") }
}
}在具体模块(比如 app)的 build.gradle.kts 中添加依赖:
dependencies {
// ...
implementation("com.github.BotJsPro:Bot.js-Plugin-SDK:1.0.0")
}更多信息参见 Jitpack.io。
关于 compileOnly
如果宿主/插件框架在运行时保证提供同一份 SDK 类(例如插件代码被动态加载到宿主进程,与宿主共享 ClassLoader),可以改为 compileOnly,避免插件 APK 重复打包 SDK:
compileOnly("com.github.BotJsPro:Bot.js-Plugin-SDK:1.0.0")但若插件 APK 的 PluginService 运行在独立进程、且该进程不能从宿主获得 SDK 类,则仍需 implementation,让插件 APK 自己能加载 PluginService / Plugin 等基类。
插件配置
1. 新建 PluginHelloWorld 文件,继承于 Plugin.
public class PluginHelloWorld extends Plugin {
public PluginHelloWorld(Context context, Context selfContext, Object runtime, Object topLevelScope) {
super(context, selfContext, runtime, topLevelScope);
}
// 返回插件的 JavaScript 胶水层代码的 assets 目录路径
@Override
public String getAssetsScriptDir() {
return "js-plugin";
}
// 插件 public API,可被 JavaScript 代码调用
public String getStringFromJava() {
return "Hello, Bot.js Pro!";
}
// 插件 public API,可被 JavaScript 代码调用
public void say(String message) {
getSelfContext().startActivity(new Intent(getSelfContext(), HelloWorldActivity.class)
.putExtra("message", message)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
// 启动普通后台服务(可选)
public boolean startMyService() {
Intent serviceIntent = new Intent();
serviceIntent.setComponent(new ComponentName(getSelfContext(), MyService.class));
return getSelfContext().startService(serviceIntent) != null;
}
// 停止普通后台服务(可选)
public boolean stopMyService() {
Intent serviceIntent = new Intent();
serviceIntent.setComponent(new ComponentName(getSelfContext(), MyService.class));
return getSelfContext().stopService(serviceIntent);
}
}2. 新增 MyPluginRegistry 文件,继承于 PluginRegistry:
public class MyPluginRegistry extends PluginRegistry {
static {
PluginLoader loader = new PluginLoader() {
@Override
public Plugin load(Context context, Context selfContext, Object runtime, Object topLevelScope) {
return new PluginHelloWorld(context, selfContext, runtime, topLevelScope);
}
};
// 供 $plugins.load("org.botjs.plugin.sdk.demo") 使用,注册名要与加载名一致
registerPlugin("org.botjs.plugin.sdk.demo", loader);
// 可选:同时注册为默认插件,供默认加载路径使用
registerDefaultPlugin(loader);
}
}注册名必须与加载名一致
$plugins.load(name) 最终按 PluginRegistry.load(name, ...) 查找,只匹配通过 registerPlugin(name, loader) 注册的 loader。registerDefaultPlugin(loader) 注册的是 "::default",只影响默认加载路径,不会让 $plugins.load("org.botjs.plugin.sdk.demo") 命中。如果只调用 registerDefaultPlugin,宿主应走默认加载路径,或补上 registerPlugin("org.botjs.plugin.sdk.demo", loader)。
在 AndroidManifest.xml 中配置以下 meta-data,name 为 "org.botjs.plugin.sdk.registry",value 为 MyPluginRegistry 的完整类名。
<application
...>
<meta-data
android:name="org.botjs.plugin.sdk.registry"
android:value="org.botjs.plugin.sdk.demo.MyPluginRegistry" />
<activity
android:name=".HelloWorldActivity"
android:exported="true" />
<service
android:name=".HelloworldPluginService"
android:exported="true" />
<service
android:name=".MyService"
android:exported="false" />
</application>3. 编写 JavaScript 胶水层
在 assets 的相应目录(由 Plugin.getAssetsScriptDir() 返回)中添加 index.js 文件,用于作为胶水层导出插件 API。
module.exports = function (plugin) {
let runtime = plugin.runtime;
let scope = plugin.topLevelScope;
function helloWorld() {
}
// 纯数据属性,调用时不要加括号
helloWorld.getAssetsScriptDir = plugin.getAssetsScriptDir();
helloWorld.stringFromJava = plugin.getStringFromJava();
// 方法
helloWorld.say = function (message) {
plugin.say(message);
};
helloWorld.remoteAdd = function (a, b) {
return plugin.waitForConnection().call('add', {
a: a,
b: b
}, null).get('sum');
};
helloWorld.startMyService = function () {
return plugin.startMyService();
};
helloWorld.stopMyService = function () {
return plugin.stopMyService();
};
return helloWorld;
};说明:
plugin.runtime/plugin.topLevelScope在 Rhino 中可直接按属性访问(等价于getRuntime()/getTopLevelScope()),示例中未使用可省略。
4. 在 Bot.js Pro 中调用
编译插件为 apk(assembleDebug / assembleRelease),安装到设备上。在 Bot.js Pro 中使用以下代码调用:
let helloWorld = $plugins.load("org.botjs.plugin.sdk.demo");
console.log(helloWorld.stringFromJava);
helloWorld.say("Hello, Bot.js Pro Plugin");5. 独立服务 AIDL 方式调用
可以在插件中编写一个 Service,由 Bot.js Pro 唤起和绑定,并可在 js 中通过 AIDL 调用 Service 的接口。
在 Plugin 中重写方法 getService。
// 插件服务类,可选,用于 AIDL 方式和 Bot.js Pro 本体通信。可返回 null
@Override
public ComponentName getService() {
return new ComponentName(getSelfContext().getPackageName(), HelloworldPluginService.class.getName());
}新建一个 Service 组件(注意在 AndroidManifest 中注册时需要是 exported="true"),继承于 PluginService。
public class HelloworldPluginService extends PluginService {
private static final String ACTION_ADD = "add";
@Override
protected Result onRemoteCall(@NonNull String action, @NonNull Map<String, Object> args, @Nullable RemoteCallback callback) throws RuntimeException {
switch (action) {
case ACTION_ADD:
return invokeAdd(args);
}
return Result.notImplemented(action);
}
private Result invokeAdd(Map<String, Object> args) {
Number a = PluginUtils.getNotNull(args, "a");
Number b = PluginUtils.getNotNull(args, "b");
double sum = a.doubleValue() + b.doubleValue();
return new Result(Collections.singletonMap("sum", sum));
}
}在 index.js 中添加胶水层代码:
helloWorld.remoteAdd = function (a, b) {
return plugin.waitForConnection().call('add', {
a: a,
b: b
}, null).get('sum');
};然后可以在 Bot.js Pro 中调用:
let helloWorld = $plugins.load("org.botjs.plugin.sdk.demo");
console.log(helloWorld.remoteAdd(1, 2));6. 普通后台服务启停(可选)
除了 AIDL 方式,插件也可以启动/停止一个普通后台服务。新建 MyService 继承于 PluginService(onBind 返回 null 即不对外提供 Binder 调用):
public class MyService extends PluginService {
private static final String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: 服务已创建");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: 服务已启动");
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: 服务已销毁");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
protected Result onRemoteCall(@NonNull String var1, @NonNull Map<String, Object> var2, @Nullable RemoteCallback var3) throws RuntimeException {
return null;
}
}在 PluginHelloWorld 中通过 startService / stopService 控制:
public boolean startMyService() {
Intent serviceIntent = new Intent();
serviceIntent.setComponent(new ComponentName(getSelfContext(), MyService.class));
return getSelfContext().startService(serviceIntent) != null;
}
public boolean stopMyService() {
Intent serviceIntent = new Intent();
serviceIntent.setComponent(new ComponentName(getSelfContext(), MyService.class));
return getSelfContext().stopService(serviceIntent);
}在 index.js 中导出:
helloWorld.startMyService = function () {
return plugin.startMyService();
};
helloWorld.stopMyService = function () {
return plugin.stopMyService();
};在 Bot.js Pro 中调用:
let helloWorld = $plugins.load("org.botjs.plugin.sdk.demo");
console.log(helloWorld.startMyService());
console.log(helloWorld.stopMyService());返回值只表示
startService()/stopService()调用是否成功,不代表服务已进入运行状态或已完全停止。
完整调用示例
将上面所有能力串起来,一个完整的调用脚本如下:
// ============================================================
// Bot.js Pro 插件完整调用示例
// 对应插件包名: org.botjs.plugin.sdk.demo
// ============================================================
// 1. 加载插件,返回 index.js 胶水层导出的 API 对象
let helloWorld = $plugins.load("org.botjs.plugin.sdk.demo");
console.log("插件加载成功");
// 2. 读取由 Java 层提供的纯数据属性(不要加括号)
console.log("assets 目录: " + helloWorld.getAssetsScriptDir);
console.log("Java 字符串: " + helloWorld.stringFromJava);
// 3. 调用 Java 方法(会在设备上弹出 HelloWorldActivity)
helloWorld.say("Hello, Bot.js Pro Plugin");
// 4. AIDL 远程调用:调用插件 Service 的 "add",同步返回结果
let sum = helloWorld.remoteAdd(10, 32);
console.log("remoteAdd(10, 32) = " + sum);
// 5. 启动 / 停止普通后台服务 MyService
console.log("startMyService() = " + helloWorld.startMyService());
console.log("stopMyService() = " + helloWorld.stopMyService());
console.log("所有插件 API 调用示例执行完毕");预期控制台日志:
插件加载成功
assets 目录: js-plugin
Java 字符串: Hello, Bot.js Pro!
remoteAdd(10, 32) = 42
startMyService() = true
stopMyService() = true
所有插件 API 调用示例执行完毕
say()会启动HelloWorldActivity,该界面不会出现在控制台日志中。
注意事项
- 注册名必须与加载名一致:
$plugins.load(name)只匹配registerPlugin(name, loader);registerDefaultPlugin(loader)注册的是"::default",只影响默认加载路径。 - 需要 AIDL 调用时要重写
getService():Plugin.getService()基类默认返回null,不重写就不会建立 Binder 连接,waitForConnection()也会一直等待。 - 跨进程参数只能传 Parcel 支持的类型:基本类型、
String、Bundle、Parcelable等,不要传 JS 函数、不可序列化对象或超大 Map。 - Service 安全:同应用/同 UID 可将 Service 设为
android:exported="false";跨应用宿主需要 bind 时,通常需要android:exported="true"并加签名级权限,同时在 Binder 调用入口校验调用方 UID。 - 线程模型:
onRemoteCall运行在 Binder 线程,不能做 UI 操作或长耗时任务;Connection.call()是同步阻塞调用,不要在高频循环中调用。
