展现鸿蒙的独特魅力:跨设备调用窗口(Page Ability)_鸿蒙 互相调用

65 阅读7分钟

public class DeviceIdsAbility extends Ability { // 保存获取到的所有设备的信息 private List deviceInfos; private ListContainer listContainerDeviceIds; // 获取所有可用的设备的相关信息 public static List getAvailableDeviceIds() { List 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组件设置列表项监听器 listContainerDeviceIds.setItemClickedListener(new ListContainer.ItemClickedListener() { @Override public void onItemClicked(ListContainer listContainer, Component component, int i, long l) { // 当单击某个列表项(设备)后,会获取该设备的ID,并将这个ID作为Page Ability // 的结果返回 String deviceId = deviceInfos.get(i).getDeviceId(); Intent intent = new Intent(); intent.setParam("deviceId", deviceId); setResult(100,intent); // 关闭当前的Page Ability terminateAbility();

} }); // 为ListContainer组件设置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) { // 显示设备名 textDeviceName.setText(deviceInfos.get(i).getDeviceName()); } if(textDeviceId != null) { // 显示设备ID textDeviceId.setText(deviceInfos.get(i).getDeviceId()); } return component; } });

}

} }

在DeviceIdsAbility类中为ListContainer组件装载列表项时,在getComponent方法中利用了第2个参数component,该参数就是列表项的根视图。如果component为null,表明并没有可以利用的列表项视图,所以要创建一个新的列表项视图。如果不为null,表明可以利用其他的还没有显示的列表项视图,只需要替换该视图的Text组件中显示的信息即可。

最后在config.json文件中添加一些与分布式相关的权限。

"reqPermissions": [ { "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO" }, { "name": "com.huawei.permission.ACCESS_DISTRIBUTED_ABILITY_GROUP" }, { "name": "ohos.permission.DISTRIBUTED_DATASYNC" } ]

运行程序,会看到如图4所示的设备列表。

图4 获取可用设备的ID

要注意的是,通过DeviceManager.getDeviceList方法只能获取其他设备的信息,不能获取自身的信息,例如,有设备A、设备B和设备C。在设备A中只能获取设备B和设备C的信息,而不能获取设备A的信息。在设备B和设备C中的表现也类似。

3 根据设备ID调用Page Ability

一个Page Ability要想跨设备访问,必须实现IAbilityContinuation接口,否则会抛出异常。该接口必须实现的有4个方法,他们的含义如下:

public interface IAbilityContinuation { // 开始迁移,如果返回true,表示可以开始迁移 boolean onStartContinuation(); // 开始传递数据,如果返回true,表示成功传递数据 boolean onSaveData(IntentParams var1); // 开始恢复数据,如果返回true,表示成功恢复数据 boolean onRestoreData(IntentParams var1); // 已经完成Page Ability迁移 void onCompleteContinuation(int var1); }

假设在设备A上将Page Ability迁移到设备B。onStartContinuation方法和onSaveData方法是在设备A上被调用的,而onRestoreData方法和onCompleteContinuation方法是在设备B上被调用的。为了迁移Page Ability,需要在设备A上执行下面的代码:

continueAbility(deviceId);

其中deviceID是设备ID。当调用该方法后,在设备A上就会依次调用onStartContinuation方法和onSaveData方法,在设备B上会依次调用onRestoreData方法和onCompleteContinuation方法。其中onSaveData方法和onRestoreData方法都有一个IntentParams类型的参数,通过该参数可以在设备A和设备B之间通过Page Ability传递数据(使用方式与Intent类似)。通常在onRestoreData方法中恢复Page Ability从设备A上迁移到设备B上时的数据。

下面给出一个实际的案例,在Page Ability上放置了一个TextField组件,并在该组件中输入了一些文本,然后点击按钮,将该Page Ability迁移到另一部HarmonyOS手机上,并恢复迁移时的数据。

实现代码如下:

public class CrossDevicePageAbility extends Ability implements IAbilityContinuation { private List deviceInfos; private ListContainer listContainerDeviceIds; private TextField textFieldContent; private String content; // 授权方法 private void requestPermission() { // 实现Page Ability跨设备迁移,必须用Java代码申请下面的权限 // 否则不会有任何反应 String[] permission = { "ohos.permission.DISTRIBUTED_DATASYNC"}; List applyPermissions = new ArrayList<>(); for (String element : permission) { // 验证自身是否已经获得了该权限 if (verifySelfPermission(element) != 0) { if (canRequestPermission(element)) { // 如果未获得权限,将该权限添加到权限列表 applyPermissions.add(element); } else { } } else { } } // 申请相应权限 requestPermissionsFromUser(applyPermissions.toArray(new String[0]), 0); } // 要想成功跨设备迁移Page Ability,该方法必须返回true @Override public boolean onStartContinuation() { return true; }
@Override public boolean onSaveData(IntentParams intentParams) { // 保存要传递的数据 intentParams.setParam("content",textFieldContent.getText()); return true; } @Override public boolean onRestoreData(IntentParams intentParams) { // 获取传递过来的数据 content = String.valueOf(intentParams.getParam("content")); return true; } @Override public void onCompleteContinuation(int i) {

} @Override protected void onAbilityResult(int requestCode, int resultCode, Intent resultData) { // 当选择设备后,利用返回的设备ID迁移Page Ability if(resultCode == 100 && requestCode == 99) { // 获取设备ID String deviceId = resultData.getStringParam("deviceId"); Tools.showTip(this, deviceId); // 跨设备迁移Page Ability continueAbility(deviceId); } }

@Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_cross_device_page_ability); // 申请权限 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) { // 恢复TextField组件中的数据 textFieldContent.setText(content); } } }

阅读这段代码,需要了解下面几点:

  • 要想成功迁移Page Ability,并成功传递数据。onStartContinuation方法、onSaveData方法和onRestoreData方法都必须返回true,如果读者使用IDE的自动生成代码功能,默认这几个方法都会返回false,请将他们的返回值改成true;
  • 在HarmonyOS中有一些权限,并不是在config.json中声明就可以了,还需要使用Java代码申请,例如,Page Ability跨设备迁移就需要使用Java代码申请ohos.permission.DISTRIBUTED_DATASYNC权限。如果是第一次申请,会弹出如图5的授权对话框,点击“始终允许”按钮关闭该对话框,第2次申请权限,就不会弹出该对话框了;
  • 由于onRestoreData方法在onStart方法之前调用,所以不能直接在onRestoreData方法中使用组件对象,因为组件对象通常都是在onStart方法中创建的。所以在onRestoreData方法被调用时,这些组件对象还都是空。正确的做法是在onRestoreData方法中将要恢复的数据保存到成员变量中,然后在onStart方法中创建完组件对象后,用这些变量恢复组件中的数据。
  • 本例考虑了多部HarmonyOS设备迁移的问题,所以使用了上一节编写的设备列表窗口。在开始跨设备迁移Page Ability之前,会先弹出一个设备列表窗口,当用户选择一个设备后,会返回该设备的ID,然后在onAbilityResult方法中获取这个返回的设备ID,最后使用continueAbility方法迁移Page Ability;

图5 授权对话框

现在运行程序,关闭授权对话框,并在TextField组件中输入一些内容,最后点击“跨设备迁移Page Ability”按钮,会弹出一个设备列表窗口,选择相应的设备后,会在选中的设备中弹出同样的Page Ability,并且TextField组件的数据与原设备上的完全相同,如图6所示。注意,只要被调用方安装了App,不管设备是否已经启动了App,否会自动弹出这个被迁移的Page Ability。

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

img img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取