HarmonyOS技术社区 · 2021年03月25日

鸿蒙软总线跨设备访问该怎么玩——小总结

目录:
1、跨设备启动FA、跨设备迁移、回迁
2、跨设备连接Service
3、更多文章

重点撸代码:
1、跨设备启动FA、跨设备迁移、回迁
(1)权限

ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE:用于允许监听分布式组网内的设备状态变化。
ohos.permission.GET_DISTRIBUTED_DEVICE_INFO:用于允许获取分布式组网内的设备列表和设备信息。
ohos.permission.GET_BUNDLE_INFO:用于查询其他应用的信息。
ohos.permission.DISTRIBUTED_DATASYNC:用于允许不同设备间的数据交换。

"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"}
]

//主动申明,要多设备协同,让用户选择允许还是禁止
requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0);

(2)界面:ability\_main.xml

<Button
    ohos:id="$+id:main_start_fa_btn"
    ohos:height="match_content"
    ohos:width="300vp"
    ohos:text="1.启动远程设备的FA"
    ohos:text_size="20fp"
    ohos:text_color="#ffffff"
    ohos:background_element="$graphic:button_bg"
    ohos:layout_alignment="horizontal_center"
    ohos:top_padding="8vp"
    ohos:bottom_padding="8vp"
    ohos:left_padding="40vp"
    ohos:right_padding="40vp"
    ohos:top_margin="20vp"
     data-tomark-pass  data-tomark-pass />

<Button
    ohos:id="$+id:main_migration_btn"
    ohos:height="match_content"
    ohos:width="300vp"
    ohos:text="2.迁移到远程设备"
    ohos:text_size="20fp"
    ohos:text_color="#ffffff"
    ohos:background_element="$graphic:button_bg"
    ohos:layout_alignment="horizontal_center"
    ohos:top_padding="8vp"
    ohos:bottom_padding="8vp"
    ohos:left_padding="40vp"
    ohos:right_padding="40vp"
    ohos:top_margin="20vp"
     data-tomark-pass  data-tomark-pass />

button\_bg.xml

<span class="colour" style="color:rgb(0, 0, 255)"><?</span><span class="colour" style="color:rgb(255, 0, 255)">xml version="1.0" encoding="utf-8"</span><span class="colour" style="color:rgb(0, 0, 255)">?></span><span class="colour" style="color:rgb(0, 0, 255)"><</span><span class="colour" style="color:rgb(128, 0, 0)">shape  </span><span class="colour" style="color:rgb(255, 0, 0)">xmlns:ohos</span><span class="colour" style="color:rgb(0, 0, 255)">="[http://schemas.huawei.com/res/ohos](http://schemas.huawei.com/res/ohos)"</span><span class="colour" style="color:rgb(255, 0, 0)">
        ohos:shape</span><span class="colour" style="color:rgb(0, 0, 255)">="rectangle"</span><span class="colour" style="color:rgb(0, 0, 255)">></span>
     <span class="colour" style="color:rgb(0, 0, 255)"><</span><span class="colour" style="color:rgb(128, 0, 0)">solid </span><span class="colour" style="color:rgb(255, 0, 0)">ohos:color</span><span class="colour" style="color:rgb(0, 0, 255)">="#007DFF"</span><span class="colour" style="color:rgb(0, 0, 255)">/></span>
     <span class="colour" style="color:rgb(0, 0, 255)"><</span><span class="colour" style="color:rgb(128, 0, 0)">corners </span><span class="colour" style="color:rgb(255, 0, 0)">ohos:radius</span><span class="colour" style="color:rgb(0, 0, 255)">="40"</span><span class="colour" style="color:rgb(0, 0, 255)">/></span><span class="colour" style="color:rgb(0, 0, 255)"></</span><span class="colour" style="color:rgb(128, 0, 0)">shape</span><span class="colour" style="color:rgb(0, 0, 255)">></span>

另外我们需要的Page Abiltiy:MigrationAbility、RemoveAbility

MainAbilitySlice:

public class MainAbilitySlice extends AbilitySlice {
private Button mainStartFABtn,mainMigrationBtn;

@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
mainStartFABtn = (Button)findComponentById(ResourceTable.Id_main_start_fa_btn);
mainMigrationBtn = (Button)findComponentById(ResourceTable.Id_main_migration_btn);

mainStartFABtn.setClickedListener(mClickListener);
mainMigrationBtn.setClickedListener(mClickListener);
}

private Component.ClickedListener mClickListener = new Component.ClickedListener() {
@Override
public void onClick(Component component) {
int compoentId = component.getId();
switch (compoentId){
case ResourceTable.Id_main_start_fa_btn:
//点击后跨设备打开Fa
//第一种写法
Intent intent = new Intent();
Operation op = new Intent.OperationBuilder()
.withDeviceId(Common.getOnLineDeviceId())
.withBundleName("com.ybzy.demo")
.withAbilityName("com.ybzy.demo.RemoveAbility")
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
intent.setOperation(op);
intent.setParam("msg","我夸设备把你这个FA拉起来了!");
startAbility(intent);

//第二钟写法
intent.setElement(new ElementName(Common.getOnLineDeviceId()
,"com.ybzy.demo","com.ybzy.demo.RemoveAbility"));
intent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
intent.setParam("msg","我夸设备把你这个FA拉起来了!");
startAbility(intent);

break;
case ResourceTable.Id_main_migration_btn:
//点击后进入要迁移的Ability页面
Intent migrationIntent = new Intent();
migrationIntent.setElement(new ElementName("","com.ybzy.demo"
,"com.ybzy.demo.MigrationAbility"));
startAbility(migrationIntent);
break;
default:
break;
}
}
};

@Override
public void onActive() {
super.onActive();
}

@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}

ability\_migration.xml


<Text
    ohos:id="$+id:migration_text"
    ohos:height="match_content"
    ohos:width="250vp"
    ohos:background_element="#0088bb"
    ohos:layout_alignment="horizontal_center"
    ohos:text="下面是一个可编辑的文本框"
    ohos:text_size="50"
    ohos:padding="5vp"
    ohos:top_margin="30vp"
     data-tomark-pass  data-tomark-pass />

<TextField
    ohos:id="$+id:migration_textfield"
    ohos:height="250vp"
    ohos:width="250vp"
    ohos:hint="请输入..."
    ohos:layout_alignment="horizontal_center"
    ohos:background_element="#ffffff"
    ohos:text_color="#888888"
    ohos:text_size="20fp"
    ohos:padding="5vp"
     data-tomark-pass  data-tomark-pass />
<Button
    ohos:id="$+id:migration_migration_btn"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:text="点击迁移"
    ohos:text_size="20fp"
    ohos:text_color="#ffffff"
    ohos:background_element="$graphic:button_bg"
    ohos:top_padding="8vp"
    ohos:bottom_padding="8vp"
    ohos:left_padding="50vp"
    ohos:right_padding="50vp"
    ohos:layout_alignment="horizontal_center"
    ohos:top_margin="30vp"
     data-tomark-pass  data-tomark-pass />

<Button
    ohos:id="$+id:migration_migration_back_btn"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:text="点击迁移回来"
    ohos:text_size="20fp"
    ohos:text_color="#ffffff"
    ohos:background_element="$graphic:button_bg"
    ohos:top_padding="8vp"
    ohos:bottom_padding="8vp"
    ohos:left_padding="50vp"
    ohos:right_padding="50vp"
    ohos:layout_alignment="horizontal_center"
    ohos:top_margin="30vp"
     data-tomark-pass  data-tomark-pass />

RemoveAbility...把接收到的值显示到页面就行,setText()

(3)工具类

public class Common{
public static String getDeviceId(){
List<DeviceInfo> deviceList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
if(deviceList.isEmpty()){
return null;
}
int deviceNum = deviceList.size();
List<String> deviceIds = new ArrayList<>(deviceNum);
List<String> deviceNames = new ArrayList<>(deviceNum);
deviceList.forEach((device)->{
deviceIds.add(device.getDeviceId());
deviceNames.add(device.getDeviceName());
});

//我们这里的实验环境,就两部手机,组件还没讲
//我就直接使用deviceIds的第一个元素,做为启动远程设备的目标id
String devcieIdStr = deviceIds.get(0);
return devcieIdStr;
}


public static void myShowTip(Context context,String msg){
//提示框的核心组件文本
Text text = new Text(context);
text.setWidth(MATCH_CONTENT);
text.setHeight(MATCH_CONTENT);
text.setTextSize(16, Text.TextSizeType.FP);
text.setText(msg);
text.setPadding(30,20,30,20);
text.setMultipleLine(true);
text.setMarginLeft(30);
text.setMarginRight(30);
text.setTextColor(Color.WHITE);
text.setTextAlignment(TextAlignment.CENTER);

//给上面的文本设置一个背景样式
ShapeElement style = new ShapeElement();
style.setShape(ShapeElement.RECTANGLE);
style.setRgbColor(new RgbColor(77,77,77));
style.setCornerRadius(15);
text.setBackground(style);

//构建存放上面的text的布局
DirectionalLayout mainLayout = new DirectionalLayout(context);
mainLayout.setWidth(MATCH_PARENT);
mainLayout.setHeight(MATCH_CONTENT);
mainLayout.setAlignment(LayoutAlignment.CENTER);
mainLayout.addComponent(text);

//最后要让上面的组件绑定dialog
ToastDialog toastDialog = new ToastDialog(context);
toastDialog.setSize(MATCH_PARENT,MATCH_CONTENT);
toastDialog.setDuration(1500);
toastDialog.setAutoClosable(true);
toastDialog.setTransparent(true);
toastDialog.setAlignment(LayoutAlignment.CENTER);
toastDialog.setComponent((Component) mainLayout);
toastDialog.show();
}

}

(4)实现功能
MigrationAbilitySlice:

public class MigrationAbilitySlice extends AbilitySlice implements IAbilityContinuation {
TextField migrationTextField;
Button migrationMigrationBtn,migrationMigrationBackBtn;
String msg = "";

@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_migration);

migrationTextField = (TextField)findComponentById(ResourceTable.Id_migration_textfield);
migrationTextField.setText(msg);
migrationMigrationBtn = (Button)findComponentById(ResourceTable.Id_migration_migration_btn);
migrationMigrationBtn.setClickedListener(component -> {
//1、要进行迁移的Ability实现接口 IAbilityContinuation,实现的方法返回值改成true
//2、要进行迁移的Ability下面关联的所有AbilitySlice都要实现接口 IAbilityContinuation,
//  实现的方法上处理数据
//3、进行迁移
String deviceId = Common.getOnLineDeviceId();
if(deviceId != null){
//                continueAbility(deviceId);
continueAbilityReversibly(deviceId);
}
});

migrationMigrationBackBtn = (Button) findComponentById(ResourceTable
.Id_migration_migration_back_btn);
migrationMigrationBackBtn.setClickedListener(component -> {
reverseContinueAbility();
});
}

@Override
public void onActive() {
super.onActive();
}

@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}

@Override
public boolean onStartContinuation() {
return true;
}

@Override
public boolean onSaveData(IntentParams intentParams) {
intentParams.setParam("msg",migrationTextField.getText());
return true;
}

@Override
public boolean onRestoreData(IntentParams intentParams) {
msg = intentParams.getParam("msg").toString();
//        getUITaskDispatcher().asyncDispatch(() -> {
//            migrationTextField.setText(intentParams.getParam("msg").toString());
//        });
return true;
}

@Override
public void onCompleteContinuation(int i) {

}

}

2、跨设备连接Service

启动远程设备Service的代码示例如下:
添加按钮:

<Button
ohos:id="$+id:main_stop_removeService_btn"
ohos:height="match_content"
ohos:width="300vp"
ohos:text="远程关闭ServiceAbility"
ohos:text_size="20fp"
ohos:text_color="#ffffff"
ohos:background_element="$graphic:button_bg"
ohos:layout_alignment="horizontal_center"
ohos:top_padding="8vp"
ohos:bottom_padding="8vp"
ohos:left_padding="40vp"
ohos:right_padding="40vp"
ohos:top_margin="20vp"
 data-tomark-pass  data-tomark-pass />

新建RemoteServiceAbility:

public class RemoteServiceAbility extends Ability {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
private Source sVideoSource;
private Player sPlayer;

@Override
public void onStart(Intent intent) {
HiLog.error(LABEL_LOG, "RmoteServiceAbility::onStart");
super.onStart(intent);
Common.myShowTip(this,"remote onstart");
sPlayer = new Player(RemoteServiceAbility.this);
new PlayerThread().start();
}
class PlayerThread extends Thread {
@Override
public void run() {
try {
File mp3FilePath = getExternalFilesDir(Environment.DIRECTORY_MUSIC);
if (!mp3FilePath.exists()) {
mp3FilePath.mkdirs();
}
File mp3File = new File(mp3FilePath.getAbsolutePath() + "/" + "bj.mp3");

Resource res = getResourceManager()
.getRawFileEntry("resources/rawfile/bj.mp3").openRawFile();
byte[] buf = new byte[4096];
int count = 0;
FileOutputStream fos = new FileOutputStream(mp3File);
while ((count = res.read(buf)) != -1) {
fos.write(buf, 0, count);
}
FileDescriptor fileDescriptor = new FileInputStream(mp3File).getFD();
sVideoSource = new Source(fileDescriptor);
sPlayer.setSource(sVideoSource);
sPlayer.prepare();
sPlayer.setVolume(0.3f);
sPlayer.enableSingleLooping(true);
sPlayer.play();
} catch (IOException e) {
e.printStackTrace();
}
}
}

@Override
public void onStop() {
super.onStop();
Common.myShowTip(RemoteServiceAbility.this,"remote onStop");
sPlayer.stop();
}

@Override
public IRemoteObject onConnect(Intent intent) {
return null;
}
@Override
public void onDisconnect(Intent intent) {
}
}

启动:

case ResourceTable.Id_main_start_remoteService_btn:

Common.myShowTip(MainAbilitySlice.this,deviceId);
Intent startRemoteServiceIntent = new Intent();
startRemoteServiceIntent.setElement(new ElementName(
deviceId,
"com.ybzy.demo",
"com.ybzy.demo.RemoteServiceAbility"
));
startRemoteServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
startAbility(startRemoteServiceIntent);
break;

关闭远程设备Service:

case ResourceTable.Id_main_stop_remoteService_btn:
Common.myShowTip(MainAbilitySlice.this,deviceId);
Intent stopRemoteServiceIntent = new Intent();
stopRemoteServiceIntent.setElement(new ElementName(
deviceId,
"com.ybzy.demo",
"com.ybzy.demo.RemoteServiceAbility"
));
stopRemoteServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
stopAbility(stopRemoteServiceIntent);
break;

仅通过启动和停止Service Ability两种方式对Service进行调度无法应对需长期交互的场景,
简单地说,信息就是只能去,回不来!
因此,分布式任务调度平台向开发者提供了跨设备Service连接及断开连接的能力。
链接上了,信息可去可回!

链接是使用connectAbility()方法,需要传入目标Service的Intent与接口IAbilityConnection的实例对象。

接口IAbilityConnection提供了两个方法供开发者实现:
(1)onAbilityConnectDone()用来处理连接的回调。
(2)onAbilityDisconnectDone()用来处理断开连接的回调。

我们可以在onAbilityConnectDone()中获取管理链接的代理,进一步为了使用该代理跨设备调度Service,
开发者需要在本地及远端分别实现对外接口一致的代理,这个接口是IRemoteBroker。

添加按钮:

<Button
    ohos:id="$+id:main_connect_remoteService_btn"
    ohos:height="match_content"
    ohos:width="300vp"
    ohos:text="远程链接ServiceAbility"
    ohos:text_size="20fp"
    ohos:text_color="#ffffff"
    ohos:background_element="$graphic:button_bg"
    ohos:layout_alignment="horizontal_center"
    ohos:top_padding="8vp"
    ohos:bottom_padding="8vp"
    ohos:left_padding="40vp"
    ohos:right_padding="40vp"
    ohos:top_margin="20vp"
     data-tomark-pass />

<Button
    ohos:id="$+id:main_use_remoteService_btn"
    ohos:height="match_content"
    ohos:width="300vp"
    ohos:text="使用远程ServiceAbility"
    ohos:text_size="20fp"
    ohos:text_color="#ffffff"
    ohos:background_element="$graphic:button_bg"
    ohos:layout_alignment="horizontal_center"
    ohos:top_padding="8vp"
    ohos:bottom_padding="8vp"
    ohos:left_padding="40vp"
    ohos:right_padding="40vp"
    ohos:top_margin="20vp"
     data-tomark-pass />

<Button
    ohos:id="$+id:main_disconnect_remoteService_btn"
    ohos:height="match_content"
    ohos:width="300vp"
    ohos:text="远程断开ServiceAbility"
    ohos:text_size="20fp"
    ohos:text_color="#ffffff"
    ohos:background_element="$graphic:button_bg"
    ohos:layout_alignment="horizontal_center"
    ohos:top_padding="8vp"
    ohos:bottom_padding="8vp"
    ohos:left_padding="40vp"
    ohos:right_padding="40vp"
    ohos:top_margin="20vp"
     data-tomark-pass />

发起连接的本地侧的代理示例如下:

public class MyRemoteProxy implements IRemoteBroker {
    //IRemoteBroker:获取远程代理对象的持有者
    private static final int ERR_OK = 0;
    //COMMAND_PLUS表示有效消息进行通信的约定的标记,MIN_TRANSACTION_ID是这个标记可以用的最小值:1
    private static final int COMMAND_PLUS = IRemoteObject.MIN_TRANSACTION_ID;

    //IRemoteObject:此接口
    // 可用于查询或获取接口描述符、
    // 添加或删除死亡通知、
    // 将对象状态转储到特定文件以及发送消息。
    private final IRemoteObject remote;
    public MyRemoteProxy(IRemoteObject remote) {
        this.remote = remote;
    }

    @Override
    public IRemoteObject asObject() {//获取远程代理对象的方法
        return remote;
    }

    public int plus(int a,int b) throws RemoteException {
        //MessageParcel:这个类提供了读写对象、接口标记、文件描述符和大数据的方法。
        MessageParcel data = MessageParcel.obtain();
        //obtain()创建索引为0的空MessageParcel对象
        MessageParcel reply = MessageParcel.obtain();

        //MessageOption:定义与sendRequest一起发送消息的选项。
        // option不同的取值,决定采用同步或异步方式跨设备调用Service
        // 这个例子我们需要同步获取对端Service执行加法运算后的结果,同步模式调用sendRequest接口,即MessageOption.TF_SYNC
        // 对应的是异步:TF_ASYNC
        MessageOption option = new MessageOption(MessageOption.TF_SYNC);
        data.writeInt(a);
        data.writeInt(b);

        try {
            remote.sendRequest(COMMAND_PLUS, data, reply, option);
            //第1个参数:约定通信双方确定的消息标记。
            //第2个参数:发送到对等端侧的数据包裹MessageParcel对象。
            //第3个参数:对等端侧返回的数据包裹MessageParcel对象。
            //第4个参数:设置发送消息,用同步还是异步模式。
            int ec = reply.readInt(); //返回通信成不成功,约定的标记ERR_OK
            if (ec != ERR_OK) {
                throw new RemoteException();
            }
            int result = reply.readInt();
            return result;
        } catch (RemoteException e) {
            throw new RemoteException();
        } finally {
            data.reclaim(); //reclaim()清除不再使用的MessageParcel对象。
            reply.reclaim();
        }
    }
}

等待连接的远端侧的代理示例如下:

public class MyRemote extends RemoteObject implements IRemoteBroker{
private static final int ERR_OK = 0;
private static final int ERROR = -1;
private static final int COMMAND_PLUS = IRemoteObject.MIN_TRANSACTION_ID;

public MyRemote() {
super("MyService_Remote");
}

@Override
public IRemoteObject asObject() {
return this;
}

@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
if (code != COMMAND_PLUS) {
reply.writeInt(ERROR);
return false;
}
int value1 = data.readInt();
int value2 = data.readInt();
int sum = value1 + value2;
reply.writeInt(ERR_OK);
reply.writeInt(sum);
return true;
}
}

等待连接侧还需要作如下修改:

// 绑定前面定义的代理,实例化出发起链接侧需要的代理
private MyRemote remote = new MyRemote();
@Override
public IRemoteObject onConnect(Intent intent) {
//链接成功的时候,给发起链接侧返回去
return remote.asObject();
}

完成上述步骤后,可以通过点击事件实现连接、利用连接关系控制PA以及断开连接等行为,代码示例如下:

private MyRemoteProxy mProxy = null;
// 创建连接回调实例
private IAbilityConnection conn = new IAbilityConnection() {
// 连接到Service的回调
@Override
public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
// 在这里开发者可以拿到服务端传过来IRemoteObject对象,从中解析出服务端传过来的信息
mProxy = new MyRemoteProxy(iRemoteObject);
UIUtils.showTip(MainAbilitySlice.this,"拿到remoteObject:" + mProxy);
}

// 意外断开连接才会回调
@Override
public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
}
};

// 连接远程
case ResourceTable.Id_main_connect_remoteService_btn:
//1、实现连接的本地侧的代理
//2、实现等待连接的远端侧的代理
//3、修改等待连接侧的Service
//4、在本地(发起链接侧)获取远端(被链接侧)返回过来的链接代理,创建链接后的回调函数
//5、实现链接,通过代理对象使用Service的服务
if (deviceId != null) {
Intent connectServiceIntent = new Intent();
connectServiceIntent.setElement(new ElementName(
deviceId,
"com.ybzy.demo",
"com.ybzy.demo.RemoteServiceAbility"
));
connectServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
connectAbility(connectServiceIntent, iAbilityConnection);
}
break;

// 链接后使用
case ResourceTable.Id_main_use_remoteService_btn:
if (mProxy != null) {
int ret = -1;
try {
ret = mProxy.plus(10, 20);
} catch (RemoteException e) {
e.printStackTrace();
}
Common.myShowTip(MainAbilitySlice.this, "获取的结果:" + ret);
}
break;

// 用完断开
case ResourceTable.Id_main_disconnect_remoteService_btn:
disconnectAbility(iAbilityConnection);
break;

作者:zhonghongfa
想了解更多内容,请访问51CTO和华为合作共建的鸿蒙社区:https://harmonyos.51cto.com

21_9.jpg

推荐阅读
关注数
2957
内容数
446
华为鸿蒙相关技术,活动及资讯,欢迎关注及加入创作
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息