赞
踩
创建Service比较简单,基本不用写代码,一路点下去就行,如下图所示:
由于Service区分为本地和远程的,所以这里创建两个Service,以备使用,分别为:LocalServiceAbility和RemoteServiceAbility:
public class LocalServiceAbility extends Ability { private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "LocalServiceAbility "); @Override public void onStart(Intent intent) { HiLog.error(LABEL_LOG, "LocalServiceAbility::onStart"); super.onStart(intent); } @Override public void onBackground() { super.onBackground(); HiLog.info(LABEL_LOG, "LocalServiceAbility::onBackground"); } @Override public void onStop() { super.onStop(); HiLog.info(LABEL_LOG, "LocalServiceAbility::onStop"); } @Override public void onCommand(Intent intent, boolean restart, int startId) { } @Override public IRemoteObject onConnect(Intent intent) { return null; } @Override public void onDisconnect(Intent intent) { } }
当点击创建Serivice的Finish按钮后,上述代码就会自动生成,开发者可根据自己的习惯配置一下日志输出格式。RemoteServiceAbility和LocalServiceAbility除了类名不一样外,其他都一样。由于Service是Ability的一种,所以Service都是继承Ability的,使用是比较方便,但是Service不同于Page,放在一起使用容易混乱,所以使用Service的时候建议封装个基类比较好容易区分,便于管理。当Service创建完后,DevEco Studio 自动在config.json中注册,无需手动注册。
Intent intent1 = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.harmonyos.service")
.withAbilityName("com.harmonyos.service.LocalServiceAbility")
.build();
intent1.setOperation(operation);
startAbility(intent1);
Intent intent1 = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.harmonyos.service")
.withAbilityName("com.harmonyos.service.LocalServiceAbility")
.build();
intent1.setOperation(operation);
stopAbility(intent1);
Intent intent1 = new Intent();
present(new SecondAbilitySlice(), intent1);
简单设置了三个按钮:开启服务、停止服务、下一页。仔细观察一下上述代码,可以发现服务的开启与停止和Ability的使用是一样的,如果有什么不清楚的可以看看HarmonyOS-page之间的跳转。
使用的LocalServiceAbility的代码和文章开头创建的一样,在生命周期中的每个方法进行日志输出,在onCommand()中将所有的参数进行了输出与弹出:
@Override
public void onCommand(Intent intent, boolean restart, int startId) {
System.out.println("LocalServiceAbility::onCommand restart:" + restart + ",startId:" + startId);
ToastDialog toastDialog = new ToastDialog(getContext());
toastDialog.setText("::onCommand restart:" + restart + ",startId:" + startId);
toastDialog.show();
}
不管是在手机上还是电视上,效果都一样,光看页面看不出什么,主要是分析日志。
不管执行到哪一步,直接输出Service的实例,发现Service实例对应的地址是一样的。
上述方法的执行顺序,侧面应证了Service的一些特点:
上文中一个简单的服务从开启到停止一个完整的流程已经走完,但是onConnect()和onDisconnect()没有被召唤,这两个方法肯定不会是多余的,onConnect()和onDisconnect()是Service使用的另一种方式。之所以配置两种使用方式,是适用于不同的场景。
开启和停止服务属于最基本操作,该使用方式属于开关式,只管开始和结束,无法控制过程,即如果使用该方式开启了音乐播放,只能开启播放音乐,但是是否开启成功,播放哪一首,到什么时间了,是否被人为关掉了,该方式均无法把控。所以开启和停止服务属于单项信号式,而连接与断开是双向通信的方式,不仅可以开关服务,还可以获取服务状态。
private IAbilityConnection connection = new IAbilityConnection() {
@Override
public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
}
@Override
public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
}
};
onAbilityConnectDone()是用来处理连接Service成功的回调。elementName为连接设备的相关信息,启动Ability中有使用intent.setElementName(String deviceId, String bundleName, String abilityName)来启用,不清楚的可以看看HarmonyOS-page之间的跳转末尾的评论。IRemoteObject 相当于Service连接通道中的数据包,resultCode是返回结果码,一般0代表连接成功,否则连接失败。
onAbilityDisconnectDone()参数和onAbilityConnectDone方法中一样。注意上图中的crashes,unexpectedly,说明onAbilityDisconnectDone是用来处理Service异常死亡的回调,所以在正常的断开连接时是看不到此方法的调用。
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId("deviceId")
.withBundleName("com.harmonyos.service")
.withAbilityName("com.harmonyos.service.LocalServiceAbility")
.build();
intent.setOperation(operation);
connectAbility(intent, connection);
连接时需要调用connectAbility方法,而且需要将创建好的Service实例connection带进入即可。
disconnectAbility(connection)
断开调用disconnectAbility,把Service实例connection传进入即可。
创建返回数据就是Service侧也需要在onConnect()时返回IRemoteObject,从而定义与Service进行通信的接口。系统默认提供IRemoteObject的实现LocalRemoteObject ,也可以直接继承RemoteObject
// private class CurrentRemoteObject extends ohos.aafwk.ability.LocalRemoteObject { // // public CurrentRemoteObject() { // } // } private class CurrentRemoteObject extends RemoteObject { private CurrentRemoteObject() { super("CurrentRemoteObject"); } @Override public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) { return true; } }
LocalRemoteObject 也是继承了RemoteObject ,所以两种方式其实是一样的。创建好了之后在LocalServiceAbility中将其返回
@Override
public IRemoteObject onConnect(Intent intent) {
System.out.println("LocalServiceAbility::onConnect");
return new CurrentRemoteObject();
}
页面还是上文中的页面,但是日志变化不小。
远程服务的开启与停止,连接与断开与本地服务的操作基本一致,但是deviceId要手动获取,并添加flag:
...
Operation operation = new Intent.OperationBuilder()
.withDeviceId(deviceId)
.withBundleName("com.harmonyos.service")
.withAbilityName("com.harmonyos.service.RemoteServiceAbility")
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
...
deviceId怎么获取,可使用如下方法获取:
private String getRemoteDeviceId() {
List<DeviceInfo> infoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ALL_DEVICE);
if ((infoList == null) || (infoList.size() == 0)) {
return "";
}
int random = new SecureRandom().nextInt(infoList.size());
return infoList.get(random).getDeviceId();
}
withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)就是添加个标签,远程服务就是跨设备、多设备,所以添加个FLAG_ABILITYSLICE_MULTI_DEVICE理所应当的,也可以在intent.setFlags中添加,但是该方法已被废弃,过时废弃的方法能不用就不用。
远程服务和本地服务不同之处在于远程服务中你不知道谁是服务端,谁是客户端,所以需要在代码中两端的操作都要做好。
这里简单模拟的是客户端传递一个int类型的数据给远程服务端,然后服务端将数据*1024再返回。首先创建客户端代理类并实现IRemoteBroker,IRemoteBroker也就是远程代理,就像两个经纪人之间的交流,并非本主。
public class ClientRemoteProxy implements IRemoteBroker { private static final int RESULT_SUCCESS = 0; private static final int RESULT_TODO = 1; private final IRemoteObject remoteObject; public ClientRemoteProxy(IRemoteObject remoteObject) { this.remoteObject = remoteObject; } public int todoServiceJob(int command) { MessageParcel message = MessageParcel.obtain(); message.writeInt(command); MessageParcel reply = MessageParcel.obtain(); MessageOption option = new MessageOption(MessageOption.TF_SYNC); int result = 0; try { remoteObject.sendRequest(RESULT_TODO, message, reply, option); int resultCode = reply.readInt(); if (resultCode != RESULT_SUCCESS) { throw new RemoteException(); } result = reply.readInt(); } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public IRemoteObject asObject() { return remoteObject; } }
最重要的是todoServiceJob方法,这里是消息发送最关键的地方,remoteObject.sendRequest()就是向远程发布消息,告诉远程设备我要干嘛。
boolean sendRequest(int var1, MessageParcel var2, MessageParcel var3, MessageOption var4) throws RemoteException;
第一个参数相当于请求码,两端约定好,客户端发送这个码,远程设备接口后识别这个码就知道要干嘛了。MessageParcel 使用起来像队列,实际功能却和Map很像,通过MessageParcel.obtain()获取,然后writeInt和readInt写入和写出数据,数据类型包含基本类型和自己创建的对象。
MessageParcel读取或写入数据时,写一次指针往后移动一位,写一个数据就占一格,再写就往后再占一格,依次按顺序往下,使用的时候按顺序来就行。第二个参数MessageParcel var2就是客户端向远程设备传递的设备,第三个参数MessageParcel var3是远程设备向客户端回复的消息,发送的时候就已经把需要接收的消息的位置给留好了,而不是远程设备接收数据后将数据清空然后再将结果写入。两个篮子,一个装请求,一个装结果,互不干扰。第四个参数MessageOption var4是本次通信是同步的还是异步的,同步的如下:
MessageOption option = new MessageOption(MessageOption.TF_SYNC);
异步的就是将MessageOption.TF_SYNC换成MessageOption.TF_ASYNC。返回的数据中一般第一位为状态码,resultCode = reply.readInt(),如果resultCode等于成功的状态码就取第二位,第二位才是真正的结果,然后将其传给需要的地方。数据存放的格式不固定,两端约定好就行,不一定要把状态码放在第一位,可以不传,可以放在前三位,均可。
ClientRemoteProxy 的使用如下:
public class MainAbilitySlice extends AbilitySlice { private ClientRemoteProxy clientRemoteProxy = null; private IAbilityConnection connection = new IAbilityConnection() { @Override public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int i) { clientRemoteProxy = new ClientRemoteProxy(iRemoteObject); System.out.println("onAbilityConnectDone"); } @Override public void onAbilityDisconnectDone(ElementName elementName, int i) { System.out.println("onAbilityDisconnectDone"); } }; @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_main); findComponentById(ResourceTable.Id_btn_next_page).setClickedListener(component -> { if(clientRemoteProxy != null){ int result = clientRemoteProxy.todoServiceJob(512); System.out.println("result:" + result); } }); ... }
当服务连接成功后调用onAbilityConnectDone方法并返回IRemoteObject ,此时ClientRemoteProxy拿到IRemoteObject创建实例,然后在相应的位置发送消息clientRemoteProxy.todoServiceJob(512),就是将512发送给远程设备,等待远程设备返回结果。
远程设备就是将传递过来的数据处理后再传回去。
public class ServiceRemoteProxy extends RemoteObject implements IRemoteBroker { private static final int RESULT_SUCCESS = 0; private static final int RESULT_FAILED = -1; private static final int RESULT_TODO = 1; public ServiceRemoteProxy(String descriptor) { super(descriptor); } @Override public IRemoteObject asObject() { return this; } @Override public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) throws RemoteException { if(code != RESULT_TODO){ reply.writeInt(RESULT_FAILED); return false; } int initData = data.readInt(); int resultData = initData * 1024; reply.writeInt(RESULT_SUCCESS); reply.writeInt(resultData); return true; } }
最重要的是onRemoteRequest,里面的参数解释和ClientRemoteProxy 中的sendRequest一样,若code不等于约定值,直接返回错误码拒绝服务。代码中将传进来的数据*1024后传回去,至此远程设备的服务工作已结束但是远程设备还没有被使用,使用如下:
public class RemoteServiceAbility extends Ability {
...
private ServiceRemoteProxy serviceRemoteProxy = new ServiceRemoteProxy("");
...
@Override
public IRemoteObject onConnect(Intent intent) {
return serviceRemoteProxy;
}
...
}
在连接远程服务调用onConnect,直接将远程服务的代理类ServiceRemoteProxy的实例返回即可。至此所有的远程服务工作结束。
顺序不一定是上面的顺序,只要该有的都有了,调用远程服务就没有问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。