【HarmonyOS鸿蒙开发Service专题NO.1】ServiceAbility概念与基本使用

内容纲要

作者:韩茹

公司:程序咖(北京)科技有限公司

鸿蒙巴士专栏作家

基于Service模板的Ability(以下简称“Service”)主要用于后台运行任务(如执行音乐播放、文件下载等),但不提供用户交互界面。Service可由其他应用或Ability启动,即使用户切换到其他应用,Service仍将在后台继续运行。

Service是单实例的。在一个设备上,相同的Service只会存在一个实例。如果多个Ability共用这个实例,只有当与Service绑定的所有Ability都退出后,Service才能够退出。由于Service是在主线程里执行的,因此,如果在Service里面的操作时间过长,开发者必须在Service里创建新的线程来处理,防止造成主线程阻塞,应用程序无响应。

WX20210708-103028@2x

根据调用方法的不同,两种Service生命周期都可以细分为普通服务和连接服务(HarmonyOS中称为连接服务,Android中则为绑定服务)。

  • 普通服务:一般后台服务,比如地图定位、文件下载等。
  • 连接服务:Service在其他Ability调用connectAbility()时创建,客户端可通过调用disconnectAbility()断开连接。多个客户端可以绑定到相同Service,而且当所有绑定全部取消后,系统即会销毁该Service。

根据使用场景不同,HarmonyOS Service和Android Service都分为后台服务,前台服务,绑定(连接)服务。

  • 后台服务:后台服务执行用户不会直接注意到的操作。一般情况下,Service都是在后台运行的,后台Service的优先级都是比较低的,当资源不足时,系统有可能回收正在运行的后台Service。
  • 前台服务:前台服务执行一些用户能注意到的操作。在一些场景下(如播放音乐),用户希望应用能够一直保持运行,此时就需要使用前台Service。前台Service会始终保持正在运行的图标在系统状态栏显示。
  • 连接服务:绑定服务是客户端-服务器接口中的服务器。借助绑定服务,组件(例如 Activity)可以绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。

不管是哪一种服务,服务既可以单独存在,也可以共存,即可以同时有前台服务和绑定服务,或者其他组合方式存在。

创建Service

介绍如何创建一个Service。

  1. 创建Ability的子类,实现Service相关的生命周期方法。Service也是一种Ability,Ability为Service提供了以下生命周期方法,用户可以重写这些方法,来添加其他Ability请求与Service Ability交互时的处理方法。

    • onStart()

      该方法在创建Service的时候调用,用于Service的初始化。在Service的整个生命周期只会调用一次,调用时传入的Intent应为空。

    • onCommand()

      在Service创建完成之后调用,该方法在客户端每次启动该Service时都会调用,用户可以在该方法中做一些调用统计、初始化类的操作。

    • onConnect()

      在Ability和Service连接时调用,该方法返回IRemoteObject对象,用户可以在该回调函数中生成对应Service的IPC通信通道,以便Ability与Service交互。Ability可以多次连接同一个Service,系统会缓存该Service的IPC通信对象,只有第一个客户端连接Service时,系统才会调用Service的onConnect方法来生成IRemoteObject对象,而后系统会将同一个RemoteObject对象传递至其他连接同一个Service的所有客户端,而无需再次调用onConnect方法。

    • onDisconnect()

      在Ability与绑定的Service断开连接时调用。

    • onStop()

      在Service销毁时调用。Service应通过实现此方法来清理任何资源,如关闭线程、注册的侦听器等。

我们先新建一个LocalService,

WX20210708-111751@2x

创建Service的代码示例如下:

package com.example.hanruserviceability;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.rpc.IRemoteObject;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

/**
 * 创建一个本地 Service
 */
public class LocalServiceAbility extends Ability {
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0x00201, "LocalServiceAbility");

    @Override
    public void onStart(Intent intent) {
        HiLog.info(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) {
        HiLog.info(LABEL_LOG, "onCommand--->startId:"+startId);
    }

    @Override
    public IRemoteObject onConnect(Intent intent) {
        HiLog.info(LABEL_LOG, "onConnect--->");
        return null;
    }

    @Override
    public void onDisconnect(Intent intent) {
    }
}
  1. 注册Service。

Service也需要在应用配置文件中进行注册,注册类型type需要设置为service。

{
    "module": {
        "abilities": [         
            {
              "name": "com.example.hanruserviceability.LocalServiceAbility",
              "icon": "$media:icon",
              "description": "$string:localserviceability_description",
              "type": "service"
            }
        ]
        ...
    }
    ...
}

启动本地Service

通过startAbility()启动Service以及对应的停止方法。

  • 启动Service

    Ability为开发者提供了startAbility()方法来启动另外一个Ability。因为Service也是Ability的一种,开发者同样可以通过将Intent传递给该方法来启动Service。不仅支持启动本地Service,还支持启动远程Service。

    开发者可以通过构造包含DeviceId、BundleName与AbilityName的Operation对象来设置目标Service信息。这三个参数的含义如下:

    • DeviceId:表示设备ID。如果是本地设备,则可以直接留空;如果是远程设备,可以通过ohos.distributedschedule.interwork.DeviceManager提供的getDeviceList获取设备列表,详见《API参考》。

    • BundleName:表示包名称。

    • AbilityName:表示待启动的Ability名称。

我们现在ability_main布局文件中:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:padding="5vp"
    ohos:orientation="vertical">

    <Button
        ohos:id="$+id:start_local_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:padding="5vp"
        ohos:text="启动本地Service"
        ohos:background_element="$graphic:button_bg"
        ohos:top_margin="50vp"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>

</DirectionalLayout>

在graphic下,button_bg.xml中:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:ohos="http://schemas.huawei.com/res/ohos"
       ohos:shape="rectangle">
    <corners
        ohos:radius="75"/>
    <solid
        ohos:color="#0d000000"/>
</shape>

在MainAbilitySlice中添加一个initComponents()方法,用于初始化组件,并在onStart()中进行调用:

        // 初始化组件
    private void initComponents() {
        Component startLocalButton = findComponentById(ResourceTable.Id_start_local_button);
    }

然后我们定义一下BundleName和AbilityName:

        // 本地service
    private static final String LOCAL_BUNDLE = "com.example.hanruserviceability";
    private static final String NORMAL_SERVICE = "LocalServiceAbility";

再加个HiLogLabel,方便一会儿观察日志:

        private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0x00201, "MainAbilitySlice");

因为启动Ability需要Intent,我们先添加一个方法,用于获取Intent

        // 获取启动本地服务的Intent
    private Intent getLocalServiceIntent(String bundleName, String serviceName) {
        Operation operation = new Intent.OperationBuilder().withDeviceId("")
                .withBundleName(bundleName)
                .withAbilityName(serviceName)
                .build();
        Intent intent = new Intent();
        intent.setOperation(operation);
        return intent;
    }

然后再添加一个方法,用于启动本地Service:

        // 启动本地服务
    private void startLocalService(String bundleName, String serviceName) {
        HiLog.info(LABEL_LOG, "startLocalService------ ");
        Intent localServiceIntent = getLocalServiceIntent(bundleName, serviceName);
        startAbility(localServiceIntent);
    }

然后我们给按钮添加点击事件:

        // 初始化组件
    private void initComponents() {
        // 启动本地service
        Component startLocalButton = findComponentById(ResourceTable.Id_start_local_button);
        startLocalButton.setClickedListener(component -> startLocalService(LOCAL_BUNDLE, NORMAL_SERVICE));
    }
  • 执行上述代码后,Ability将通过startAbility() 方法来启动Service。

    • 如果Service尚未运行,则系统会先调用onStart()来初始化Service,再回调Service的onCommand()方法来启动Service。
    • 如果Service正在运行,则系统会直接回调Service的onCommand()方法来启动Service。

然后就可以跑一下了:

WX20210708-114559@2x

我连续点击了2次启动本地Service的按钮,我们发现onStart()方法,确实只调用了一次,也说明了service确实是单实例的。

停止本地Service

Service一旦创建就会一直保持在后台运行,除非必须回收内存资源,否则系统不会停止或销毁Service。开发者可以在Service中通过terminateAbility()停止本Service或在其他Ability调用stopAbility()来停止Service。

停止Service同样支持停止本地设备Service和停止远程设备Service,使用方法与启动Service一样。一旦调用停止Service的方法,系统便会尽快销毁Service。

我们在ability_main.xml中再添加一个按钮,用于停止本地Service:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:padding="5vp"
    ohos:orientation="vertical">

        ...
    <Button
        ohos:id="$+id:stop_local_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:padding="5vp"
        ohos:text="停止本地Service"
        ohos:background_element="$graphic:button_bg"
        ohos:top_margin="10vp"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>

</DirectionalLayout>

然后添加一个方法:

        // 停止本地服务
    private void stopLocalService() {
        HiLog.info(LABEL_LOG, "stopService------ ");
        Intent intent = getLocalServiceIntent(LOCAL_BUNDLE, NORMAL_SERVICE);
        stopAbility(intent);
    }

我们给按钮添加点击事件:

                // 停止本地service              
                Component stopLocalButton = findComponentById(ResourceTable.Id_stop_local_button);
        stopLocalButton.setClickedListener(component -> stopLocalService());

然后运行,先点击启动服务,再点击停止服务:

WX20210708-133630@2x

连接本地Service和断开连接

上面一个简单的服务从开启到停止一个完整的流程已经走完,但是onConnect()和onDisconnect()没有被召唤,这两个方法肯定不会是多余的,onConnect()和onDisconnect()是Service使用的另一种方式。之所以配置两种使用方式,是适用于不同的场景。 开启和停止服务属于最基本操作,该使用方式属于开关式,只管开始和结束,无法控制过程,即如果使用该方式开启了音乐播放,只能开启播放音乐,但是是否开启成功,播放哪一首,到什么时间了,是否被人为关掉了,该方式均无法把控。所以开启和停止服务属于单项信号式,而连接与断开是双向通信的方式,不仅可以开关服务,还可以获取服务状态。

如果Service需要与Page Ability或其他应用的Service Ability进行交互,则须创建用于连接的Connection。Service支持其他Ability通过connectAbility()方法与其进行连接。

在使用connectAbility()处理回调时,需要传入目标Service的Intent与IAbilityConnection的实例。IAbilityConnection提供了两个方法供开发者实现:onAbilityConnectDone()是用来处理连接Service成功的回调,onAbilityDisconnectDone()是用来处理Service异常死亡的回调。

创建连接Service回调实例的代码示例如下:

// 创建连接Service回调实例
private IAbilityConnection connection = new IAbilityConnection() {
    // 连接到Service的回调
    @Override
    public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
        // Client侧需要定义与Service侧相同的IRemoteObject实现类。开发者获取服务端传过来IRemoteObject对象,并从中解析出服务端传过来的信息。
    }

    // Service异常死亡的回调
    @Override
    public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
    }
};

onAbilityConnectDone()是用来处理连接Service成功的回调。

  • elementName为连接设备的相关信息,启动Ability中有使用intent.setElementName(String deviceId, String bundleName, String abilityName)来启用。
  • IRemoteObject 相当于Service连接通道中的数据包。
  • resultCode是返回结果码,一般0代表连接成功,否则连接失败。

onAbilityDisconnectDone()参数和onAbilityConnectDone方法中一样。onAbilityDisconnectDone是用来处理Service异常死亡的回调,所以在正常的断开连接时是看不到此方法的调用。

同时,Service侧也需要在onConnect()时返回IRemoteObject,从而定义与Service进行通信的接口。onConnect()需要返回一个IRemoteObject对象,HarmonyOS提供了IRemoteObject的默认实现,用户可以通过继承LocalRemoteObject来创建自定义的实现类。Service侧把自身的实例返回给调用侧的代码示例如下:

// 创建自定义IRemoteObject实现类
private class MyRemoteObject extends LocalRemoteObject {
    MyRemoteObject(){
    }
}

// 把IRemoteObject返回给客户端
@Override
protected IRemoteObject onConnect(Intent intent) {
    return new MyRemoteObject();
}

我们在ability_main.xml中再添加两个按钮,用于连接和断开连接本地Service:

    <Button
        ohos:id="$+id:connect_local_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:top_margin="10vp"
        ohos:padding="5vp"
        ohos:text="连接本地Service"
        ohos:background_element="$graphic:button_bg"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>

    <Button
        ohos:id="$+id:disconnect_local_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:top_margin="10vp"
        ohos:padding="5vp"
        ohos:text="断开连接本地Service"
        ohos:background_element="$graphic:button_bg"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>

在LocalServiceAbility中,添加一个RemoteObject子类,并实例化:

        private MyRemoteObject myRemoteObject = new MyRemoteObject();    

        @Override
    public IRemoteObject onConnect(Intent intent) {
        HiLog.info(LABEL_LOG, "onConnect--->"+myRemoteObject.hashCode());
        return myRemoteObject;
    }
    // 创建自定义IRemoteObject实现类
    public class MyRemoteObject extends LocalRemoteObject {
        MyRemoteObject(){
            HiLog.info(LABEL_LOG, "MyRemoteObject()------");
        }

    }

然后在MainAbilitySlice中添加一个方法,用于连接本地service:

        // 连接本地服务
    private void connectLocalService() {
        HiLog.info(LABEL_LOG, "connectService------ ");
        Intent intent = getLocalServiceIntent(LOCAL_BUNDLE, NORMAL_SERVICE);
        connectAbility(intent, connection);
    }

这里需要一个connection:

        // 连接对象
    // 创建连接Service回调实例
    private IAbilityConnection connection = new IAbilityConnection() {
        // 连接到Service的回调
        @Override
        public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
            // Client侧需要定义与Service侧相同的IRemoteObject实现类。开发者获取服务端传过来IRemoteObject对象,并从中解析出服务端传过来的信息。

            HiLog.info(LABEL_LOG, "onAbilityConnectDone------ resultCode:"+resultCode+","+iRemoteObject.hashCode());

        }

        // Service异常死亡的回调
        @Override
        public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
            HiLog.info(LABEL_LOG, "onAbilityDisconnectDone------ resultCode:"+resultCode);
        }
    };

点击按钮:连接本地Service和断开连接Service

                Component connectLocalButton = findComponentById(ResourceTable.Id_connect_local_button);
        Component disconnectLocalButton = findComponentById(ResourceTable.Id_disconnect_local_button);
        connectLocalButton.setClickedListener(component -> connectLocalService());
        disconnectLocalButton.setClickedListener(component -> disconnectAbility(connection));

断开连接很简单,直接调用disconnectAbility()方法即可。

运行结果:

WX20210708-151606@2x

因为我们打印了hashCode,在service中onConnect()方法返回的IRemoteObject,就是在连接成功后的回掉方法onAbilityConnectDone()中第二个参数IRemoteObject对象。它们的哈希值是一样的。

那我们传个消息试试,在service中:

    // 创建自定义IRemoteObject实现类
    public class MyRemoteObject extends LocalRemoteObject {
        MyRemoteObject(){
            HiLog.info(LABEL_LOG, "MyRemoteObject()------");
        }
        public String callHello(String name){
            return sayHello(name);
        }
    }
    String sayHello(String name){
        return "Hello "+name;
    }

在MainAbilitySlice中:

        LocalServiceAbility .MyRemoteObject myRemoteObject;
    // 连接对象
    // 创建连接Service回调实例
    private IAbilityConnection connection = new IAbilityConnection() {
        // 连接到Service的回调
        @Override
        public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
            // Client侧需要定义与Service侧相同的IRemoteObject实现类。开发者获取服务端传过来IRemoteObject对象,并从中解析出服务端传过来的信息。

            HiLog.info(LABEL_LOG, "onAbilityConnectDone------ resultCode:"+resultCode+","+iRemoteObject.hashCode());
            myRemoteObject = (LocalServiceAbility.MyRemoteObject) iRemoteObject;
            if(myRemoteObject != null){
                String valueFromService = myRemoteObject.callHello("hanru");
                HiLog.info(LABEL_LOG, "valueFromService-->"+valueFromService);
            }else{
                HiLog.info(LABEL_LOG, "myRemoteObject is null!!");
            }

        }

然后运行:

WX20210708-152544@2x

可以接受到service传来的数据。

远程Service的启动和停止

下面我们来启动和停止一下远程的Service,首先创建一个ServiceAbility:RemoteServiceAbility.java

package com.example.hanruserviceability;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.rpc.IRemoteObject;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

public class RemoteServiceAbility extends Ability {
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0x00201, "RemoteServiceAbility");

    @Override
    public void onStart(Intent intent) {
        HiLog.info(LABEL_LOG, "RemoteServiceAbility::onStart");
        super.onStart(intent);
    }

    @Override
    public void onBackground() {
        super.onBackground();
        HiLog.info(LABEL_LOG, "RemoteServiceAbility::onBackground");
    }

    @Override
    public void onStop() {
        super.onStop();
        HiLog.info(LABEL_LOG, "RemoteServiceAbility::onStop");
    }

    @Override
    public void onCommand(Intent intent, boolean restart, int startId) {
        HiLog.info(LABEL_LOG, "onCommand-----startId:"+startId);
    }

    @Override
    public IRemoteObject onConnect(Intent intent) {
        HiLog.info(LABEL_LOG, "onConnect--------");
        return null;
    }

    @Override
    public void onDisconnect(Intent intent) {
        HiLog.info(LABEL_LOG, "onDisconnect--------");
    }
}

在ability_main.xml中,再添加两个按钮:

        <Button
        ohos:id="$+id:start_remote_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:padding="5vp"
        ohos:text="启动远程Service"
        ohos:background_element="$graphic:button_bg"
        ohos:top_margin="10vp"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>
    <Button
        ohos:id="$+id:stop_remote_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:padding="5vp"
        ohos:text="停止远程Service"
        ohos:background_element="$graphic:button_bg"
        ohos:top_margin="10vp"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>

在MainAbilitySlice.java中,先设置一下要连接的远程Service的bundleName和AbilityName:

        private static final String REMOTE_BUNDLE = "com.example.hanruserviceability";

    private static final String REMOTE_SERVICE = "RemoteServiceAbility";

远程服务的开启与停止,连接与断开与本地服务的操作基本一致,但是deviceId要手动获取:

        // 获取远程设备的ID
    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();
    }

再添加一个方法,用于获取启动远程Service的Intent:

        // 获取远程服务的Intent
    private Intent getRemoteServiceIntent(String bundleName, String serviceName) {
        HiLog.info(LABEL_LOG, "getRemoteServiceIntent------ ");
        Operation operation = new Intent.OperationBuilder().withDeviceId(getRemoteDeviceId())
                .withBundleName(bundleName)
                .withAbilityName(serviceName)
                .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE) // 设置支持分布式调度系统多设备启动的标识
                .build();
        Intent intent = new Intent();
        intent.setOperation(operation);
        return intent;
    }

这里要添加Flag:withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)就是添加个标签,远程服务就是跨设备、多设备,所以添加个FLAG_ABILITYSLICE_MULTI_DEVICE理所应当的。远程服务和本地服务不同之处在于远程服务中你不知道谁是服务端,谁是客户端,所以需要在代码中两端的操作都要做好。

再写个方法,用于启动远程Service:

        // 启动远程服务
    private void startRemoteService(String bundleName, String serviceName) {
        HiLog.info(LABEL_LOG, "startRemoteService------ ");
        Intent remoteServiceIntent = getRemoteServiceIntent(bundleName, serviceName);
        startAbility(remoteServiceIntent);
    }

再添加个停止远程Service的方法:

        // 停止本地服务
    private void stopRemoteService() {
        HiLog.info(LABEL_LOG, "stopRemoteService------ ");
        Intent intent = getRemoteServiceIntent(REMOTE_BUNDLE, REMOTE_SERVICE);
        stopAbility(intent);
    }

最后我们给按钮添加点击事件,分别用于启动和停止远程Service:

                Component startRemoteButton = findComponentById(ResourceTable.Id_start_remote_button);
        Component stopRemoteButton = findComponentById(ResourceTable.Id_stop_remote_button);
        startRemoteButton.setClickedListener(component -> startRemoteService(REMOTE_BUNDLE, REMOTE_SERVICE));
        stopRemoteButton.setClickedListener(component -> stopRemoteService());

远程Service的连接和断开

下面我们连接一下远程Service,并传递过去数据,远程Service再传递数据回来。

现在ability_main.xml中,再添加两个按钮,用于连接和断开远程Service:

        <Button
        ohos:id="$+id:connect_remote_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:top_margin="10vp"
        ohos:padding="5vp"
        ohos:text="连接远程Service"
        ohos:background_element="$graphic:button_bg"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>

    <Button
        ohos:id="$+id:disconnect_remote_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:top_margin="10vp"
        ohos:padding="5vp"
        ohos:text="断开连接远程Service"
        ohos:background_element="$graphic:button_bg"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>

在MainAbilitySlice中添加方法:

        // 连接远程服务
    private void connectRemoteService() {
        HiLog.info(LABEL_LOG, "connectRemoteService------ ");
        Intent intent = getRemoteServiceIntent(REMOTE_BUNDLE, REMOTE_SERVICE);
        connectAbility(intent, connection2);
    }

这里需要IAbilityConnection,连接对象:

        // 用于连接远程Service
    private IAbilityConnection connection2 = new IAbilityConnection() {

        @Override
        public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int i) {
            HiLog.info(LABEL_LOG, "onAbilityConnectDone------ ");

            ClientRemoteProxy clientRemoteProxy = new ClientRemoteProxy(iRemoteObject);
            clientRemoteProxy.todoServiceJob();

        }

        @Override
        public void onAbilityDisconnectDone(ElementName elementName, int i) {

        }
    };

首先创建客户端代理类并实现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 void todoServiceJob() {
            // 用于传到远程Service的数据
            MessageParcel message = MessageParcel.obtain();
            message.writeInt(100);
            message.writeString("床前明月光");

            // 用于接收远程Service传回的数据
            MessageParcel reply = MessageParcel.obtain();

            // 同步还是异步
            MessageOption option = new MessageOption(MessageOption.TF_SYNC);
            try {
                // remoteObject.sendRequest()就是向远程发布消息,告诉远程设备我要干嘛。
                /**
                 * 第一个参数相当于请求码,两端约定好,客户端发送这个码,远程设备接口后识别这个码就知道要干嘛了。
                 *
                 * MessageParcel 使用起来像队列,实际功能却和Map很像,通过MessageParcel.obtain()获取,然后writeInt和readInt写入和写出数据,数据类型包含基本类型和自己创建的对象。
                 *
                 * message,是客户端向远程设备传递的设备
                 *
                 * reply,远程设备向客户端回复的消息,发送的时候就已经把需要接收的消息的位置给留好了,而不是远程设备接收数据后将数据清空然后再将结果写入。
                 *
                 * 第四个参数MessageOption var4是本次通信是同步的还是异步的
                 */
                remoteObject.sendRequest(RESULT_TODO, message, reply, option);

                int resultCode = reply.readInt();
                if (resultCode != RESULT_SUCCESS) {
                    throw new RemoteException();
                }
                int resultInt = reply.readInt();
                String resultStr = reply.readString();

                HiLog.info(LABEL_LOG, "result------ "+resultInt+", "+resultStr);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public IRemoteObject asObject() {
            return remoteObject;
        }
    }

主要就是向远程的Service传递 数据,并且处理远程Service传回来的数据。

远程Service中:

private static final String DESCRIPTOR = "com.example.hanruserviceability.RemoteAbility";

    private ServiceRemoteProxy serviceRemoteProxy = new ServiceRemoteProxy(DESCRIPTOR);

    public class ServiceRemoteProxy extends RemoteObject implements IRemoteBroker {

        private static final int RESULT_SUCCESS = 0;

        public ServiceRemoteProxy(String descriptor) {
            super(descriptor);

        }

        @Override
        public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) throws RemoteException {
            HiLog.info(LABEL_LOG, "onRemoteRequest----code:" + code);

            int initData = data.readInt();
            String initStr = data.readString();
            HiLog.info(LABEL_LOG, "onRemoteRequest----接收到:" + initData + "," + initStr);
            int resultData = initData * 1024;
            reply.writeInt(RESULT_SUCCESS);
            reply.writeInt(resultData);
            reply.writeString("地上鞋三双");

            return true;
        }

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

最后给按钮添加点击事件即可。

WX20210708-163449@2x

远程服务流程

  1. 创建发送端的代理类实现IRemoteBroker,用于发送数据。
  2. 创建远程端的代理类继承RemoteObject并实现IRemoteBroker,用于接收、处理、返回数据。
  3. 创建连接远程服务需要的的IAbilityConnection,并在onAbilityConnectDone中获取返回IRemoteObject创建发送端代理类的实例,并在适合的位置用创建的实例发送消息。
  4. 连接起远程服务后会调用onConnect,在此方法中返回远程代理类的实例。

顺序不一定是上面的顺序,只要该有的都有了,调用远程服务就没有问题。

前台Service

一般情况下,Service都是在后台运行的,后台Service的优先级都是比较低的,当资源不足时,系统有可能回收正在运行的后台Service。

在一些场景下(如播放音乐),用户希望应用能够一直保持运行,此时就需要使用前台Service。前台Service会始终保持正在运行的图标在系统状态栏显示。

使用前台Service并不复杂,开发者只需在Service创建的方法里,调用keepBackgroundRunning()将Service与通知绑定。调用keepBackgroundRunning()方法前需要在配置文件中声明ohos.permission.KEEP_BACKGROUND_RUNNING权限,同时还需要在配置文件中添加对应的backgroundModes参数。在onStop()方法中调用cancelBackgroundRunning()方法可停止前台Service。

使用前台Service的onStart()代码示例如下:

// 创建通知,其中1005为notificationId
NotificationRequest request = new NotificationRequest(1005);
NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent();
content.setTitle("title").setText("text");
NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(content);
request.setContent(notificationContent);

// 绑定通知,1005为创建通知时传入的notificationId
keepBackgroundRunning(1005, request);

在配置文件中,“module > abilities”字段下对当前Service做如下配置:

{    
    "name": ".ServiceAbility",
    "type": "service",
    "visible": true,
    "backgroundModes": ["dataTransfer", "location"]
}

在布局文件中,添加最后一个按钮:

    <Button
        ohos:id="$+id:keep_run_button"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:padding="5vp"
        ohos:top_margin="10vp"
        ohos:bottom_margin="10vp"
        ohos:text="Keep Background Running"
        ohos:background_element="$graphic:button_bg"
        ohos:text_alignment="center"
        ohos:text_size="18fp"/>

然后新建一个ServiceAbility:

package com.example.hanruserviceability;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.event.notification.NotificationRequest;
import ohos.rpc.IRemoteObject;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

public class ForegroundServiceAbility extends Ability {
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0x00201, "ForegroundServiceAbility");

    private static final int NOTIFICATION_ID = 0XD0000002;
    @Override
    public void onStart(Intent intent) {
        HiLog.error(LABEL_LOG, "ForegroundServiceAbility::onStart");
        super.onStart(intent);
        NotificationRequest request = new NotificationRequest(NOTIFICATION_ID).setTapDismissed(true);
        NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent();
        content.setTitle("Foreground Service").setText("I'm a Foreground Service");
        NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(
                content);
        request.setContent(notificationContent);
        keepBackgroundRunning(NOTIFICATION_ID, request);
    }

    @Override
    public void onBackground() {
        super.onBackground();
        HiLog.info(LABEL_LOG, "ForegroundServiceAbility::onBackground");
    }

    @Override
    public void onStop() {
        super.onStop();
        HiLog.info(LABEL_LOG, "ForegroundServiceAbility::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) {
    }
}

在MainAbilitySlice中,处理按钮的点击事件:

        private static final String FOREGROUND_SERVICE = "ForegroundServiceAbility";

        // 初始化组件
    private void initComponents() {
                ...
        // 前台service
        Component keepRunningButton = findComponentById(ResourceTable.Id_keep_run_button);
        keepRunningButton.setClickedListener(component -> startLocalService(LOCAL_BUNDLE, FOREGROUND_SERVICE));

    }

然后点击按钮运行:

serviceyunxing1

Service Ability生命周期

与Page类似,Service也拥有生命周期,如图1所示。根据调用方法的不同,其生命周期有以下两种路径:

  • 启动

    Service

    该Service在其他Ability调用startAbility()时创建,然后保持运行。其他Ability通过调用stopAbility()来停止Service,Service停止后,系统会将其销毁。

  • 连接

    Service

    该Service在其他Ability调用connectAbility()时创建,客户端可通过调用disconnectAbility()断开连接。多个客户端可以绑定到相同Service,而且当所有绑定全部取消后,系统即会销毁该Service。

    connectAbility()也可以连接通过startAbility()创建的Service。

0000000000011111111.20210702111617.04204162495410024928461829747996

源代码

感谢阿品:https://juejin.cn/post/6973836899462938631

更多内容:

1、社区:鸿蒙巴士https://www.harmonybus.net/

2、公众号:HarmonyBus

3、技术交流QQ群:714518656

4、视频课:https://www.chengxuka.com

发布者:韩茹,未经授权,禁止转载,违者必究:https://bus.chengxuka.com/archives/2488

发表评论

登录后才能评论