AbilityMeta 能力元信息:不只是能调用,还要能看懂
AbilityMeta 能力元信息:不只是能调用,还要能看懂
这是「ASCF 架构升级」系列的第 4 篇,对应提交
5825abb的另一半。
上一篇讲了为什么用NativeAbilityRegistry取代 if-else;这一篇专门讲那张「报到表」的另一列:AbilityMeta—— 能力的说明书。
一、先想一个问题:「action → handler」够不够
上一篇里的注册表是这样的:
register(action:string,handler:NativeAbilityHandler):void一个名字、一个函数。这样能跑吗?能。H5 报getDeviceInfo,注册表查到对应函数、调用、返回结果。作为「能让 H5 调通」的最小集合,这一对足够了。
但 demo 跑起来之后,下面这些问题立刻冒出来:
- 调试面板想列「目前底座支持哪些能力」 —— 没地方读
- README 想给读者展示一张能力清单 —— 得人工抄
- 想做权限校验:「这个 H5 想调相册,它配不配?」 —— 没有元信息可查
- BridgeLog 想标注「这条调用属于 device 类、是 mock 实现」 —— 也无从查起
- 想做能力开关:「上线先把支付这个能力关掉」 —— 不知道往哪儿挂状态
这些问题的共性是:光知道「能调」还不够,得知道「这是个什么东西」。
对应到产品话术,就是「能力得有说明书」。
二、AbilityMeta是什么
文件在entry/src/main/ets/bridge/AbilityMeta.ets:
exportinterfaceAbilityMeta{action:string;namespace:string;description:string;permission:string;mock:boolean;enabled:boolean;}可以理解为「能力的身份证」:以前注册表只记一个名字,现在每个能力都带一张证件,正反面写满字段。
逐字段拆解:
action—— H5 调用时报的名字,跟BridgeAction常量一一对应,比如'getDeviceInfo'。
namespace—— 能力分类,例如device / runtime / ui / clipboard / location。等调试面板做出来,能力会按 namespace 分组显示;BridgeLog 里也会以device/getDeviceInfo这种格式展现,一眼能看出是哪一类。
description—— 人话能力说明,比如'获取设备信息'。给两类人看:调试面板上的开发者、自动生成 README 时的读者。
permission—— 权限标识,目前是字符串:
'none'—— 不需要权限'clipboard.read' / 'clipboard.write'—— 剪贴板读 / 写'location.mock'—— 模拟定位
将来真要做权限校验,会有一张「能力 → 需要权限」的表,dispatch 时先查权限再决定调不调。现在先用字符串占位,把这条数据通道打通。
mock—— 这个能力是否是「模拟实现」。
demo 里像剪贴板(内存模拟)、定位(写死坐标)就是mock: true。打到真机时一眼能看出哪些是「为了能跑而 mock 的」,哪些是「读真实系统的」。这个字段以后还能驱动 UI:模拟能力前面加个「演」字标识,避免演示时被误以为是真功能。
enabled—— 是否启用。
注册表dispatch时会查这个字段:能力虽然登记在册,但enabled === false就回ABILITY_DISABLED(403),不进 handler。给「能力开关」、「灰度上线」、「应急下线」预留了入口。
三、AbilityMeta和 handler 的关系
二者放在同一份记录里:
exportinterfaceRegisteredAbility{meta:AbilityMeta;// 这是个什么东西handler:NativeAbilityHandler;// 怎么做这件事}可以这么类比:
| meta | handler | |
|---|---|---|
| 类比 | 营业执照 + 业务说明牌 | 真正办事的窗口职员 |
| 内容 | 「我是谁、归哪管、要什么证件、是不是模拟营业、今天开不开门」 | 「来一个我办一个」 |
| 谁来读 | 调试面板、BridgeLog、权限校验、README 生成器 | 注册表的dispatch |
| 静态 / 动态 | 静态(一旦注册一般不变) | 动态(每次请求都跑一次) |
两者必须同时出现:注册一个能力的时候,handler 是必填,meta 也是必填。这避免了「有人偷偷塞个 handler 进去不写元信息」这种情况。来看实际注册代码:
constdeviceInfoMeta:AbilityMeta={action:BridgeAction.GET_DEVICE_INFO,namespace:'device',description:'获取设备信息',permission:'none',mock:true,enabled:true};registry.register(deviceInfoMeta,(req:BridgeRequest):Record<string,string>=>{returnthis.getDeviceInfo();});每个能力都是「先把说明书填好,再把办事函数交出来」。这意味着代码里再也没法出现「这能力我也不知道是干嘛的,就是有」的灰色地带。
四、为什么AbilityMeta适合上 BridgeLog / 能力面板 / README
回想一下没有 meta 的 BridgeLog 长什么样:
[完成] getDeviceInfo id req_001 → {...} ← {...} [失败] foobar id t1 → {...} ← {"code":404, ...}只有 action 名。看着不亏,但回答不了下面这些问题:
- 这是个什么类别的能力?
- 这次调用是 mock 还是真实底座?
- 这能力默认要权限吗?
把 meta 一接上,立马不一样:
[完成] device/getDeviceInfo id req_001 permission=none mock=true 8ms [失败] unknown/foobar id t1 3ms ← code 404同样这条日志,现在能告诉你:
- 这是 device 类能力
- 不需要权限
- 是模拟实现(演示用,不要打包到生产去)
- 总耗时 8 ms
调试面板做出来后,能直接画成一张表:每行 action、namespace、description、permission、mock、enabled、最近调用次数、平均耗时 ——所有需要的字段,meta 这边已经备齐了。这就是「把数据备好 ≠ 立即做面板」,但等想做时不用回头补数据。
README 自动生成同理:写一段 markdown 模板遍历registry.listAbilities(),每个 ability 渲染成一行说明 —— 这张能力清单就再也不会因为「忘了同步」而过时。
五、用getLocation举一个完整的例子
来挑一个 demo 里的getLocation能力,逐字段说说 meta 怎么被用上:
{action:'getLocation',namespace:'location',description:'获取模拟定位信息',permission:'location.mock',mock:true,enabled:true}action: 'getLocation'—— H5 调用时send({ action: 'getLocation', ... })就走到这里。namespace: 'location'—— BridgeLog 里这条记录会显示location/getLocation。未来调试面板按 namespace 分组时,它跟其他定位类能力(geocode、reverseGeocode 之类,未来加的话)会被分到同一组。description: '获取模拟定位信息'—— README 自动生成的能力清单里,这条会输出「location.getLocation—— 获取模拟定位信息」。permission: 'location.mock'—— 这是个特殊权限标记:「模拟定位」。将来真接定位时,会换成'location.read',需要的权限就升级了。现在打这个标记,将来做权限校验时一眼能看出来「这能力在 demo 阶段允许,到真机要换」。mock: true——NativeAbilityImp.readLocation()写死了一组坐标(22.3193, 114.1694),返回 source=mock。BridgeLog 看到mock=true就知道这条数据不能用于业务决策。enabled: true—— 注册时启用;如果要临时关掉,改成false,再调就会回 403。
一条 meta,让一个能力同时具备「执行」「展示」「分类」「权限识别」「开关控制」五种属性。
六、hasAbility/isEnabled/listAbilities怎么用
AbilityMeta不是只能写不能读。Registry 提供三个读取入口:
hasAbility(action:string):boolean// action 是否已注册isEnabled(action:string):boolean// 已注册且 enabled=truelistAbilities():AbilityMeta[]// 列出所有 meta(不暴露 handler)hasAbility—— 「这个 action 我认不认识」的预检;调试面板用它筛已注册名单。isEnabled—— 「这个能力现在能不能调」的预检;UI 上灰显某个按钮的逻辑可以挂在这里。listAbilities—— 直接把所有 meta 倒出来给上层用。
为什么listAbilities不返回 handler,只返回 meta?因为对外暴露的应该是「能力的描述」,不是「能力的实现」。Handler 是动态的、有副作用的、可能改的;meta 是静态的、能转 JSON 的、可以截屏放进文档的。
七、如果要加扫码 / 支付 / 分享,meta 怎么扩
设想一下,明天要加三个能力:扫码、支付、分享。每个能力的 meta 提前先想清楚:
constscanQrCodeMeta:AbilityMeta={action:'scanQrCode',namespace:'camera',description:'调起扫码界面,识别二维码 / 条形码',permission:'camera.use',mock:false,enabled:true};constrequestPaymentMeta:AbilityMeta={action:'requestPayment',namespace:'payment',description:'发起一次支付(需要商户接入)',permission:'payment.request',mock:true,// demo 阶段先 mock 一个支付成功enabled:false// 默认关,需要灰度时再打开};constshareTextMeta:AbilityMeta={action:'shareText',namespace:'social',description:'调起系统分享面板分享一段文字',permission:'share.system',mock:false,enabled:true};看出门道了吗?AbilityMeta让「这能力到底什么样」的所有维度都集中在一个对象里:
- 扫码需要相机权限 ——
permission: 'camera.use' - 支付目前是 mock,并且默认不开 ——
mock: true, enabled: false - 分享是 UI 层副作用 ——
namespace: 'social'
如果只有action → handler的注册方式,这些「能力到底什么样」的信息就会散在四处:权限分布在某个权限校验函数里、是不是 mock 注释在 Imp 文件里、是不是默认开看运维配置文件……每加一个能力都要找一遍。有了 meta,一处定义、处处使用。
八、新人最容易踩到的几个点
1. meta 不是注释。
看上去 meta 字段都是「人话描述」,但它们是 ArkTS 强类型对象,会跟着代码一起被打包、可以被读、可以被序列化。注释不能被代码访问,meta 可以 —— 这是它们的根本区别。
2.enabled和mock是两个独立维度。
新手容易混。enabled = false表示「这能力不能调」,是一个开关;mock = true表示「这能力的实现是模拟的」,是一种性质。一个 mock 能力可以是enabled = true(dem 里能调);一个真实能力也可以是enabled = false(线上没准备好)。
3.permission暂时只是字符串。
当前项目没接真实权限校验框架,permission字段就是个标签,谁都没拿它做拦截判断。但字段已经在那儿了,将来加权限校验时,只需要在Registry.dispatch里加一句if (!permissionGranted(reg.meta.permission)) return fail(...),整张表的权限要求都生效 —— 这就是「数据先备齐、再做功能」的好处。
4. meta 必须和 handler 一起注册。
不存在「先注册 handler,后补 meta」的 API。这是设计上故意的:能力的「身份」和「实现」要么一起出现要么都没有,避免出现「能调但说不出是干嘛」的孤魂能力。
我应该能讲出来的 5 个问题
- 只有 action → handler 的注册方式,会让哪些场景没法做?至少举出三个。
AbilityMeta里六个字段(action / namespace / description / permission / mock / enabled)分别给什么场景用?请逐个对应。mock和enabled这两个字段差别在哪?能不能用同一个字段表示?listAbilities()为什么返回AbilityMeta[]而不是RegisteredAbility[]?- 假设明天要加一个
requestPayment能力,且支付权限尚未到位,应该怎么填它的 meta?为什么?