HarmonyOS流轉(zhuǎn)之跨端遷移
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
前言
流轉(zhuǎn)在HarmonyOS中泛指多設(shè)備分布式操作,也是HarmonyOS的亮點之一。流轉(zhuǎn)按體驗可以分為跨端遷移和多端協(xié)同,這里主要跟大家講一下如何進(jìn)行跨端遷移,以及我在項目開發(fā)過程中,所遇到的問題與解決方法。
具體概念這里就不做過多的贅述了,大家可以查閱官方文檔。
開發(fā)步驟
在開發(fā)過程中,我們可以根據(jù)業(yè)務(wù)需求分為以下兩種場景:
- 同個FA之間的遷移(Ability1—Ability1);
- 不同F(xiàn)A之間的遷移(Ability1—Ability2)。
下面給大家介紹一下以上兩種場景的具體的開發(fā)步驟。
同個FA之間的遷移
同個FA之間的遷移是指不同設(shè)備端安裝了同個FA,官方文檔已經(jīng)有比較詳細(xì)的開發(fā)步驟,下面只給大家講一下需要注意的事項及我所遇到的問題避免大家踩坑。
1.我們在創(chuàng)建完一個FA之后,因為我們大部門的業(yè)務(wù)邏輯都是在AbilitySlice,所以我們在Ability及AbilitySlice都要去實現(xiàn)IAbilityContinuation 接口,并且將Ability中實現(xiàn)的onStartContinuation()、onSaveData(IntentParams intentParams)、onRestoreData(IntentParams intentParams)的返回值,都設(shè)為true。
- public class MainAbility extends Ability implements IAbilityContinuation {
- @Override
- public boolean onStartContinuation() {
- return true;
- }
- @Override
- public boolean onSaveData(IntentParams intentParams) {
- return true;
- }
- @Override
- public boolean onRestoreData(IntentParams intentParams) {
- return true;
- }
- //省略部分代碼
- ...
- }
2.在對應(yīng)的FA模塊的config.json中,配置對應(yīng)的權(quán)限,且在代碼中也需要動態(tài)申請。
- "reqPermissions": [
- {
- "name": "ohos.permission.DISTRIBUTED_DATASYNC" },
- {
- "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE" },
- {
- "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"},
- {
- "name": "ohos.permission.GET_BUNDLE_INFO"}
- ]
- if (canRequestPermission(SystemPermission.DISTRIBUTED_DATASYNC)) {
- // 是否可以申請彈框授權(quán)(首次申請或者用戶未選擇禁止且不再提示)
- requestPermissionsFromUser(
- new String[]{SystemPermission.DISTRIBUTED_DATASYNC}, PERMISSIONS_REQUEST_DISTRIBUTED);
- }
3.定義相關(guān)參數(shù)、設(shè)置流轉(zhuǎn)任務(wù)管理服務(wù)回調(diào)函數(shù)、注冊流轉(zhuǎn)任務(wù)管理服務(wù)、管理流轉(zhuǎn)的目標(biāo)設(shè)備,同時需要在流轉(zhuǎn)結(jié)束時解注冊流轉(zhuǎn)任務(wù)管理服務(wù)。
- // 流轉(zhuǎn)應(yīng)用包名
- private String BUNDLE_NAME = "XXX.XXX.XXX";
- // 注冊流轉(zhuǎn)任務(wù)管理服務(wù)后返回的Ability token
- private int abilityToken;
- // 用戶在設(shè)備列表中選擇設(shè)備后返回的設(shè)備ID
- private String selectDeviceId;
- // 獲取流轉(zhuǎn)任務(wù)管理服務(wù)管理類
- private IContinuationRegisterManager continuationRegisterManager;
- // 設(shè)置流轉(zhuǎn)任務(wù)管理服務(wù)設(shè)備狀態(tài)變更的回調(diào)
- private IContinuationDeviceCallback continuationDeviceCallback = new IContinuationDeviceCallback() {
- @Override
- public void onDeviceConnectDone(String deviceId, String deviceType) {
- selectDeviceId = deviceId;
- continuationRegisterManager.updateConnectStatus(abilityToken, selectDeviceId, DeviceConnectState.CONNECTING.getState(), null);
- ...
- }
- @Override
- public void onDeviceDisconnectDone(String s) {
- getUITaskDispatcher().asyncDispatch(() -> {
- continuationRegisterManager.updateConnectStatus(abilityToken, selectDeviceId, DeviceConnectState.DIS_CONNECTING.getState(), null);
- });
- unRegisterContinuation();
- }
- ;
- // 設(shè)置注冊流轉(zhuǎn)任務(wù)管理服務(wù)回調(diào)
- private RequestCallback requestCallback = new RequestCallback() {
- @Override
- public void onResult(int result) {
- abilityToken = result;
- }
- };
- ...
- @Override
- public void onStart(Intent intent) {
- ...
- continuationRegisterManager = getContinuationRegisterManager();
- }
- @Override
- public void onStop() {
- super.onStop();
- // 解注冊流轉(zhuǎn)任務(wù)管理服務(wù)
- continuationRegisterManager.unregister(abilityToken, null);
- // 斷開流轉(zhuǎn)任務(wù)管理服務(wù)連接
- continuationRegisterManager.disconnect();
- }
在Api5的時候IContinuationDeviceCallback的回調(diào)接口跟官方文檔有些出入,當(dāng)你選擇設(shè)備后會在onDeviceConnectDone返回你所選擇的設(shè)備ID及設(shè)備類型。
4.注冊流轉(zhuǎn)服務(wù)之后我們便可以調(diào)起系統(tǒng)流轉(zhuǎn)選擇設(shè)備彈窗,可以通過ExtraParams對設(shè)備進(jìn)行過濾,如不需要過濾,可不傳。
- ExtraParams params = new ExtraParams();
- String[] devTypes = new String[]{ExtraParams.DEVICETYPE_SMART_PHONE, ExtraParams.DEVICETYPE_SMART_WATCH, ExtraParams.DEVICETYPE_SMART_PAD};
- params.setDevType(devTypes);
- registerContinuation();
- // 顯示選擇設(shè)備列表
- continuationRegisterManager.showDeviceList(abilityToken, params, new RequestCallback() {
- @Override
- public void onResult(int result) {
- }
- });
5.選擇完設(shè)備之后會通過上述的IContinuationDeviceCallback的onDeviceConnectDone方法進(jìn)行回調(diào),之后通過continueAbility方法傳入目標(biāo)設(shè)備的DeviceID,將運(yùn)行的FA遷移到目標(biāo)設(shè)備,實現(xiàn)業(yè)務(wù)在設(shè)備間無縫遷移。
- // 設(shè)置流轉(zhuǎn)任務(wù)管理服務(wù)設(shè)備狀態(tài)變更的回調(diào)
- private IContinuationDeviceCallback continuationDeviceCallback = new IContinuationDeviceCallback() {
- @Override
- public void onDeviceConnectDone(String deviceId, String deviceType) {
- selectDeviceId = deviceId;
- getUITaskDispatcher().asyncDispatch(() -> {
- continuationRegisterManager.updateConnectStatus(abilityToken, selectDeviceId, DeviceConnectState.CONNECTING.getState(), null);
- });
- if (selectDeviceId != null) {
- continueAbility(selectDeviceId);
- }
- ...
- }
- @Override
- public void onDeviceDisconnectDone(String s) {
- ...
- unRegisterContinuation();
- }
- };
6.在FA遷移中我覺得最主要的部分就是狀態(tài)和數(shù)據(jù)的傳遞,要讓用戶體驗到”無縫“的用戶體驗,需要通過實現(xiàn)IAbilityContinuation接口來實現(xiàn)數(shù)據(jù)的傳遞,主要代碼如下:
- @Override
- public boolean onSaveData(IntentParams saveData) {
- //根據(jù)業(yè)務(wù)需求,在這里去設(shè)置需要傳遞的數(shù)據(jù)
- saveData.setParam("continueParam", continueParam);
- return true;
- }
- @Override
- public boolean onRestoreData(IntentParams restoreData) {
- // 遠(yuǎn)端FA遷移傳來的狀態(tài)數(shù)據(jù),開發(fā)者可以按照自身業(yè)務(wù)對這些數(shù)據(jù)進(jìn)行處理
- Object data = restoreData.getParam("continueParam");
- getUITaskDispatcher().asyncDispatch(() -> {
- });
- return true;
- }
需要注意的是,在onRestoreData處理數(shù)據(jù)更新UI的時候,需要在UI線程中去更新,否則會報錯。
不同F(xiàn)A之間的遷移
在實際開發(fā)中可能會因為設(shè)備端的部分需求、UI的不同,例如車機(jī)、手機(jī)、手表,從而開發(fā)了不同的FA。不同F(xiàn)A之間的遷移幾乎與同個FA之間遷移配置一致,只是我們的AbilitySlice不需要再實現(xiàn)IAbilityContinuation接口來實現(xiàn)數(shù)據(jù)的同步,而是通過Intent,具體實現(xiàn)如下。
1.首先,我們先在選擇設(shè)備成功后的回調(diào)IContinuationDeviceCallback初始化分布式環(huán)境。
- // 設(shè)置流轉(zhuǎn)任務(wù)管理服務(wù)設(shè)備狀態(tài)變更的回調(diào)
- private IContinuationDeviceCallback continuationDeviceCallback = new IContinuationDeviceCallback() {
- @Override
- public void onDeviceConnectDone(String deviceId, String deviceType) {
- selectDeviceId = deviceId;
- //省略部分代碼
- ...
- try {
- // 初始化分布式環(huán)境
- DeviceManager.initDistributedEnvironment(selectDeviceId, new IInitCallback() {
- @Override
- public void onInitSuccess(String success) {
- }
- @Override
- public void onInitFailure(String failure, int result) {
- }
- });
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- ...
- }
- ....
- };
2.之前,我們是通過continueAbility()方法進(jìn)行跳轉(zhuǎn),而現(xiàn)在我們需要通過Intent方法進(jìn)行跳轉(zhuǎn)。
- Intent intent = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId(deviceId)
- .withBundleName(bundleName)
- .withAbilityName(abilityName)
- .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
- .build();
- intent.setOperation(operation);
- IntentParams intentParams = new IntentParams();
- //通過IntentParams傳遞參數(shù)
- ...
- startAbility(intent);
在接收方,我們可以通過onStart(Intent intent)方法接受傳遞過來的參數(shù),再根據(jù)自己的業(yè)務(wù)邏輯實現(xiàn)數(shù)據(jù)同步。
自定義設(shè)備選擇彈窗
在實際項目開發(fā)中我們也可以自定義流轉(zhuǎn)彈窗樣式,但并不推薦這種方式,經(jīng)測試發(fā)現(xiàn)只有在兩個設(shè)備通過藍(lán)牙連接的時候才能獲取到設(shè)備列表,只有在特定的場景,例如手機(jī)與車機(jī)、手機(jī)與手表在實際使用過程中我們基本上是會保持藍(lán)牙連接的,通過這種方式實現(xiàn)流轉(zhuǎn)會更穩(wěn)定。但如果不能保持藍(lán)牙實時連接的場景則不推薦。
1.官方API提供了DeviceManager.getDeviceList()來獲取遠(yuǎn)端設(shè)備,具體代碼如下:
- public static List<DeviceInfo> getDeviceList() {
- // 調(diào)用DeviceManager的getDeviceList接口,通過FLAG_GET_ONLINE_DEVICE標(biāo)記獲得在線設(shè)備列表
- List<DeviceInfo> onlineDevices = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
- // 判斷組網(wǎng)設(shè)備是否為空
- if (onlineDevices == null) {
- LogUtil.e(TAG, "online devices is null");
- return new ArrayList<>();
- }
- return onlineDevices;
- }
2.獲取到設(shè)備列表后,我們就可以自行實現(xiàn)頁面了,在上述的showDeviceList()彈出設(shè)備列表的位置替換成自己的彈窗即可。
效果展示

結(jié)語
目前在DevEco Studio 2.1 Release以上版本已經(jīng)支持跨端遷移的模擬器了,如果沒有顯示出來可以在Settings-DevEco Labs 勾選Enable Super Device。
文章相關(guān)附件可以點擊下面的原文鏈接前往下載:
https://harmonyos.51cto.com/resource/1426
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)