Harmony OS 分布式操作(跨设备拉起以及Ability迁移) 精华
权限配置以及申请
首先设备要分布式申请权限,和Abilities平级写在config.json中。
权限配置相关文档
在这简述:
{允许应用程序与其他设备交换用户数据(如图像、音乐、视频和应用程序数据)}
{允许设备状态改变}
{允许获取其他设备信息(Id、name等)}
{允许非系统应用程序查询有关其他应用程序的信息。}
"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"}
]
在MainAbility中添加一行申请权限代码
public class MainAbility extends Ability {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
// 申请权限
requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0);
}
}
void requestPermissionsFromUser (String[] permissions, int requestCode)
接口功能:向系统权限管理模块申请权限(接口可支持一次申请多个。若下一步操作涉及到多个敏感权限,可以这么用,其他情况建议不要这么用。因为弹框还是按权限组一个个去弹框,耗时比较长。用到哪个权限就去申请哪个)
输入参数: permissions:权限名列表;requestCode: 请求应答会带回此编码以匹配本次申请的权限请求
输出参数:无
返回值:无
权限申请配置完成。
获取在线设备Id
首先分布式操作肯定要获取其他在线设备信息(Id等)通过Id找到设备交互
这个可以单独另写一个工具类来获取在线设备Id(具体看代码注释)
public class CommonTools {
// 获取软总线上的设备ID
public static String getDeviceId(){
// 将在线设备获取下来存入List(不包括本机)
List<DeviceInfo> deviceList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
/*
获取软总线中本机的设备ID(需要添加参数 context)
KvManagerConfig kvManagerConfig = new KvManagerConfig(context);
kvManagerConfig.getUserInfo().getUserId();
*/
if (deviceList.isEmpty()){
return null;
}
int deviceNum = deviceList.size();
// ArrayList 类是一个可以动态修改的数组,没有固定大小的限制,可以添加或删除元素。
List<String> deviceIds = new ArrayList<>(deviceNum);
List<String> deviceNames = new ArrayList<>(deviceNum);
// forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
deviceList.forEach((device) ->{
deviceIds.add(device.getDeviceId());
deviceNames.add(device.getDeviceName());
});
// 因为只开了两个虚拟设备,直接选用了列表里第0个device(即远端设备)作为要启动的远程设备
String deviceIdStr = deviceIds.get(0);
return deviceIdStr;
}
}
设置按钮监听事件
进行主页布局设置,主页就设置两个按钮分别展示拉起和迁移两个功能。
略过UI布局,接下来是对按钮进行事件监听
对onClick接口进行重新,因为是两个按钮,所以要判断是点击了哪个按钮,用一个switch判断。
private Component.ClickedListener myClickedListener = new Component.ClickedListener() {
@Override
public void onClick(Component component) {
int component_id = component.getId();
switch (component_id){
case ResourceTable.Id_main_start_fa_btn:{
Intent startFaDe = new Intent();
// 此时deviceId已经获取到了,调用上边写好获取在线设备Id的方法
Operation op = new Intent.OperationBuilder()
.withDeviceId(deviceId)
.withBundleName("com.example.myconnect")
.withAbilityName("com.example.myconnect.RemoteAbility")
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
startFaDe.setOperation(op);
startFaDe.setParam("msg","我跨设备拉起你这个FA了");
startAbility(startFaDe);
break;
}
case ResourceTable.Id_main_migration_btn:{
Intent openSubMigrationPage = new Intent();
openSubMigrationPage.setElement(new ElementName(
// 是否跨设备,空字符串表示不跨设备,否则传设备ID
"",
// 包名
"com.example.myconnect",
// 跳转到哪一个Ability
"com.example.myconnect.MigrationAbility"
));
startAbility(openSubMigrationPage);
break;
}
}
}
};
拉起远端设备FeatureAbility
演示过程
先要在两个设备上都运行一下 ,让两个设备都安装上hap包
打开其中一个设备的应用,弹出权限访问dialog,允许访问,然后点击启动远程设备FA
成功拉起另一个设备的RemoteAbility。
在这个地方他是在89设备的MainAbility执行操作,在MainAbilitySlice点击第一个按钮,执行拉起功能,拉起设备88的RemoteAnility,渲染RemoteAbilitySlice(XML没做改动即你好世界)。
页面迁移
点击第二个按钮进入MigrationAbilitySlice(一个TextField和两个Button)
在文本中输入一些文字,点击迁移,效果如下,会在另一台设备拉起该页且会将文本数据通过软总线传输过去,给用户的使用好像就像一台设备,就好像一台设备通过底层总线进行传输数据。
在远端设备进行输入,然后在本地端点击迁回,会关闭远端并且将远端文本传回
迁移页面的后端代码以及注释:
//(implements)要实现 IAbilityContinuation接口才能实现迁移
public class MigrationAbilitySlice extends AbilitySlice implements IAbilityContinuation {
private Button btn;
private Button back_btn;
private TextField textField;
private String msg;
private String deviceId;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_migration);
// 获取在线设备Id
deviceId = CommonTools.getDeviceId();
// 找到布局中的三个组件(一个Text以及两个Button)
textField = findComponentById(ResourceTable.Id_migration_textfield);
btn = findComponentById(ResourceTable.Id_migration_migration_btn);
back_btn = findComponentById(ResourceTable.Id_migration_migration_back_btn);
// 对两个Button进行监听,重写onClick方法
btn.setClickedListener(myClickedlistener);
back_btn.setClickedListener(myClickedlistener);
// 在这个地方对应后边onRestoreData()方法,在这个地方设置Text的文本而不是在onRestoreData方法中设置文本,
// 因为迁移操作属于子线程,要在子线程中改变主线程里的UI组件可能会出问题
textField.setText(msg);
}
Component.ClickedListener myClickedlistener = new Component.ClickedListener() {
@Override
public void onClick(Component component) {
if (deviceId != null){
switch (component.getId()){
case ResourceTable.Id_migration_migration_btn:{
/*
单纯迁移
continueAbility(deviceId);
*/
// 迁移
continueAbilityReversibly(deviceId);
break;
}
case ResourceTable.Id_migration_migration_back_btn:{
// 迁回
reverseContinueAbility();
break;
}
}
}
}
};
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
// 开始迁移时的回调函数,返回true才会执行下边onSaveData回调
@Override
public boolean onStartContinuation() {
return true;
}
// 将发起迁移页面上的数据保存时回调,返回true才会接着执行onRestoreData
@Override
public boolean onSaveData(IntentParams intentParams) {
String m = textField.getText();
intentParams.setParam("msg",m);
return true;
}
// 在远端恢复数据时回调,返回ture才会执行(远端)
@Override
public boolean onRestoreData(IntentParams intentParams) {
msg = intentParams.getParam("msg").toString();
return true;
}
// 迁移完成之后才会执行(本机)
@Override
public void onCompleteContinuation(int i) {
}
}
最后同样还要在MigrationAbility中实现IAbilityContinuation接口才可以支持迁移
public class MigrationAbility extends Ability implements IAbilityContinuation {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MigrationAbilitySlice.class.getName());
}
@Override
public boolean onStartContinuation() {
return true;
}
@Override
public boolean onSaveData(IntentParams intentParams) {
return true;
}
@Override
public boolean onRestoreData(IntentParams intentParams) {
return true;
}
@Override
public void onCompleteContinuation(int i) {
}
}