|
#2020征文-手機(jī)#【鴻蒙基地】鴻蒙跨設(shè)備啟動窗口:Page Ability,
HarmonyOS的核心特性(或稱為賣點)之一就是軟總線技術(shù),而Page Ability的跨設(shè)備遷移是軟總線的一個具體技術(shù)實現(xiàn)。所謂跨設(shè)備遷移Page Ability,是指設(shè)備A中的特定App調(diào)用設(shè)備B中該App的Page Ability。這有一個前提,就是設(shè)備A和設(shè)備B都安裝了同一個App。如果B設(shè)備沒有安裝App,B設(shè)備就會自動從華為應(yīng)用商店下載這個App,當(dāng)然,這一過程是完全靜默的。下載完后,就會自動啟動相應(yīng)的Page Ability。這種技術(shù)不僅可以啟動另一個設(shè)備上的Page Ability,還可以向另一個設(shè)備中的Page Ability傳遞數(shù)據(jù)。
這種技術(shù)的一個主要應(yīng)用場景是,可以將在設(shè)備A上完成了一半的工作,遷移到設(shè)備B上繼續(xù)完成。例如,在家中平板電腦上要回一封EMail,但臨時有急事,需要出門,這時可以將在平板電腦上寫了一半的EMail遷移到手機(jī)上,需要在路上完成剩下的工作。
1. 跨設(shè)備遷移前的準(zhǔn)備工作
在進(jìn)行跨設(shè)備遷移之前,需要為HARMonyOS設(shè)備做一下準(zhǔn)備:
(1) 打開HarmonyOS設(shè)備中的藍(lán)牙;
(2)HarmonyOS設(shè)備需要連入Wi-Fi,而且多個HarmonyOS需要在同一個網(wǎng)段;
(3)多個HarmonyOS設(shè)備需要用同一個華為開發(fā)者賬號登錄,如圖1所示。
圖1 用同一個華為開發(fā)者賬號登錄
(4)點擊“設(shè)置”>“更多連接”>“多設(shè)備協(xié)同”,進(jìn)入多設(shè)備協(xié)同窗口,打開多設(shè)備協(xié)同開關(guān),如圖2所示。
圖2 多設(shè)備協(xié)同
(5)修改HarmonyOS設(shè)備名。點擊“設(shè)置”>“藍(lán)牙”>“設(shè)備名稱”,進(jìn)入設(shè)備名稱窗口,輸入一個新的什么名稱,如圖3所示。盡管這一步不是必須的,但如果擁有多部HarmonyOS設(shè)備,可能很多HarmonyOS設(shè)備的名稱是相同或相近的。為了更好區(qū)分不同的HarmonyOS設(shè)備,建議修改HarmonyOS設(shè)備名稱。
圖3 修改HarmonyOS設(shè)備名稱
2 獲取設(shè)備列表
跨設(shè)備遷移是通過設(shè)備ID來區(qū)分不同設(shè)備的,所以首先要獲取所有可用的設(shè)備的ID。獲取設(shè)備ID需要調(diào)用DeviceManager.getDeviceList方法,該方法返回一個List對象,類型是DeviceInfo,用來描述設(shè)備的相關(guān)信息,包括設(shè)備ID、設(shè)備名稱(就是上一節(jié)設(shè)置的設(shè)備名稱)等。實現(xiàn)代碼如下:
List<DeviceInfo> deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
getDeviceList方法有一個參數(shù),是一個int類型的值,表示獲取什么狀態(tài)的設(shè)備的信息?梢灾付ǖ闹等缦拢
(1) DeviceInfo.FLAG_GET_ONLINE_DEVICE:獲取所有在線設(shè)備的信息;
(2) DeviceInfo. FLAG_GET_OFFLINE_DEVICE:獲取所有離線設(shè)備的信息;
(3) DeviceInfo. FLAG_GET_ALL_DEVICE:獲取所有設(shè)備的信息;
通常會使用第1個值,獲取所有在線設(shè)備的信息,因為只有設(shè)備在線,才能將Page Ability遷移到該設(shè)備上。
下面給出一個案例,該案例實現(xiàn)了一個通用的顯示可用設(shè)備列表的Page Ability,點擊某一個設(shè)備,會返回該設(shè)備的ID,
在device_ids.xml布局文件中放置了一個ListContainer組件,用于顯示獲取的所有可用設(shè)備的相關(guān)信息。實現(xiàn)代碼如下:
public class DeviceIdsAbility extends Ability {
// 保存獲取到的所有設(shè)備的信息
private List<DeviceInfo> deviceInfos;
private ListContainer listContainerDeviceIds;
// 獲取所有可用的設(shè)備的相關(guān)信息
public static List<DeviceInfo> getAvailabLEDeviceIds() {
List<DeviceInfo> deviceInfoList =
DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
IF (deviceInfoList == null) {
return new ArrayList<>();
}
if (deviceInfoList.size() == 0) {
return new ArrayList<>();
}
return deviceInfoList;
}
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.layout_device_ids);
deviceInfos = getAvailableDeviceIds();
listContainerDeviceIds = 、
(ListContainer)findComponentById(ResourceTable.Id_listcontainer_deviceids);
if(listContainerDeviceIds != null) {
// 為ListContainer組件設(shè)置列表項監(jiān)聽器
listContainerDeviceIds.setItemclickedListener(new ListContainer.ItemClickedListener() {
@Override
public void onItemClicked(ListContainer listContainer, Component component, int i, long l) {
// 當(dāng)單擊某個列表項(設(shè)備)后,會獲取該設(shè)備的ID,并將這個ID作為Page Ability
// 的結(jié)果返回
String deviceId = deviceInfos.get(i).getDeviceId();
Intent intent = new Intent();
intent.setParam(“deviceId“, deviceId);
setResult(100,intent);
// 關(guān)閉當(dāng)前的Page Ability
terminateAbility();
}
});
// 為ListContainer組件設(shè)置Provider
listContainerDeviceIds.setItemProvider(new RecycleItemProvider() {
@Override
public int getCount() {
return deviceInfos.size();
}
@Override
public Object getItem(int i) {
return deviceInfos.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
if(component == null) {
// 如果component為null,說明沒有可以利用的列表項視圖,所以要從布局文件
// 裝載一個新的視圖對象
component = (DirectionalLayout)LayoutScatter.getInstance(DeviceIdsAbility.this).parse(ResourceTable.Layout_device_id_item,null,false);
}
Text textDeviceName = (Text)component.findComponentById(ResourceTable.Id_text_device_name);
Text textDeviceId = (Text)component.findComponentById(ResourceTable.Id_text_device_id);
if(textDeviceName != null) {
// 顯示設(shè)備名
textDeviceName.setText(deviceInfos.get(i).getDeviceName());
}
if(textDeviceId != null) {
// 顯示設(shè)備ID
textDeviceId.setText(deviceInfos.get(i).getDeviceId());
}
return component;
}
});
}
}
}
在DeviceIdsAbility類中為ListContainer組件裝載列表項時,在getComponent方法中利用了第2個參數(shù)component,該參數(shù)就是列表項的根視圖。如果component為null,表明并沒有可以利用的列表項視圖,所以要創(chuàng)建一個新的列表項視圖。如果不為null,表明可以利用其他的還沒有顯示的列表項視圖,只需要替換該視圖的Text組件中顯示的信息即可。
最后在config.json文件中添加一些與分布式相關(guān)的權(quán)限。
“reqPermissions“: [
{
“name“: “ohos.permission.GET_DISTRIBUTED_DEVICE_INFO“
},
{
“name“: “com.huawei.permission.ACCESS_DISTRIBUTED_ABILITY_GROUP“
},
{
“name“: “ohos.permission.DISTRIBUTED_DATASYNC“
}
]
運行程序,會看到如圖4所示的設(shè)備列表。
圖4 獲取可用設(shè)備的ID
要注意的是,通過DeviceManager.getDeviceList方法只能獲取其他設(shè)備的信息,不能獲取自身的信息,例如,有設(shè)備A、設(shè)備B和設(shè)備C。在設(shè)備A中只能獲取設(shè)備B和設(shè)備C的信息,而不能獲取設(shè)備A的信息。在設(shè)備B和設(shè)備C中的表現(xiàn)也類似。
3 根據(jù)設(shè)備ID調(diào)用Page Ability
一個Page Ability要想跨設(shè)備訪問,必須實現(xiàn)IAbilityContinuation接口,否則會拋出異常。該接口必須實現(xiàn)的有4個方法,他們的含義如下:
public inteRFace IAbilityContinuation {
// 開始遷移,如果返回true,表示可以開始遷移
boolean onStartContinuation();
// 開始傳遞數(shù)據(jù),如果返回true,表示成功傳遞數(shù)據(jù)
boolean onSaveData(IntentParams var1);
// 開始恢復(fù)數(shù)據(jù),如果返回true,表示成功恢復(fù)數(shù)據(jù)
boolean onRestoreData(IntentParams var1);
// 已經(jīng)完成Page Ability遷移
void onCompleteContinuation(int var1);
}
假設(shè)在設(shè)備A上將Page Ability遷移到設(shè)備B。onStartContinuation方法和onSaveData方法是在設(shè)備A上被調(diào)用的,而onRestoreData方法和onCompleteContinuation方法是在設(shè)備B上被調(diào)用的。為了遷移Page Ability,需要在設(shè)備A上執(zhí)行下面的代碼:
continueAbility(deviceId);
其中deviceID是設(shè)備ID。當(dāng)調(diào)用該方法后,在設(shè)備A上就會依次調(diào)用onStartContinuation方法和onSaveData方法,在設(shè)備B上會依次調(diào)用onRestoreData方法和onCompleteContinuation方法。其中onSaveData方法和onRestoreData方法都有一個IntentParams類型的參數(shù),通過該參數(shù)可以在設(shè)備A和設(shè)備B之間通過Page Ability傳遞數(shù)據(jù)(使用方式與Intent類似)。通常在onRestoreData方法中恢復(fù)Page Ability從設(shè)備A上遷移到設(shè)備B上時的數(shù)據(jù)。
下面給出一個實際的案例,在Page Ability上放置了一個TextField組件,并在該組件中輸入了一些文本,然后點擊按鈕,將該Page Ability遷移到另一部HarmonyOS手機(jī)上,并恢復(fù)遷移時的數(shù)據(jù)。
實現(xiàn)代碼如下:
public class CrossDevicePageAbility extends Ability implements IAbilityContinuation {
private List<DeviceInfo> deviceInfos;
private ListContainer listContainerDeviceIds;
private TextField textFieldContent;
private String content;
// 授權(quán)方法
private void requestPermission() {
// 實現(xiàn)Page Ability跨設(shè)備遷移,必須用Java代碼申請下面的權(quán)限
// 否則不會有任何反應(yīng)
String[] permission = {
“ohos.permission.DISTRIBUTED_DATASYNC“};
List<String> applyPermissions = new ArrayList<>();
for (String element : permission) {
// 驗證自身是否已經(jīng)獲得了該權(quán)限
if (verifySelfPermission(element) != 0) {
if (canRequestPermission(element)) {
// 如果未獲得權(quán)限,將該權(quán)限添加到權(quán)限列表
applyPermissions.add(element);
} else {
}
} else {
}
}
// 申請相應(yīng)權(quán)限
requestPermissionsFromUser(applyPermissions.toArray(new String[0]), 0);
}
// 要想成功跨設(shè)備遷移Page Ability,該方法必須返回true
@Override
public boolean onStartContinuation() {
return true;
}
@Override
public boolean onSaveData(IntentParams intentParams) {
// 保存要傳遞的數(shù)據(jù)
intentParams.setParam(“content“,textFieldContent.getText());
return true;
}
@Override
public boolean onRestoreData(IntentParams intentParams) {
// 獲取傳遞過來的數(shù)據(jù)
content = String.valueOf(intentParams.getParam(“content“));
return true;
}
@Override
public void onCompleteContinuation(int i) {
}
@Override
protected void onAbilityResult(int requestCode, int resultCode, Intent resultData) {
// 當(dāng)選擇設(shè)備后,利用返回的設(shè)備ID遷移Page Ability
if(resultCode == 100 && requestCode == 99) {
// 獲取設(shè)備ID
String deviceId = resultData.getStringParam(“deviceId“);
Tools.showTip(this, deviceId);
// 跨設(shè)備遷移Page Ability
continueAbility(deviceId);
}
}
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_cross_device_page_ability);
// 申請權(quán)限
requestPermission();
Button button =
(Button)findComponentById(ResourceTable.Id_button_cross_device_page_ability);
if(button != null) {
button.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
// 顯示列表列表窗口
Intent intentPageAbility = new Intent();
Operation operation = new Intent.OperationBuilder()
.withBundleName(“com.unitymarvel.demo“)
.withAbilityName(“com.unitymarvel.demo.ability.DeviceIdsAbility“)
.build();
intentPageAbility.setOperation(operation);
startAbilityForResult(intentPageAbility,99);
}
});
}
textFieldContent = (TextField)findComponentById(ResourceTable.Id_textfield_content);
if(textFieldContent != null) {
// 恢復(fù)TextField組件中的數(shù)據(jù)
textFieldContent.setText(content);
}
}
}
閱讀這段代碼,需要了解下面幾點:
(1)要想成功遷移Page Ability,并成功傳遞數(shù)據(jù)。onStartContinuation方法、onSaveData方法和onRestoreData方法都必須返回true,如果讀者使用IDE的自動生成代碼功能,默認(rèn)這幾個方法都會返回false,請將他們的返回值改成true;
(2)在HarmonyOS中有一些權(quán)限,并不是在config.json中聲明就可以了,還需要使用Java代碼申請,例如,Page Ability跨設(shè)備遷移就需要使用Java代碼申請ohos.permission.DISTRIBUTED_DATASYNC權(quán)限。如果是第一次申請,會彈出如圖5的授權(quán)對話框,點擊“始終允許”按鈕關(guān)閉該對話框,第2次申請權(quán)限,就不會彈出該對話框了;
(3)由于onRestoreData方法在onStart方法之前調(diào)用,所以不能直接在onRestoreData方法中使用組件對象,因為組件對象通常都是在onStart方法中創(chuàng)建的。所以在onRestoreData方法被調(diào)用時,這些組件對象還都是空。正確的做法是在onRestoreData方法中將要恢復(fù)的數(shù)據(jù)保存到成員變量中,然后在onStart方法中創(chuàng)建完組件對象后,用這些變量恢復(fù)組件中的數(shù)據(jù)。
(4)本例考慮了多部HarmonyOS設(shè)備遷移的問題,所以使用了上一節(jié)編寫的設(shè)備列表窗口。在開始跨設(shè)備遷移Page Ability之前,會先彈出一個設(shè)備列表窗口,當(dāng)用戶選擇一個設(shè)備后,會返回該設(shè)備的ID,然后在onAbilityResult方法中獲取這個返回的設(shè)備ID,最后使用continueAbility方法遷移Page Ability;
圖5 授權(quán)對話框
現(xiàn)在運行程序,關(guān)閉授權(quán)對話框,并在TextField組件中輸入一些內(nèi)容,最后點擊“跨設(shè)備遷移Page Ability”按鈕,會彈出一個設(shè)備列表窗口,選擇相應(yīng)的設(shè)備后,會在選中的設(shè)備中彈出同樣的Page Ability,并且TextField組件的數(shù)據(jù)與原設(shè)備上的完全相同,如圖6所示。注意,只要被調(diào)用方安裝了App,不管設(shè)備是否已經(jīng)啟動了App,否會自動彈出這個被遷移的Page Ability。
圖6 跨設(shè)備遷移Page Ability的效果
原文來自51CTO |
|