你好欢迎来到我的博客我是【菜鸟学鸿蒙】我是一名在路上的移动端开发者正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来也为了和更多同路人互相启发我决定把探索 HarmonyOS 的过程都记录在这里。️主要方向ArkTS 语言基础、HarmonyOS 原生应用Stage 模型、UIAbility/ServiceAbility、分布式能力与软总线、元服务/卡片、应用签名与上架、性能与内存优化、项目实战以及 Android → 鸿蒙的迁移踩坑与复盘。内容节奏从基础到实战——小示例拆解框架认知、专项优化手记、实战项目拆包、面试题思考与复盘让每篇都有可落地的代码与方法论。 我相信写作是把知识内化的过程分享是让生态更繁荣的方式。如果你也想拥抱鸿蒙、热爱成长欢迎关注我一起交流进步前言很多页面跳转只需要“打开下一页”但选择类业务不一样。比如首页打开城市选择页用户选中“深圳”后城市选择页不仅要关闭还要把“深圳”传回原来的页面联系人选择、地址选择、文件类型选择本质上都是同一类交互。这种场景如果只用startAbility()拉起动作本身没有“等待目标 UIAbility 返回业务结果”的语义。HarmonyOS 的UIAbilityContext提供了startAbilityForResult()调用方拉起目标 UIAbility 后可以在目标 UIAbility 结束时获得AbilityResult目标方则可以使用terminateSelfWithResult()在退出时把resultCode和数据一并带回。这次就用一个最小“城市选择”示例把这条链路完整走一遍。一、为什么这里不直接用 startAbility先区分两个接口的目的。startAbility()的 Promise 返回类型是Promisevoid它关心的是“UIAbility 是否成功发起启动”startAbilityForResult()的 Promise 返回类型则是PromiseAbilityResult它还会等待目标 UIAbility 退出并接收返回数据。所以普通页面跳转 A ── startAbility ── B 选择并返回 A ── startAbilityForResult ── B A ── AbilityResult ───────── B这两个 API 看起来只差了几个单词但业务语义完全不同。城市、联系人、地址、文件类型这类“进入一个独立页面完成一次选择然后把结果交还调用方”的流程更适合第二种方式。二、先把版本和接口边界弄清楚截至当前华为官方文档HarmonyOS 7.0 对应的 API 版本为26.0.0。华为在 26.0.0 起调整了 API 版本号格式并在 HarmonyOS 7.0 升级适配说明中明确给出了两者的对应关系。本文按照当前 HarmonyOS 7.0 / API 26.0.0 文档核对接口但startAbilityForResult()并不是 HarmonyOS 7 才出现的新能力。当前UIAbilityContext文档明确说明UIAbilityContext属于 Ability KitStage 模型下使用模块首批接口从 API version 9 开始支持startAbilityForResult()和terminateSelfWithResult()都只能在主线程调用系统能力为SystemCapability.Ability.AbilityRuntime.Core元服务从 API version 11 开始也可以使用相关接口。AbilityResult则包含两个与本文直接相关的成员resultCode:numberwant?:Want正常情况下resultCode是目标方主动返回的结果码异常情况下官方定义会返回-1。want是可选项用于携带返回数据。本文只演示同一应用内两个 UIAbility 的显式拉起不额外引入权限。跨应用场景还要继续检查目标组件可见性、权限以及 Stage 模型组件启动规则不能把同应用示例直接等价为任意跨应用拉起。startAbilityForResult()本身也定义了诸如目标 Ability 不存在、组件不可见、权限不足等错误码。三、搭一个最小实践城市选择页工程中准备两个 UIAbilityEntryAbility └── pages/Index.ets CityPickerAbility └── pages/CityPickerPage.ets流程很简单1. Index 页面点击“选择城市” 2. EntryAbility 通过 startAbilityForResult 拉起 CityPickerAbility 3. CityPickerAbility 接收启动 Want 4. 用户点击“深圳” 5. CityPickerAbility 调用 terminateSelfWithResult 6. resultCode 0Want.parameters 中携带 city 7. Index 页面拿到 AbilityResult 并更新界面为了同时演示取消再约定两个业务结果码constRESULT_OK:number0;constRESULT_CANCEL:number1;这里刻意不使用-1作为业务取消码因为官方已经把异常退出时的resultCode -1定义为异常结果。3.1 配置第二个 UIAbility首先需要让系统知道CityPickerAbility的存在。在现有module.json5的abilities中加入一个 UIAbility{ module: { // 省略工程已有配置 abilities: [ { name: EntryAbility, srcEntry: ./ets/entryability/EntryAbility.ets, exported: true }, { name: CityPickerAbility, srcEntry: ./ets/citypickerability/CityPickerAbility.ets, exported: false } ] } }abilities中的name对应后面 Want 的abilityNamesrcEntry指向 UIAbility 的 ArkTS 入口文件。官方module.json5示例同样使用abilities、name、srcEntry、exported描述 UIAbility。这里的选择页只供当前应用内部使用因此示例将exported设为false。页面配置文件中同时加入选择页例如{src:[pages/Index,pages/CityPickerPage]}四、A 页面发起 startAbilityForResultA 页面真正需要关心的不是“怎么跳过去”而是“跳过去以后等待什么结果”。Index.ets可以这样组织import{common,Want}fromkit.AbilityKit;import{BusinessError}fromkit.BasicServicesKit;constRESULT_OK:number0;constRESULT_CANCEL:number1;EntryComponentstruct Index{StateselectedCity:string尚未选择;privateasyncopenCityPicker():Promisevoid{constcontextthis.getUIContext().getHostContext()ascommon.UIAbilityContext;constwant:Want{bundleName:com.example.resultdemo,abilityName:CityPickerAbility,parameters:{scene:city-picker}};try{constresult:common.AbilityResultawaitcontext.startAbilityForResult(want);if(result.resultCodeRESULT_OK){constcityresult.want?.parameters?.[city];if(typeofcitystring){this.selectedCitycity;}return;}if(result.resultCodeRESULT_CANCEL){console.info(User canceled city selection.);return;}if(result.resultCode-1){console.error(CityPickerAbility exited abnormally.);return;}console.info(Unknown resultCode:${result.resultCode});}catch(error){consterrerrorasBusinessError;console.error(startAbilityForResult failed, code${err.code}, message${err.message});}}build(){Column({space:16}){Text(当前城市${this.selectedCity}).fontSize(20)Button(选择城市).onClick((){this.openCityPicker();})}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}这里有三个关键点。第一个是页面中获取的必须是当前页面对应的UIAbilityContext。当前 ArkUI 文档推荐在页面侧通过getUIContext().getHostContext()获取 Ability Context以避免多实例环境下使用无明确归属的全局上下文。第二个是 Want。Want 是组件之间传递信息的载体。启动 UIAbility 时bundleName和abilityName用来指定目标组件而parameters可以携带业务参数。所以这里传入parameters:{scene:city-picker}它属于A → B 的启动参数。第三个才是await context.startAbilityForResult(want)。这一行不会只得到“启动成功”这么简单的结果而是在目标 UIAbility 结束后得到AbilityResult。这也是整个选择页模式成立的核心。五、B Ability接收启动参数目标 UIAbility 在创建时可以通过onCreate()参数取得启动 Want。CityPickerAbility.etsimport{AbilityConstant,UIAbility,Want}fromkit.AbilityKit;import{window}fromkit.ArkUI;exportdefaultclassCityPickerAbilityextendsUIAbility{onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{constscenewant.parameters?.[scene];if(typeofscenestring){console.info(CityPickerAbility scene:${scene});}}onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent(pages/CityPickerPage);}}官方开发资料明确展示了 UIAbility 可以在onCreate(want)中读取want.parameters中的启动数据。这里也能看出一个很实用的边界A → B 读取启动时传入的 Want B → A 通过 AbilityResult.want 返回虽然两边都使用 Want但方向不同不要把“启动参数”和“返回参数”混成一份状态。六、B 页面选择完成后返回业务数据接下来是闭环中最关键的一步。用户选择城市后不是简单执行terminateSelf()而是调用terminateSelfWithResult()CityPickerPage.etsimport{common,Want}fromkit.AbilityKit;import{BusinessError}fromkit.BasicServicesKit;constRESULT_OK:number0;constRESULT_CANCEL:number1;EntryComponentstruct CityPickerPage{privateasyncfinishWithCity(city:string):Promisevoid{constcontextthis.getUIContext().getHostContext()ascommon.UIAbilityContext;constresultWant:Want{bundleName:com.example.resultdemo,abilityName:EntryAbility,parameters:{city:city}};constabilityResult:common.AbilityResult{resultCode:RESULT_OK,want:resultWant};try{awaitcontext.terminateSelfWithResult(abilityResult);}catch(error){consterrerrorasBusinessError;console.error(terminateSelfWithResult failed, code${err.code}, message${err.message});}}privateasynccancel():Promisevoid{constcontextthis.getUIContext().getHostContext()ascommon.UIAbilityContext;constabilityResult:common.AbilityResult{resultCode:RESULT_CANCEL};try{awaitcontext.terminateSelfWithResult(abilityResult);}catch(error){consterrerrorasBusinessError;console.error(cancel failed, code${err.code}, message${err.message});}}build(){Column({space:12}){Text(选择城市).fontSize(24)Button(北京).onClick((){this.finishWithCity(北京);})Button(上海).onClick((){this.finishWithCity(上海);})Button(深圳).onClick((){this.finishWithCity(深圳);})Button(取消).onClick((){this.cancel();})}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}官方对terminateSelfWithResult()的定义非常明确它会销毁当前 UIAbility如果当前 UIAbility 原本就是通过startAbilityForResult()拉起的则把传入的AbilityResult返回调用方。反过来说如果目标 UIAbility 不是通过startAbilityForResult()拉起的即使调用terminateSelfWithResult()也不会形成本文这条结果返回链路。七、resultCode 和业务数据最好分开表达这个地方比较容易理解错。不要让resultCode同时承担大量业务数据。更清晰的方式是resultCode 负责描述这次交互“发生了什么” Want.parameters 负责描述这次交互“返回了什么”例如情况resultCode返回数据正常选中0city 深圳用户取消1不需要UIAbility 异常退出-1按异常处理这样调用方处理结果时非常直观if(result.resultCodeRESULT_OK){// 解析业务数据}elseif(result.resultCodeRESULT_CANCEL){// 用户主动取消}elseif(result.resultCode-1){// 异常结果}尤其是-1不建议重新拿来定义“用户取消”。因为按照AbilityResult官方定义异常情况下系统本身就会返回-1。八、为什么异常退出也必须单独考虑很多代码只写这一句constresultawaitcontext.startAbilityForResult(want);然后默认“只要 Promise 返回了就一定是业务成功”。这个理解不完整。官方在startAbilityForResult()文档中特别说明目标 UIAbility 被异常销毁时也会向调用方返回结果此时resultCode为-1。如果目标 UIAbility 使用单实例模式并被不同应用多次通过该接口启动还存在只有最后一个调用方获得正常结果、其他调用方获得-1的情况。因此至少要区分两类失败。第一类是startAbilityForResult 本身调用失败例如目标 Ability 不存在、不可见、权限或参数有问题。这类问题走 Promise 的catch。第二类是Ability 已经成功拉起但之后异常结束这类情况可能得到一个AbilityResult但其中result.resultCode-1这两条错误路径不是同一回事。九、取消操作不要和异常混在一起对于选择页面“没选数据”不一定表示出错。用户点取消属于正常业务分支。因此示例专门使用constRESULT_CANCEL1;并主动awaitcontext.terminateSelfWithResult({resultCode:RESULT_CANCEL});这样 A 页面能够明确知道不是接口报错 不是 Ability 异常 只是用户不想选了如果产品还要求把系统返回键也严格视为同一种“业务取消”则还要结合项目的返回行为统一处理本文不额外扩展未经验证的返回键实现而把重点放在startAbilityForResult / terminateSelfWithResult这条官方明确支持的结果链路上。十、封装成一个可复用的“选择页面”模式当项目中城市、地址、联系人分类、文件类型等页面越来越多时不建议每个页面都重新发明结果协议。可以统一约定exportconstRESULT_OK:number0;exportconstRESULT_CANCEL:number1;再约定每个选择器自己的参数 key城市选择 parameters[city] 地址选择 parameters[addressId] 联系人选择 parameters[contactId] 文件类型 parameters[fileType]调用模式就会稳定成构造 Want ↓ startAbilityForResult() ↓ 目标 Ability 展示选择页面 ↓ 用户完成 / 取消 ↓ terminateSelfWithResult() ↓ 调用方按 resultCode 分支 ↓ 再解析 Want.parameters甚至可以在业务层继续封装成asyncfunctionselectCity():Promisestring|undefined由这个函数内部处理 UIAbilityContext、Want、resultCode和参数校验上层页面只关心最终有没有获得城市。真正值得复用的并不是某一段按钮代码而是这个请求—选择—结果协议。十一、容易出错的几个地方1. abilityName 写错Want 中的abilityName:CityPickerAbility必须和module.json5中声明的 Ability 名称对应。官方为startAbilityForResult()定义了16000001指定 Ability 不存在。排查这类问题时先核对配置不要先怀疑 ArkUI 页面。2. 把 startAbility 当成 startAbilityForResult如果需求是等待结果就应该从调用入口开始确定结果模型。startAbility()返回Promisevoid它不是“先 startAbility然后想办法从 B 页面回调 A 页面”的简写版本。3. B 页面只 terminateSelf没有返回结果terminateSelf()的目标只是结束 UIAbility。需要把选择结果一起交回调用方时应使用terminateSelfWithResult()。4. 不检查 want 是否存在AbilityResult.want在接口定义中是可选的。所以不要直接result.want.parameters[city]更稳妥的是constcityresult.want?.parameters?.[city];if(typeofcitystring){// 使用数据}业务结果码正确不等于业务参数一定完整。5. 把所有非 0 结果都当接口异常resultCode本来就允许目标方返回自己的业务结果。所以“用户取消”完全可以设计成正常的业务码不应该和catch混为一谈。十二、实际项目中怎么排查遇到“页面能打开但结果回不来”时可以按下面顺序检查。第一步确认调用的是startAbilityForResult()。如果入口就是普通startAbility()先把结果模型纠正。第二步确认目标确实是被这次调用拉起的 UIAbility。检查bundleName、abilityName和module.json5。第三步检查 B 侧是否真正执行了terminateSelfWithResult()。只切页面、调用普通退出接口都不是同一套结果机制。第四步检查AbilityResult.resultCode。先区分0 / 自定义成功码 业务取消码 -1 异常结果再决定要不要解析 Want。第五步检查result.want?.parameters。不要把“拿到了 AbilityResult”和“业务字段一定存在”当成同一件事。第六步如果 Promise 直接进入 catch查接口错误码。例如16000001 目标 Ability 不存在 16000004 不能启动不可见组件 16000005 进程权限不满足 401 参数错误具体错误码和版本范围应继续以当前UIAbilityContext官方 API Reference 为准。开发经验总结startAbilityForResult()本身并不复杂真正需要设计清楚的是调用双方之间的结果协议。对于“打开一个独立选择页面选完再回到原页面”的场景可以抓住四件事A 侧用startAbilityForResult()而不是普通startAbility()启动数据放进启动 WantB 可以在 UIAbility 的onCreate()中接收B 侧完成选择后通过terminateSelfWithResult()返回AbilityResultresultCode描述成功、取消或异常真正的业务数据放进返回 Want 的parameters。HarmonyOS 7.0 / API 26.0.0 仍然支持这套能力而且它不是 HarmonyOS 7 才加入的新接口。对于现有工程升级到 HarmonyOS 7更应该关注工程 SDK 配套关系和组件启动规则而不是重新设计选择页面的数据回传方式。如果项目里已经有多个“选择页面”可以顺手检查一下现在这些页面是在各自维护一套返回规则还是已经统一成了一个明确的resultCode Want.parameters协议。后者通常更容易维护和排查。 写在最后如果你觉得这篇文章对你有帮助或者有任何想法、建议欢迎在评论区留言交流你的每一个点赞 、收藏 ⭐、关注 ❤️都是我持续更新的最大动力我是一个在代码世界里不断摸索的小码农愿我们都能在成长的路上越走越远越学越强感谢你的阅读我们下篇文章再见✍️ 作者菜鸟不学编程 本文原创转载请注明出处。
阅读完成 · 觉得有帮助?