往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)
✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
✏️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
✏️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
✏️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
✏️ 记录一场鸿蒙开发岗位面试经历~
✏️ 持续更新中……
概述
在日常生活中,需要借助 应用接续 功能来处理设备切换的状况。本文针对长列表进度、媒体播放进度以及Web浏览进度这三个场景,实现了浏览进度接续问题的有效解决,为用户在实现无缝的设备切换与浏览体验延续提供了可靠的方案支撑,使得用户在设备切换时能够轻松接续之前的浏览进度,极大地提升了使用的便利性与连贯性,实现真正的无缝接续。
长列表进度接续:可以让用户从上次浏览的位置继续,无需重新从列表顶部开始滑动查找,精准定位到之前离开的条目附近,节省用户的时间和操作成本,提升在长列表内容浏览时的便利性和体验感。
媒体播放进度接续:能够从源设备暂停的位置继续播放视频,视频的播放进度、画面质量、音频设置等都保持一致,为用户提供不间断的观影体验,无论是在线视频平台的剧集、电影,还是本地存储的视频文件,都能实现流畅的接续播放。
Web浏览进度接续:能够迅速定位到源设备浏览的网页位置,保证用户的浏览连续性,避免重复查找信息的繁琐过程,提高信息获取的效率。
实现原理
接续过程底层依赖分布式框架和软总线,开发者只需要启用接续、保存数据和恢复数据,具体运作机制可参考:
开发流程
进度的接续实质上是要确保进度数据能够在不同设备之间实现精准的传递,并维持同步状态。在实际开发过程中,开发者往往会遇到各式各样复杂的进度接续需求,首要的任务便是深入剖析哪些是对进度起着关键控制作用的数据。在源端发起接续时,要保存数据;到对端接续时,则需准确恢复数据,以此保障进度的连贯性以及设备间数据的一致性。
本章节介绍使用分布式数据对象进行数据同步的方法,更多使用方法可见分布式数据对象跨设备数据同步,具体场景的实现见本文长列表进度接续、媒体播放进度接续和Web浏览进度接续。
- 启用接续
在module.json5文件的abilities中,将continuable标签配置为“true”,表示该UIAbility可被迁移。配置为false的UIAbility将被系统识别为无法迁移且该配置默认值为false。
{
"module": {
// ...
"abilities": [
{
// ...
"continuable": true
}
]
}
}
- 源端保存迁移数据
开发者可以将要迁移的数据通过键值对的方式保存在wantParam中。
async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> {
// 1.1 调用create()接口创建并得到一个分布式数据对象实例
// continueData是一个object,可以用来存储需要接续的数据
let continueIndex = AppStorage.get('continueIndex') as number;
let currentOffset = AppStorage.get('currentOffset') as number;
let continueData: ContinueData = new ContinueData(continueIndex, currentOffset);
let dataObject = distributedDataObject.create(this.context, continueData);
// 1.2 调用genSessionId()接口创建一个sessionId,调用setSessionId()接口设置同步的sessionId,并将这个sessionId放入wantParam
let sessionId = distributedDataObject.genSessionId();
console.log(`gen sessionId: ${sessionId}`);
dataObject.setSessionId(sessionId);
wantParam.distributedSessionId = sessionId;
// 1.3 从wantParam获取接收端设备networkId,使用这个networkId调用save接口保存数据到接收端
let deviceId = wantParam.targetDevice as string;
console.log(`get deviceId: ${deviceId}`);
dataObject.save(deviceId);
return AbilityConstant.OnContinueResult.AGREE;
}
- 对端恢复数据
在onCreate()生命周期回调和onNewWant()生命周期回调中恢复数据。
continueRestore(want: Want) {
if (!want.parameters || !want.parameters.distributedSessionId) {
console.error('missing sessionId');
return;
}
// 2.1 调用create()接口创建并得到一个分布式数据对象实例
let continueData: ContinueData = new ContinueData(undefined,undefined);
let dataObject = distributedDataObject.create(this.context, continueData);
// 2.2 注册恢复状态监听。收到状态为'restored'的回调通知时,表示接收端分布式数据对象已恢复发起端保存过来的数据(有资产数据时,对应的文件也迁移过来了)
dataObject.on('status', (sessionId: string, networkId: string, status: string) => {
if (status === 'restored') {
// 收到'restored'的状态通知表示已恢复发起端保存的数据
AppStorage.setOrCreate('continueIndex', dataObject['continueIndex']);
AppStorage.setOrCreate('currentOffset', dataObject['currentOffset']);
AppStorage.setOrCreate('continueEntry', true);
AppStorage.setOrCreate('setcurrentOffset', true);
}
});
// 2.3 从want.parameters中获取发起端放入的sessionId,调用setSessionId接口设置同步的sessionId
let sessionId = want.parameters.distributedSessionId as string;
dataObject.setSessionId(sessionId);
this.context.restoreWindowStage(new LocalStorage());
}
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'EntryAbility', '%{public}s', 'Ability onCreate');
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
if (want.parameters && want.parameters.distributedSessionId) {
this.continueRestore(want);
}
}
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
if (want.parameters && want.parameters.distributedSessionId) {
this.continueRestore(want);
}
}
}
长列表进度接续
长列表通常用于存储大量信息,使用List、Grid、Scroll、WaterFlow进行封装,系统提供了分布式迁移标识用于这些组件恢复进度状态,在使用时可以轻松的调用,使用方法如下:
WaterFlow() {
// ...
}
.restoreId(1)
使用分布式迁移标识可以快速的实现接续。但该方法具有局限性,其支持场景和版本见 分布式迁移标识API ,如果想在开发中做更多自定义设置以提供更好的体验,可以参照如下步骤 。
- 启用接续
- 使用onDidScroll()接口监听长列表浏览进度变化。
Scroll(this.scroller) {
// ...
.onDidScroll((xOffset: number, yOffset: number, scrollState: ScrollState)=>{
if(!this.setcurrentOffset){
this.currentOffset = this.scroller.currentOffset().yOffset;
}
})
- 将this.WaterFlowIndex在onContinue()回调中保存到分布式对象中。
- 在onNewWant()回调/onCreate()回调中从分布式对象中恢复数据。
- 在onDidBuild()事件中恢复浏览状态。
onDidBuild(): void {
if(this.setcurrentOffset){
this.scroller.scrollTo({xOffset:0, yOffset:this.currentOffset})
this.setcurrentOffset = false;
}
}
媒体播放进度接续
媒体播放需要接续的内容主要包含媒体在播放列表中的集数、播放状态、进度,也可以接续其它播放设置加强用户体验,开发时请参考如下步骤
- 启用接续
- 使用avPlayer.on(‘timeUpdate’)接口监听媒体播放进度的变化。
this.avPlayer.on('timeUpdate', (time: number) => {
this.currentTime = time;
});
- 将当前播放的时间this.time在onContinue()回调中保存到分布式对象中
- 在onNewWant()回调/onCreate()回调中从分布式对象中恢复数据
- 在onLoad()事件中调用seek()接口恢复其状态,注意,其必须在video的playing状态时使用。
if (this.continue) {
this.videoSeek(continueTime);
this.continue = false;
}
Web浏览进度接续
系统提供了Web组件以在应用程序中显示Web页面内容,当Web组件承载大量信息时,浏览进度的接续变得十分重要,接续的内容和长列表类似,均需要传递当前滚动位置,需要使用runJavaScript()接口获取和恢复。
- 启用接续
- 使用onTouch()事件监听滑动页面,使用runJavaScript()接口获取页面滚动条距离顶部的距离。
- 将this.scrollDistance在onContinue()回调中保存到分布式对象中
- 在onNewWant()回调/onCreate()回调中从分布式对象中恢复数据
- 在onPageEnd()回调中调用runJavaScript()接口恢复进度。
Web({ src: this.pageUrl, controller: this.controller })
// ...
.onPageEnd(() => {
// ...
if (this.continueRestore) {
if(this.pageUrl.includes('product_list')){
this.controller.runJavaScript('javascript:document.getElementById("productList").scrollTop = ' + this.scrollDistance);
}else if(this.pageUrl.includes('product_detail')){
this.controller.runJavaScript('javascript:document.getElementById("detail-info").scrollTop = ' + this.scrollDistance);
}
}
})
// ...
.onTouch(async (event: TouchEvent) => {
if (event.type === TouchType.Up) {
if(this.pageUrl.includes('product_list')){
let result = await this.controller.runJavaScript('javascript:document.getElementById("productList").scrollTop');
console.log(result)
this.scrollDistance = Number(result);
}else if(this.pageUrl.includes('product_detail')){
let result = await this.controller.runJavaScript('javascript:document.getElementById("detail-info").scrollTop');
console.log(result)
this.scrollDistance = Number(result);
}
}
})