在鸿蒙元服务开发中,经常会遇到这样的需求:用户在A元服务里点击一个按钮,需要临时调用B元服务的能力,操作完回到A服务继续原流程。比如支付、实名认证、选优惠券等。早期我尝试用 has.navigateTo 去跳转,很快发现不行——那是同一个元服务内的页面跳转,跨服务的拉起需要走ASCF,也就是元服务跨服务调用框架。ASCF提供了专门的半屏拉起组件 open-embedded-atomicservice,目标服务从底部滑上来,盖住屏幕下半部分,用户操作完自动退出,回到原服务页面,体验上比跳新页面再返回流畅不少。项目代码里已经能用到这个组件,我在实际对接过程中验证了几个关键点,整理成文。
组件核心用法
open-embedded-atomicservice 是ASCF提供的半屏拉起组件的标签名,使用方式就是把它包在触发操作的元素外层。它不需要自己写跨进程通信逻辑,也不需要管理路由栈,拉起、传参、返回值、异常回调都由组件帮你包好。
组件属性只有5个:appid(必填)、path、want-param、bindterminated、binderror。其中 appid 是唯一必填项,相当于目标元服务的唯一标识,可以从目标服务开发者那里获取,或者在AppGallery Connect上查到。我第一次测试时随便填了一个id,结果binderror报错10001,提示找不到对应的元服务。这里要注意,appid必须对应华为市场正式上架、或者调试签名匹配的元服务,二者不一致时本地调试会一直失败。
- <open-embedded-atomicservice appid="{{appid}}">
- <button>打开半屏元服务</button>
- </open-embedded-atomicservice>
复制代码
path指定打开目标服务的入口页面。如果目标服务有多个页面,用path指定具体路径,路径格式跟app.json里定义的一致,后面可以带 ?key=value 参数,目标服务Page.onLoad里能直接拿到。path为空或无效时,默认打开目标服务的首页。
- <open-embedded-atomicservice
- appid="{{appid}}"
- path="page/index/index?type=payment&amount=99">
- <button>去支付</button>
- </open-embedded-atomicservice>
复制代码
需要传复杂数据时用want-param。格式取决于目标服务的开发框架,这是最容易踩坑的地方。如果目标是ASCF框架开发的元服务,数据必须用 ascfPara.extraData 字段包一层;如果是ArkTS框架开发的,直接平铺键值对即可。
- // 目标为ASCF元服务时
- Page({
- data: {
- params: {
- ascfPara: {
- extraData: {
- orderId: '123456',
- amount: 99.00,
- productName: '高级会员'
- }
- }
- }
- },
- });
复制代码- // 目标为ArkTS元服务时
- Page({
- data: {
- params: {
- data: 'test',
- orderId: '123456'
- }
- },
- });
复制代码
我第一次对接时给ASCF服务传了平铺格式,对方一直拿不到extraData,排查了半天才意识到要包一层ascfPara,浪费了不少时间。建议对接前先确认目标服务的开发框架,或者直接问对方要传参样例。
回调与错误处理
bindterminated 在目标服务正常退出时触发,回调参数里能拿到目标服务返回的数据。返回数据的获取方式也分框架:ArkTS目标服务调用 terminateSelfWithResult 退出,数据会带回;ASCF目标服务(1.0.18+)调用 has.terminateSelf 即可。
- <open-embedded-atomicservice
- appid="{{appid}}"
- bindterminated="onTerminated">
- <button>打开</button>
- </open-embedded-atomicservice>
复制代码- Page({
- onTerminated(info) {
- // info.detail.params 里是目标服务返回的数据
- console.info('目标服务返回:', JSON.stringify(info.detail.params));
- has.showModal({
- title: '返回数据',
- content: JSON.stringify(info.detail.params),
- });
- },
- });
复制代码
binderror 在打开失败或运行异常时触发。打开失败的原因基本就是三类:appid不对、目标服务没安装、目标服务启动崩溃。建议两个回调都绑上。我一开始只绑了bindterminated没绑binderror,测试时故意让目标服务崩溃了一下,发现bindterminated不触发,还以为数据丢了——后来才知道闪退或被杀时触发的是binderror。
- Page({
- onError(err) {
- console.error('打开失败:', err.detail.errCode, err.detail.errMsg);
- has.showModal({
- title: '打开失败',
- content: err.detail.errMsg,
- });
- },
- });
复制代码
典型场景:支付服务拉起
以电商订单支付为例,主服务渲染订单信息,按钮外层套open-embedded-atomicservice,appid指向支付元服务,path指定支付页面并携带订单参数,want-param用ascfPara.extraData包裹订单数据,bindterminated处理支付结果,binderror做降级。支付完成自动回到主服务,不需要手动返回。
- <view class="scene-card">
- <text class="scene-title">订单支付</text>
- <view class="order-info">
- <text class="order-label">商品:鸿蒙开发实战课程</text>
- <text class="order-label">金额:¥99.00</text>
- </view>
- <open-embedded-atomicservice
- appid="{{paymentAppId}}"
- path="page/payment/index?amount=99"
- want-param="{{paymentParams}}"
- bindterminated="onPaymentDone"
- binderror="onPaymentError">
- <button type="primary" class="pay-btn">去支付</button>
- </open-embedded-atomicservice>
- </view>
复制代码- Page({
- data: {
- paymentAppId: '57xxxxxxxxx', // 支付服务的 appid
- paymentParams: {
- ascfPara: {
- extraData: {
- orderId: 'ORD20240628001',
- amount: 99,
- subject: '鸿蒙开发实战课程',
- },
- },
- },
- },
- onPaymentDone(info) {
- const result = info.detail.params;
- if (result.payResult === 'success') {
- has.showToast({ title: '支付成功' });
- } else {
- has.showToast({ title: '支付取消', icon: 'none' });
- }
- },
- onPaymentError(err) {
- has.showToast({ title: '支付服务异常', icon: 'none' });
- },
- });
复制代码
类似的场景还有打开认证服务:账户操作前半屏拉起人脸识别或身份确认服务,认证通过后bindterminated回调返回verified字段,再执行后续操作。注意如果目标服务未安装,打开会失败,建议在binderror里做降级处理,比如提示用户先安装认证服务,或者走备选方案。
与 has.navigateTo 的边界
has.navigateTo 只适用于同一个元服务内部的页面跳转。跨元服务、需要半屏呈现、需要把结果带回来,用open-embedded-atomicservice。业务决策很简单:看你的目标页面在不在当前元服务的包内,不在就上这个组件。
踩坑记录
把我在实际开发中遇到的几个坑整理如下,每个都是真实排查过的。
appid与调试签名不匹配。测试环境填正式环境的appid,本地调试一直报10001,因为调式签名跟正式appid对不上,换成测试环境的appid就正常了。
want-param格式不匹配。ASCF服务要包ascfPara.extraData,ArkTS直接平铺。搞混的话目标服务解析不到数据,看起来像是组件坏了,其实是格式问题。
bindterminated只在正常退出时触发。目标服务闪退或被系统杀掉时,触发的是binderror不是bindterminated。所以降级逻辑不要只写在bindterminated里,binderror也要处理。
path路径必须写完整。app.json里定义的是 "page/payment/index",path就写 "page/payment/index?key=val",不能简写成 "/payment/index" 或 "payment/index"。
半屏布局适配。半屏状态下目标服务显示区域变小,如果页面按全屏设计,底部按钮可能被截掉一半。我自己就遇到过:被拉起的服务底部确认按钮被半屏切掉,后来在目标服务里加媒体查询,检测到窗口高度低于阈值时缩小padding和字号才解决。更重要的是,半屏状态下getSystemInfo、getWindowInfo这些API的返回值可能跟全屏不一致,不要依赖它们做布局计算,用flex等比自适应更安全。
SDK版本要求。open-embedded-atomicservice组件的起始版本是1.0.17,SDK版本要求不低于5.1.0(18)。版本不够组件不生效,开发前先确认环境。
另外这个组件目前只有华为生态支持,在其他设备上可能不生效。做跨平台兼容时,建议先判断环境再决定要不要展示这个组件。
总结
open-embedded-atomicservice 的价值在于把跨元服务调用的复杂度封装掉了,不再需要自己写跨进程通信。核心要点四个:appid必填且要对得上、want-param格式看目标框架、bindterminated拿返回值、binderror做降级。搭配合适的path和半屏布局适配,就能实现流畅的跨元服务半屏交互。 |