上一篇讲了怎么接 Agent Framework,让系统智能助手能调用应用功能。但最近遇到一个新问题:我们应用内部有很多模块(笔记、待办、日程),现在想把这些模块能力开放给系统级的其他应用调用,同时又不想把整个应用都暴露出去。一开始想用 AIDL 或者跨设备调用,结果发现 HarmonyOS 7 提供了 ModularObjectExtensionAbility,专门干这个的。
这个能力说实话文档不多,官方 Demo 也比较简单,我们自己踩了不少坑才跑通。这篇就把整个过程记录下来。
一、真实开发中遇到的问题
产品的需求是这样的:我们的笔记应用有一个"快速记笔记"的功能,现在希望系统的日历应用、备忘录应用都能调起这个功能,往我们的应用里写一条笔记。不是打开整个 App,而是直接调用"创建笔记"这个能力。
一开始的想法是:用 FA 的跨设备调用,把整个 Ability 暴露出去。但问题来了------调用方要跳转到我们的某个页面,体验很差,而且无法直接返回数据给调用方。
后来发现 ModularObjectExtensionAbility 就是干这个的:它是一个无界面的扩展能力,接收调用方的参数,执行内部逻辑,返回结果。不需要跳转页面,体验是无感的。
但实际接入的时候,问题也不少:怎么定义开放的能力?参数怎么传?调用方怎么知道你这个能力存在?权限怎么控制?这些都得自己理清楚。
二、这个能力怎么接入
ModularObjectExtensionAbility 的核心思路是:把应用内部的功能封装成一个"模块化对象",通过 ExtensionAbility 的方式暴露给系统。其他应用可以通过 Want 指定要调用的模块化对象名称,然后传入参数调用。
接入分三步:
1. 定义模块化对象:在应用内部写一个类,封装要开放的功能。比如"笔记管理"这个模块,提供创建笔记、查询笔记的方法。
2. 注册 ExtensionAbility:在 module.json5 里注册 ModularObjectExtensionAbility,声明它提供了哪些模块化对象。
3. 实现调用入口:在 ExtensionAbility 的 onCall 方法里,根据调用方传来的对象名和方法名,分发到对应的模块化对象执行。
这里的关键是:模块化对象本身是应用内部的普通类,只是通过 ExtensionAbility 暴露了一个调用入口。调用方不需要知道你内部怎么实现的,只需要知道对象名、方法名和参数格式。

三、关键代码怎么写
下面是我们实际用的代码。先定义模块化对象,文件位置在 entry/src/main/ets/modular/NoteModule.ets:
typescript
// 模块化对象:笔记管理
export class NoteModule {
// 创建笔记
async createNote(title: string, content: string): Promise<NoteResult> {
// 内部业务逻辑:写入数据库
const note = {
id: Date.now().toString(),
title: title,
content: content,
createTime: new Date().toISOString()
};
await db.insert('notes', note);
return {
code: 200,
noteId: note.id,
message: '笔记创建成功'
};
}
// 查询最近笔记
async getRecentNotes(limit: number = 10): Promise<NoteResult[]> {
const notes = await db.query('notes', { limit: limit });
return notes.map(note => ({
code: 200,
noteId: note.id,
title: note.title
}));
}
}
// 返回结果类型
interface NoteResult {
code: number;
noteId?: string;
title?: string;
message: string;
}
这个 NoteModule 就是应用内部的一个普通业务类,有自己的方法。关键是怎么把它暴露出去。
然后是 ExtensionAbility,文件位置在 entry/src/main/ets/modular/NoteModularAbility.ets:
typescript
import { ModularObjectExtensionAbility } from '@ohos.app.ability.ModularObjectExtensionAbility';
export default class NoteModularAbility extends ModularObjectExtensionAbility {
// 模块化对象注册表
private modules: Map<string, object> = new Map();
onCreate(want: Want) {
super.onCreate(want);
// 初始化时注册所有模块化对象
this.modules.set('NoteModule', new NoteModule());
console.info('NoteModularAbility onCreate');
}
// 处理跨应用调用
async onCall(
want: Want,
parameters: Record<string, Object>
): Promise<Record<string, Object>> {
const moduleName = want.parameters?.moduleName as string;
const methodName = parameters?.method as string;
console.info(`Call module: ${moduleName}, method: ${methodName}`);
// 校验模块是否存在
const module = this.modules.get(moduleName);
if (!module) {
return { code: 404, message: `Module not found: ${moduleName}` };
}
try {
// 根据方法名调用对应的方法
const method = (module as any)[methodName];
if (typeof method !== 'function') {
return { code: 400, message: `Method not found: ${methodName}` };
}
// 提取参数
const args = parameters?.args || [];
const result = await method.apply(module, args);
// 返回结果
return {
code: 200,
data: result
};
} catch (e) {
console.error(`Call failed: ${e.message}`);
return {
code: 500,
message: `Internal error: ${e.message}`
};
}
}
onDestroy() {
// 清理资源
this.modules.clear();
}
}
这段代码的核心:在 onCreate 里把所有模块化对象注册到 Map 里。onCall 方法接收调用方的请求,根据 moduleName 和 methodName 分发到对应的方法执行,然后把结果返回给调用方。

四、运行过程中怎么处理异常
跨应用调用的异常处理比应用内调用更复杂,因为你面对的是不可控的外部调用方。我们总结了几个必须处理的异常点:
1. 模块不存在:调用方传了一个不存在的模块名,直接返回 404。不要尝试模糊匹配,不然安全边界就模糊了。
2. 方法不存在:模块存在但方法名不对,返回 400。同样不要做自动猜测。
3. 参数校验:调用方传的参数可能缺字段、类型不对。在每个方法内部做严格校验,不要信任外部传入的数据。
4. 业务异常:比如数据库写失败、网络超时,这些都要 catch 住,转换成错误码返回。不要让异常冒泡导致 ExtensionAbility 崩溃。
5. 权限校验:这是最重要的。不是所有应用都能调用你的能力。要在 onCall 里检查调用方的应用包名,确认是授权过的应用才允许调用。
五、实际开发中容易忽略的问题
1. module.json5 的配置:ModularObjectExtensionAbility 的注册要配置 type 和 metadata。漏了配置的话,系统根本发现不了你的扩展能力。metadata 里要写清楚你提供了哪些模块化对象。
2. 不要在 onCall 里做重活:onCall 是在 ExtensionAbility 的线程里执行的,太重的任务要丢到子线程。不然阻塞了整个扩展能力,其他调用方也会受影响。
3. 模块化对象要单例:我们的 NoteModule 在 onCreate 里创建一次,后续所有调用都用同一个实例。不要每次调用都 new 一个新的,浪费资源而且状态不一致。
4. 版本兼容:对外暴露的方法要做版本控制。比如 createNote 方法,v1.0 接受 2 个参数,v1.1 加了第三个参数。调用方传的参数数量不对要做兼容处理,不能直接报错。
5. 安全边界:模块化对象暴露的能力是跨应用的,要像写公共 API 一样严谨。不要在模块方法里做权限外的事情,也不要把敏感数据返回给未授权的调用方。
整个接入过程大概花了四五天。刚开始觉得文档少、能力抽象,理清了"模块化对象注册 + onCall 分发"这个核心模式之后,后面加新的模块就很简单了------写个业务类,注册到 Map 里就行。鸿蒙的系统能力确实在往"应用能力开放"这个方向走,提前把这条路打通了,后面做生态联动才有基础。