HarmonyOS 7 系统能力深度实战 02:用模块化对象开放应用内部能力

上一篇讲了怎么接 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 里就行。鸿蒙的系统能力确实在往"应用能力开放"这个方向走,提前把这条路打通了,后面做生态联动才有基础。

相关推荐
xq952711 小时前
ArkTS Component Generator 插件横空出世
harmonyos
Fate_I_C11 小时前
Capacitor 插件鸿蒙化实战:@capacitor/device 从安装到模拟器验证全记录
华为·harmonyos
2501_9197490317 小时前
华为鸿蒙免费记账与生活统计APP—小羊统计
华为·生活·harmonyos·鸿蒙
Fate_I_C17 小时前
Capacitor 应用鸿蒙化实战:用 hionic 把 React 应用跑在 OpenHarmony 上
react.js·华为·harmonyos
SuperHeroWu717 小时前
华为云码道接入 DevEco CLI 鸿蒙应用开发AICoding
华为·华为云·harmonyos
ChinaDragonDreamer18 小时前
HarmonyOS:User Authentication Kit简介
华为·harmonyos
m0_7381858218 小时前
Flutter 鸿蒙化实战:qrcode_flutter 适配 OpenHarmony,二维码生成与识别
数码相机·flutter·华为·harmonyos·鸿蒙
万物智能信息科技18 小时前
血氧心跳传感器MAX30100芯片驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
人工智能·驱动开发·华为·开源·harmonyos·鸿蒙
老陈说编程18 小时前
1. 鸿蒙 (HarmonyOS) 2012 至 2026 年的发展历程
分布式·华为·个人开发·harmonyos·鸿蒙·鸿蒙系统·程序员创富