1# 跨端迁移开发指导 2 3## 场景介绍 4 5迁移的主要工作是实现将应用当前任务,包括页面控件状态变量、分布式对象等,迁移到远端设备。页面控件状态变量用于同步页面UI数据,分布式对象用于同步内存中的数据。 6 7## 接口说明 8 9迁移提供的能力如下,具体的API详见[接口文档](../reference/apis/js-apis-application-ability.md)。 10 11**表1** 应用迁移API接口功能介绍 12 13|接口名 | 描述| 14|:------ | :------| 15| onContinue(wantParam : {[key: string]: any}): OnContinueResult | 迁移**发起端**在该回调中保存迁移所需要的数据,同时返回是否同意迁移:AGREE表示同意,REJECT表示拒绝;MISMATCH表示版本不匹配。 | 16| onCreate(want: Want, param: AbilityConstant.LaunchParam): void; | 多实例应用迁移**目标端**在该回调中完成数据恢复,并触发页面恢复。 | 17| onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void; | 单实例应用迁移**目标端**在该回调中完成数据恢复,并触发页面恢复。 | 18 19 20 21**图1** 迁移开发示意图 22 23![continuation_dev](figures/continuation-info.png) 24 25迁移实际上是Ability携带数据的跨端启动。触发迁移动作时,会在A设备上通过系统回调应用的onContinue()方法,开发者需要在此方法中实现当前数据的保存。然后系统发起在B设备上的跨端启动,并将数据一同传输到B设备。B设备系统回调onCreate()/onNewWant()方法,开发者需要在此方法中实现传输过来的数据的恢复。 26 27## 开发步骤 28 29下文代码片段来自参考[示例](https://gitee.com/openharmony/ability_dmsfwk/tree/master/services/dtbschedmgr/test/samples/continuationManualTestSuite)。 30 31### 迁移应用 32 331. 配置 34 35 - 配置应用支持迁移 36 37 在module.json5中配置continuable字段:true表示支持迁移,false表示不支持,默认为false。配置为false的应用将被系统识别为无法迁移。 38 39 ```javascript 40 { 41 "module": { 42 "abilities": [ 43 { 44 "continuable": true 45 } 46 ] 47 } 48 } 49 ``` 50 51 52 53 54 - 配置应用启动类型 55 56 多实例应用在module.json5中将launchType字段配置为multiton,目标端将会拉起一个新的应用,并恢复页面;单实例将该字段配置为singleton,如果目标端应用已经打开,迁移将会将已有页面栈清空,并根据迁移数据恢复页面。关于单实例与多实例的更多信息详见[ability开发指导](./stage-ability.md)启动模式。 57 58 多实例: 59 60 ```javascript 61 { 62 "module": { 63 "abilities": [ 64 { 65 "launchType": "multiton" 66 } 67 ] 68 } 69 } 70 ``` 71 72 缺省或如下配置为单实例: 73 74 ```javascript 75 { 76 "module": { 77 "abilities": [ 78 { 79 "launchType": "singleton" 80 } 81 ] 82 } 83 } 84 ``` 85 86 87 88 - 申请分布式权限 89 90 支持跨端迁移的应用需要在module.json5申请分布式权限 DISTRIBUTED_DATASYNC。 91 92 ```javascript 93 "requestPermissions": [ 94 { 95 "name": "ohos.permission.DISTRIBUTED_DATASYNC" 96 }, 97 ``` 98 99 100 101 这个权限需要在应用首次启动的时候弹窗让用户授予,可以通过在ability的onWindowStageCreate中添加如下代码实现: 102 103 ```javascript 104 requestPermissions = async () => { 105 let permissions: Array<string> = [ 106 "ohos.permission.DISTRIBUTED_DATASYNC" 107 ]; 108 let needGrantPermission = false 109 let accessManger = accessControl.createAtManager() 110 Logger.info("app permission get bundle info") 111 let bundleInfo = await bundle.getApplicationInfo(BUNDLE_NAME, 0, 100) 112 Logger.info(`app permission query permission ${bundleInfo.accessTokenId.toString()}`) 113 for (const permission of permissions) { 114 Logger.info(`app permission query grant status ${permission}`) 115 try { 116 let grantStatus = await accessManger.verifyAccessToken(bundleInfo.accessTokenId, permission) 117 if (grantStatus === PERMISSION_REJECT) { 118 needGrantPermission = true 119 break; 120 } 121 } catch (err) { 122 Logger.error(`app permission query grant status error ${permission} ${JSON.stringify(err)}`) 123 needGrantPermission = true 124 break; 125 } 126 } 127 if (needGrantPermission) { 128 Logger.info("app permission needGrantPermission") 129 try { 130 await accessManger.requestPermissionsFromUser(this.context, permissions) 131 } catch (err) { 132 Logger.error(`app permission ${JSON.stringify(err)}`) 133 } 134 } else { 135 Logger.info("app permission already granted") 136 } 137 } 138 ``` 139 140 141 142 1432. 实现onContinue接口 144 145 onContinue()接口在发起端被调用,主要用于在迁移发起时,通知开发者保存控件状态变量和内存中数据,准备迁移。当应用准备完成后,需要返回OnContinueResult.AGREE(0)表示同意迁移,否则返回相应的错误码拒绝迁移。如果不实现该接口,系统将默认为拒绝迁移。 146 147 导入模块 148 149 ```javascript 150 import Ability from '@ohos.application.Ability'; 151 import AbilityConstant from '@ohos.application.AbilityConstant'; 152 ``` 153 154 要实现迁移,此接口必须实现并返回AGREE,否则默认为拒绝迁移。 155 156 另外,在该接口的入参wantParam中可以获取目标设备的deviceId(key为“targetDevice”),以及目标设备上所安装的应用的版本号(key为“version”)。版本号可用来与本应用版本进行对比,做兼容性判断,如果判定本应用版本与远端不兼容,可以返回OnContinueResult.MISMATCH拒绝迁移。 157 158 示例 159 160 ```javascript 161 onContinue(wantParam : {[key: string]: any}) { 162 Logger.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`) 163 let workInput = AppStorage.Get<string>('ContinueWork'); 164 // set user input data into want params 165 wantParam["work"] = workInput // set user input data into want params 166 Logger.info(`onContinue input = ${wantParam["input"]}`); 167 return AbilityConstant.OnContinueResult.AGREE 168 } 169 ``` 170 171 172 1733. 在onCreate/onNewWant接口中实现迁移逻辑 174 175 onCreate()接口在迁移目标端被调用,在目标端ability被拉起时,通知开发者同步已保存的内存数据和控件状态,完成后触发页面的恢复。如果不实现该接口中迁移相关逻辑,ability将会作为普通的启动方式拉起,无法恢复页面。 176 177 远端设备上,在onCreate()中根据launchReason判断该次启动是否为迁移LaunchReason.CONTINUATION 178 179 完成数据恢复后,开发者需要调用restoreWindowStage来触发页面恢复。 180 181 182 183 在入参want中也可以通过want.parameters.version来获取发起端的应用版本号。 184 185 示例 186 187 ```javascript 188 import Ability from '@ohos.application.Ability'; 189 import distributedObject from '@ohos.data.distributedDataObject'; 190 191 export default class MainAbility extends Ability { 192 storage : LocalStorag; 193 194 onCreate(want, launchParam) { 195 Logger.info(`MainAbility onCreate ${AbilityConstant.LaunchReason.CONTINUATION}`) 196 if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) { 197 // get user data from want params 198 let workInput = want.parameters.work 199 Logger.info(`work input ${workInput}`) 200 AppStorage.SetOrCreate<string>('ContinueWork', workInput) 201 202 this.storage = new LocalStorage(); 203 this.context.restoreWindowStage(this.storage); 204 } 205 } 206 } 207 ``` 208如果是单实例应用,则同样的代码实现onNewWant接口即可。 209 210 211 212### 迁移数据 213 214使用分布式对象 215 216分布式数据对象提供了与本地变量类似的操作,实现两个设备的数据同步,当设备1的应用A的分布式数据对象增、删、改数据后,设备2的应用A也可以获取到对应的数据变化,同时还能监听数据变更以及对端数据对象的上下线。用法详见[分布式对象指导文档](../database/database-distributedobject-guidelines.md)。 217 218迁移场景中,分布式对象(distributedDataObject)主要用于将本机内存数据同步到目标设备。 219 220- 发起端在onContinue()中,将待迁移的数据存入分布式对象中,并调用save接口将数据保存并同步到远端,然后设置好session id,并通过wantParam将session id传到远端设备。 221 222 ```javascript 223 import Ability from '@ohos.application.Ability'; 224 import distributedObject from '@ohos.data.distributedDataObject'; 225 226 var g_object = distributedObject.createDistributedObject({data:undefined}); 227 228 export default class MainAbility extends Ability { 229 sessionId : string; 230 231 onContinue(wantParam : {[key: string]: any}) { 232 Logger.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`) 233 234 if (g_object.__sessionId === undefined) { 235 this.sessionId = distributedObject.genSessionId() 236 Logger.info(`onContinue generate new sessionId`) 237 } 238 else { 239 this.sessionId = g_object.__sessionId; 240 } 241 242 wantParam["session"] = this.sessionId 243 g_object.data = AppStorage.Get<string>('ContinueStudy'); 244 Logger.info(`onContinue sessionId = ${this.sessionId}, name = ${g_object.data}`) 245 g_object.setSessionId(this.sessionId); 246 g_object.save(wantParam.targetDevice, (result, data)=>{ 247 Logger.info("save callback"); 248 Logger.info("save sessionId " + data.sessionId); 249 Logger.info("save version " + data.version); 250 Logger.info("save deviceId " + data.deviceId); 251 }); 252 ``` 253 254 255 256- 目标设备在onCreate()中,取出发起端传过来的session id,建立分布式对象并关联该session id,这样就能实现分布式对象的同步。需要注意的是,在调用restoreWindowStage之前,迁移需要的分布式对象必须全部关联完,保证能够获取到正确的数据。 257 258 ```javascript 259 import Ability from '@ohos.application.Ability'; 260 import distributedObject from '@ohos.data.distributedDataObject'; 261 262 var g_object = distributedObject.createDistributedObject({data:undefined}); 263 264 export default class MainAbility extends Ability { 265 storage : LocalStorag; 266 267 268 onCreate(want, launchParam) { 269 Logger.info(`MainAbility onCreate ${AbilityConstant.LaunchReason.CONTINUATION}`) 270 if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) { 271 // get distributed data object session id from want params 272 this.sessionId = want.parameters.session 273 Logger.info(`onCreate for continuation sessionId: ${this.sessionId}`) 274 275 // in order to fetch from remote, reset g_object.data to undefined first 276 g_object.data = undefined; 277 // set session id, so it will fetch data from remote 278 g_object.setSessionId(this.sessionId); 279 280 AppStorage.SetOrCreate<string>('ContinueStudy', g_object.data) 281 this.storage = new LocalStorage(); 282 this.context.restoreWindowStage(this.storage); 283 } 284 285 } 286 } 287 ``` 288 289 290 291### 其他说明 292 2931. 超时机制: 294 295 - 如果目标端迁移应用未安装,系统会去查询在目标端设备上能否安装,这段最大时间为4s,超出此时间,调用者会收到超时错误码,视为不可在目标端安装。若可安装,则系统会提示用户在目标端安装,安装完成后可再次尝试发起迁移。 296 - 如果目标端迁移应用已安装 ,那么发起迁移后超时时间为20s,若超过此时间,调用者会收到超时错误码,视为此次迁移失败。 297 2982. 当前系统默认支持页面栈信息的迁移,即发起端页面栈会被自动迁移到目标端,无需开发者适配。 299 300 301 302### 约束 303 3041. 迁移要求在同ability之间进行,也就是需要相同的bundleName、moduleName和abilityName,具体含义[应用包配置文件说明](../quick-start/module-configuration-file.md)。 3052. 当前应用只能实现迁移能力,但迁移的动作只能由系统发起。 306 307 308 309### 最佳实践 310 311 为了获得最佳体验,建议100kb以下的数据直接使用wantParam传输,大于100kb的数据采用分布式对象传输。