老板在演示会上拿着手机看视频,突然把手机往平板旁边一靠,试图向客户展示鸿蒙的流转能力。结果平板上的应用不仅没续播,反而直接冷启动弹到了首页。那一瞬间,空气比停尸房还安静。这周我死磕了应用接续(Continuation)机制,终于搞明白了里面那些反直觉的设计。
同步执行的硬性底线
应用接续的第一步,是在源设备(手机)上执行 onContinue 方法,将当前页面的状态打包传给系统。很多开发者(包括当时的我)以为这是一个异步过程,试图在这里发起网络请求去拉取最新数据。
typescript
// 极其危险的写法
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
fetchDataAsync().then((data) => {
wantParam['data'] = data;
});
return AbilityConstant.OnContinueResult.AGREE;
}
这是绝对行不通的。系统会检测这里的执行耗时,如果超过 1 秒,接续流程会被直接掐断。你必须明确一个原则:onContinue 是一个严格同步的方法。你需要流转的数据(如视频进度、文档 ID),必须在触发接续动作之前,就已经准备好了。比如将它们放在 AppStorage 中,在 onContinue 里只做浅拷贝赋值。
冷启动与热启动的双重考验
当数据通过分布式软总线传到了目标设备(平板),接续的生命周期就开始了。这里有一个极其容易遗漏的警示:平板上你的应用可能是关闭的,也可能是挂在后台的。
如果应用是关闭的,系统会回调 UIAbility 的 onCreate;如果应用在后台,系统会回调 onNewWant。很多开发者只写了 onCreate 的逻辑,测试时一旦平板应用在后台,接续就彻底失效。
正确的处理逻辑是提取一个公共方法,在两个生命周期里都调用它:
typescript
private handleContinue(want: Want) {
// 判断 Want 中是否携带了接续参数
if (want.parameters && want.parameters['video_progress']) {
const progress = want.parameters['video_progress'] as number;
// 将进度存入全局状态,等待页面去读取
AppStorage.setOrCreate('restored_progress', progress);
}
}
页面跳转的时机灾难
收到进度数据后,很多人会在 onCreate 里直接调用 router.pushUrl 跳转到详情页。结果就是屏幕黑一下,然后应用直接报错闪退。
这是典型的生命周期错位。onCreate 执行时,ArkUI 的 WindowStage 还没创建完毕,你的页面栈根本不存,此时跳转就像是在半空中建楼。
正确的解法是:让数据先"落地"。把数据存在 AppStorage 里,让应用的首屏正常加载。然后在首屏的 aboutToAppear 中,去读取这个 AppStorage,如果发现存在流转过来的进度,再执行 router.pushUrl 跳转,或者直接在原页面恢复播放进度。这样用户的视觉体验就是:平板打开应用,首页闪了一下,直接无缝进入了正在看的那个视频。
环境校验与降级预案
在开发阶段,接续功能时灵时不灵是常态。绝大多数原因不是代码错了,而是设备环境不满足要求:两台设备没有登录同一个华为账号、蓝牙或 Wi-Fi 被关闭、甚至距离过远导致软总线断开。
在正式上线前,必须做前置检查。通过 distributedDeviceManager 查询当前是否有可用设备。如果没有,直接把这个流转按钮置灰,或者加上引导提示,千万别等用户点了之后才弹一个冷冰冰的"接续失败"。
用两台真机,写一个最简单的音乐播放器。在手机上播放到第 30 秒,暂停。上滑控制中心触发多设备协同,流转到平板。观察平板上的应用是否冷启动,进度条是不是正好停在第 30 秒的位置。