ASCF 的能力边界在哪儿?这是用 ASCF 做复杂功能时绕不开的问题。ASCF 的 API 比 ArkTS 少很多——没有直接的电话拨打接口、没有完整的设备信息获取、没有文件系统操作。之前做一个需要拨号的功能,翻遍 ASCF 文档没找到 has.makePhoneCall,当时一脸懵。
后来发现 ASCF 提供了一个叫 NativeBridge 的桥接机制:JS 代码里调 has.callNative,ArkTS 侧的 EntryAbility 里处理,打完收工。但老实说,配置这个桥折腾了我一整天——要发邮件申请权限、要改 EntryAbility 代码、要处理同步异步的坑。这篇文章把整个流程和踩过的坑整理出来,给后面要用的人省点时间。
NativeBridge 是什么
NativeBridge 是 ASCF 和 ArkTS 之间的通信桥梁。ASCF 框架本身是用 JS/TS 写的,能调的系统能力有限。而 ArkTS 作为鸿蒙原生框架,能用的 API 多得多。NativeBridge 做的事情就是——JS 侧发请求,ArkTS 侧处理,结果返回给 JS。
- JS (has.callNative) -> ASCF 框架 -> EntryAbility (onNativeCalled) -> ArkTS API -> 返回结果
复制代码
一句话总结:ASCF 调不了的 ArkTS 接口,通过 NativeBridge 来调。
开发前准备:先发邮件申请权限
这个跟其他 API 不一样,不是开箱即用的,要先申请权限。发邮件到 atomicservice@huawei.com,标题按固定格式写:
- [元服务申请使用框架间通信]-[元服务名称]-[APP ID]
复制代码
邮件内容里提供元服务名称和 APP ID。APP ID 在华为开发者联盟的项目设置里可以查到。官方说 3 个工作日内反馈,我当时等了 2 天收到了开通确认邮件——期间一直以为是自己写错了邮箱。
这个权限申请流程对独立开发者来说有点麻烦,尤其是还在调试阶段的服务。建议在项目初期就申请下来,别等到功能开发到一半了才发邮件,干等两天。
API 一览:同步和异步两个入口
NativeBridge 提供了两个 API,一个同步一个异步。
has.callNative(同步)
- // 传字符串
- const res = has.callNative('getSystemInfo');
- console.info('系统信息:', res);
- // 传对象
- const sum = has.callNative({
- action: 'calculate',
- args: [1, 2, 3, 4, 5],
- });
- console.info('计算结果:', sum);
复制代码
同步调用会阻塞当前线程直到结果返回。如果 ArkTS 侧处理比较耗时(比如读取大文件),JS 侧会卡住。耗时操作建议用异步版本。
同步调用返回的是字符串,如果 ArkTS 侧返回的是对象,记得 JSON.parse。
has.callNativeAsync(异步)
- has.callNativeAsync({
- params: 'getSystemInfo',
- success: (res) => {
- console.info('获取系统信息成功:', res);
- },
- fail: (err) => {
- console.error('获取失败:', err);
- },
- complete: () => {
- console.info('调用结束');
- },
- });
- has.callNativeAsync({
- params: { action: 'makeCall', args: '13112341234' },
- success: (res) => {
- console.info('拨号成功');
- },
- fail: (err) => {
- console.error('拨号失败:', err);
- },
- });
复制代码
异步调用不会阻塞 JS 线程,结果通过回调返回。适合网络请求、拨号、文件读写等耗时操作。
ArkTS 侧:EntryAbility 里配处理
这是整个流程里最容易被忽略的部分——JS 侧调用了,ArkTS 侧没人接,结果就是没反应。需要在 EntryAbility.ts 里继承 AscfUIAbility,复写 onNativeCalled(同步)或 onNativeCalledAsync(异步)。
同步处理
- import { AscfUIAbility } from '@atomicservice/ascfapi';
- import { atomicService } from '@kit.ScenarioFusionKit';
- const SYSTEM_INFO_STATE_ARRAY: atomicService.SystemInfoType[] = [
- 'brand', 'deviceModel', 'screenWidth', 'screenHeight',
- 'statusBarHeight', 'screenSafeArea', 'language', 'osFullName',
- 'sdkApiVersion', 'bluetoothEnabled', 'wifiEnabled', 'locationEnabled',
- 'deviceOrientation', 'theme', 'windowWidth', 'windowHeight'
- ];
- export default class EntryAbility extends AscfUIAbility {
- onNativeCalled(params: string | object): string {
- // 获取系统信息
- if (typeof params === 'string' && params === 'getSystemInfo') {
- const systemInfo = atomicService.getSystemInfoSync(SYSTEM_INFO_STATE_ARRAY);
- return JSON.stringify(systemInfo);
- }
- // 计算
- if (typeof params === 'object') {
- const paramObj = params as Record<string, Object>;
- if (paramObj.action === 'calculate' && paramObj.args) {
- const numbers = paramObj.args as number[];
- const sum = numbers.reduce((a, b) => a + b, 0);
- return sum.toString();
- }
- }
- return '';
- }
- }
复制代码
注意返回值必须是 string。如果想返回对象,自己 JSON.stringify 一下,JS 侧再 JSON.parse。我一开始返回了一个对象没转字符串,ArkTS 报类型错误编译不过……排查了好一会儿。
异步处理
- import { AscfUIAbility } from '@atomicservice/ascfapi';
- import { atomicService } from '@kit.ScenarioFusionKit';
- import { call } from '@kit.TelephonyKit';
- export default class EntryAbility extends AscfUIAbility {
- onNativeCalledAsync(params: string | object): Promise<object> {
- // 异步获取系统信息
- if (typeof params === 'string' && params === 'getSystemInfo') {
- return atomicService.getSystemInfo(SYSTEM_INFO_STATE_ARRAY);
- }
- // 拨打电话
- if (typeof params === 'object') {
- const paramObj = params as Record<string, Object>;
- if (paramObj.action === 'makeCall' && paramObj.args) {
- const phoneNumber = paramObj.args as string;
- return call.makeCall(phoneNumber).then(() => {
- return { success: true };
- });
- }
- }
- return Promise.reject(new Error('未知的异步操作'));
- }
- }
复制代码
异步方法返回 Promise<object>,JS 侧通过 success/fail 回调拿到结果。
callNative 和 callNativeAsync 怎么选
简单判断:ArkTS 侧是同步 API 就用 callNative,是异步 API 就用 callNativeAsync。如果不确定 ArkTS 接口是同步还是异步,看文档——大部分系统 API 带 Sync 后缀的是同步(如 getSystemInfoSync),不带的是异步。
实用场景
获取完整设备信息
ASCF 的 has.getSystemInfo 返回的信息有限,通过 NativeBridge 可以拿到更多字段:
- // JS 侧
- const infoStr = has.callNative('getSystemInfo');
- const info = JSON.parse(infoStr);
- console.info('品牌:', info.brand);
- console.info('安全区域:', JSON.stringify(info.screenSafeArea));
复制代码
ArkTS 侧用 atomicService.getSystemInfoSync 能拿到 brand、screenSafeArea、bluetoothEnabled、wifiEnabled 等 ASCF 没有的信息。
拨打电话
ASCF 没有拨号 API。通过 NativeBridge 调 ArkTS 的 call.makeCall:
- // JS 侧
- has.callNativeAsync({
- params: { action: 'makeCall', args: '13112341234' },
- success: () => {
- console.info('拨号成功');
- },
- fail: (err) => {
- console.error('拨号失败:', err);
- has.showToast({ title: '拨号失败', icon: 'none' });
- },
- });
复制代码
ArkTS 侧用 call.makeCall(phoneNumber) 实现。这个场景是 NativeBridge 最典型的应用——ASCF 做不了的事,交给 ArkTS。
读取设备电池信息
ASCF 没有电池信息相关的 API,通过桥接可以拿到:
- // ArkTS 侧
- import { batteryInfo } from '@kit.BatteryInfoKit';
- onNativeCalled(params: string | object): string {
- if (params === 'getBatteryInfo') {
- const info = {
- batteryLevel: batteryInfo.batterySOC,
- chargingStatus: batteryInfo.batteryHealthState,
- isCharging: batteryInfo.isCharging,
- };
- return JSON.stringify(info);
- }
- // ...
- }
复制代码- // JS 侧
- const batteryStr = has.callNative('getBatteryInfo');
- const battery = JSON.parse(batteryStr);
- console.info('电量:', battery.batteryLevel + '%');
- console.info('是否充电:', battery.isCharging);
复制代码
拿到电量信息后可以做低电量提醒、省电模式等功能。
读写文件
ASCF 的文件 API 有限,通过 ArkTS 的 fileIo 可以操作任意文件路径:
- // ArkTS 侧
- import { fileIo } from '@kit.CoreFileKit';
- onNativeCalledAsync(params: string | object): Promise<object> {
- if (typeof params === 'object') {
- const p = params as Record<string, Object>;
- if (p.action === 'readFile' && p.path) {
- const filePath = p.path as string;
- try {
- const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
- const buf = new ArrayBuffer(1024);
- fileIo.readSync(file.fd, buf);
- fileIo.closeSync(file);
- return Promise.resolve({ content: buf.toString() });
- } catch (e) {
- return Promise.reject(e);
- }
- }
- }
- return Promise.reject(new Error('未知操作'));
- }
复制代码
注意文件读写涉及沙箱路径和权限,不是所有目录都能访问。建议在沙箱内操作。
获取网络类型详情
ASCF 的 has.getNetworkType 只返回 wifi/4g/5g 等基本类型,ArkTS 的 network 接口能拿到更多详细信息:
- // ArkTS 侧
- import { network } from '@kit.NetworkKit';
- onNativeCalledAsync(params: string | object): Promise<object> {
- if (params === 'getNetworkDetail') {
- return network.getDefaultNet().then((netHandle) => {
- return network.getNetCapabilities(netHandle);
- });
- }
- return Promise.reject(new Error('未知操作'));
- }
复制代码
这在做网络诊断或者需要精确判断网络质量的功能时很有用。
自定义计算/数据处理
如果 JS 侧的性能不够,或者需要调用特定的 ArkTS 库做计算,可以传参过去算完再返回:
- // JS 侧
- const res = has.callNative({
- action: 'calculate',
- args: [1, 2, 3, 4, 5],
- });
- console.info('求和结果:', res);
复制代码
当然,简单的计算在 JS 侧做就行了,犯不着走一遍桥。这个更多是演示传参和返回的完整链路。
验证桥接是否生效
配置完了怎么知道桥有没有通?我一般通过三步验证。
第一步:ArkTS 侧写一个最简单的回显。在 EntryAbility 的 onNativeCalled 里加一个 ping 操作:
- onNativeCalled(params: string | object): string {
- if (params === 'ping') {
- return 'pong';
- }
- // 其他逻辑
- }
复制代码
第二步:JS 侧调一下:
- const res = has.callNative('ping');
- console.info('桥接测试结果:', res);
- // 期望输出: pong
复制代码
如果输出 pong 说明桥通了。如果一直没有输出或者报错,先确认权限申请是否通过、EntryAbility 是否正确继承 AscfUIAbility。
第三步:调一个真实的 ArkTS API。ping 通了之后,再试真正的 ArkTS API,比如获取设备信息。逐步确认问题出在桥本身还是业务逻辑上。
我当初测试的时候,ping 通了就以为全好了,结果调 getSystemInfo 还是没反应——因为忘了 JSON.stringify,ArkTS 侧编译报错了但日志被吞了,JS 侧什么也看不到。后来在 ArkTS 侧加了 try-catch 和 hilog 输出才定位到问题。
EntryAbility 文件在哪
第一次用 NativeBridge 的人可能找不到 EntryAbility 在哪——项目目录下一般是 entry/src/main/ets/entryability/EntryAbility.ts。ASCF 项目生成时默认的 EntryAbility 继承的是 UIAbility,需要手动改成 AscfUIAbility:
- // 改之前
- import { UIAbility } from '@kit.AbilityKit';
- export default class EntryAbility extends UIAbility { ... }
- // 改之后
- import { AscfUIAbility } from '@atomicservice/ascfapi';
- export default class EntryAbility extends AscfUIAbility { ... }
复制代码
改了之后如果编译报错找不到 @atomicservice/ascfapi,检查一下 oh-package.json5 里有没有加这个依赖。没有的话手动加:
- {
- "dependencies": {
- "@atomicservice/ascfapi": "file:./path/to/ascfapi"
- }
- }
复制代码
具体路径取决于项目结构,如果不确定可以问 IDE——在代码里写 import { AscfUIAbility } 时 IDE 会提示可导入的包路径。
JS 侧的错误处理
NativeBridge 调用出错时,JS 侧的表现因同步异步而异:
callNative(同步):ArkTS 侧抛异常会直接传到 JS 侧,可以用 try-catch 捕获。
callNativeAsync(异步):ArkTS 侧 Promise.reject 触发 JS 侧的 fail 回调。
- // 同步调用异常捕获
- try {
- const res = has.callNative('someMethod');
- console.info('结果:', res);
- } catch (e) {
- console.error('调用异常:', e);
- has.showToast({ title: '调用失败', icon: 'none' });
- }
- // 异步调用异常捕获
- has.callNativeAsync({
- params: 'someAsyncMethod',
- success: (res) => {
- console.info('成功:', res);
- },
- fail: (err) => {
- console.error('失败:', JSON.stringify(err));
- has.showToast({ title: '异步调用失败', icon: 'none' });
- },
- });
复制代码
ArkTS 侧建议统一用 try-catch 包裹所有业务逻辑,确保异常能被正确抛到 JS 侧。如果 ArkTS 侧吃了异常,JS 侧什么都不知道,用户点了也没反应,排查起来很费劲。
踩过的坑合集
没申请权限直接调。没发邮件申请权限就调 callNative/callNativeAsync,调用会静默失败——没有错误提示,没有日志,就是没反应。检查的第一步就是确认权限有没有开通。我调了大半个小时以为是代码写错了,结果发现压根没申请权限。
EntryAbility 没继承 AscfUIAbility。如果 EntryAbility 继承的是默认的 UIAbility 而不是 AscfUIAbility,onNativeCalled/onNativeCalledAsync 不会被调用。JS 侧调了也没反应。
同步方法返回类型必须是 string。onNativeCalled 的返回值类型是 string,不能直接返回对象或数字。如果返回了非 string 类型,ArkTS 编译阶段会报错。正确做法是 JSON.stringify 后返回,JS 侧再 JSON.parse 解析。
异步方法必须返回 Promise。onNativeCalledAsync 的返回值类型是 Promise<object>,如果返回了 null 或者 undefined,JS 侧的 fail 回调会触发。未匹配到任何操作时应 return Promise.reject(new Error('未知的操作')),这样 JS 侧可以统一在 fail 里处理错误。
参数传递的格式统一。JS 侧和 ArkTS 侧约定好参数格式是关键。我一开始 JS 侧传 { action: 'makeCall', phone: '131xxxxxxxx' },ArkTS 侧读的是 args 字段,两边字段名对不上,怎么调都不通。后来统一了字段名就好了。建议把参数格式文档化,双方各留一份。
权限申请的 appid 搞错。发邮件时填的 APP ID 是项目的 APP ID,不是网传的包名或 bundleName。APP ID 在华为开发者联盟 -> 项目设置 -> 应用 -> APP ID 里查。填错了运营人员会驳回申请,白等两三天。我第一封邮件就填错了,等了 3 天收到驳回通知,改对了重新提交又等了 2 天,一来一回一周就过去了。申请之前一定要确认清楚。
写在最后
NativeBridge 是 ASCF 的一个能力扩展通道。它的使用流程是固定的:发邮件申请权限 -> ArkTS 侧 EntryAbility 配置处理 -> JS 侧调用。虽然配置流程比普通 API 麻烦,但好处是一旦配好了,ASCF 缺失的系统能力都可以通过这个桥接调用了。
对了,不要所有功能都走 NativeBridge。只有 ASCF 确实不支持的 ArkTS 特有能力才需要走桥。ASCF 本身就有的 API(如网络请求、本地缓存)直接在 JS 侧调就行,绕一圈反而多余。判断标准就一条——ASCF 文档里搜不到的,才上桥。 |