鸿蒙 Ability 讲解(Service Ability讲解)

dmzhaoq1
发布于 2021-3-8 09:43
浏览
1收藏

然后三、Service Ability讲解

 

 先来看一下Service Ability的官方解释基于 Service 模板的 Ability(以下简称“Service”)主要用于后台运行任务(如执行音乐播放、文件下载等),但不提供用户交互界面。Service 可由其他应用或 Ability 启动,即使用户切换到其他应用,Service 仍将在后台继续运行。
  Service 是单实例的。在一个设备上,相同的 Service 只会存在一个实例。如果多个 Ability 共用这个实例,只有当与 Service 绑定的所有 Ability 都退出后,Service 才能够退出。由于Service 是在主线程里执行的,因此,如果在 Service 里面的操作时间过长,开发者必须在Service 里创建新的线程来处理,防止造成主线程阻塞,应用程序无响应。其实和Android的Service有点像。
下面创建一个Service,右键你的包名 → New → Ability → Empty Service Ability。如下图所示鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区然后鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区上面的这个Visible你如果勾选上就是你的这个Service对其他应用程序可见,而Enable background mode表示后台模式,如果你打开这个开关,就表示你的Service要在后台运行,还可以自己选择你要在后台干嘛。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区这个我翻译一下鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区这里你就必须要选一个,不选就不能创建这个Service Ability。下面我们就直接创建,不勾选,不打开。创建Service Ability不会生成AbilitySlice。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区这个时候在config.json文件中会自动生成相关的属性。

鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

可以看到相比于Page ,Service的属性要少一些,而且type的属性值是“service”。

 

① Service Ability 生命周期


下面看一下ServiceAbility的代码:

package com.llw.helloworld;

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

public class ServiceAbility extends Ability {
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");

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

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

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

生命周期:onStart()、onCommand()、onConnect()、onDisconnect()、onStop()。

单个讲解:

  • onStart() 该方法在创建 Service 的时候调用,用于 Service 的初始化,在 Service 的整个生命周期只会调用一次。
  • 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 应通过实现此方法来清理任何资源,如关闭线程、注册的侦听器等。


② 启动Service Ability


  Ability 为开发者提供了startAbility() 方法来启动另外一个 Ability。因为 Service 也是 Ability的一种,开发者同样可以通过将 Intent 传递给该方法来启动 Service。不仅支持启动本地Service,还支持启动远程 Service。
  开发者可以通过构造包含 DeviceId、BundleName 与 AbilityName 的 Operation 对象来设置目标 Service 信息。这三个参数的含义如下:

  • DeviceId:表示设备 ID。如果是本地设备,则可以直接留空;如果是远程设备,可以通过ohos.distributedschedule.interwork.DeviceManager 提供的 getDeviceList 获取设备列表。
  • BundleName:表示包名称。
  • AbilityName:表示待启动的 Ability 名称。
    下面用代码来实践一下,比如我现在要在MainAbilitySlice的onStart方法中启动ServiceAbility。就可以这么写
    	/**
         * 启动本地服务
         */
        private void startupLocalService() {
            Intent intent = new Intent();
            //构建操作方式
            Operation operation = new Intent.OperationBuilder()
                    // 设备id
                    .withDeviceId("")
                    // 应用的包名
                    .withBundleName("com.llw.helloworld")
                    // 跳转目标的路径名  通常是包名+类名
                    .withAbilityName("com.llw.helloworld.ServiceAbility")
                    .build();
            //设置操作
            intent.setOperation(operation);
            startAbility(intent);
        }
    ​

然后在onStart中调用即可。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区那么怎么证明ServiceAbility是启动了呢?很简单,我们只要在ServiceAbility的onStart方法中打印一个日志就可以了。进入到ServiceAbility,你会发现创建的时候就给你写好了日志。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区那么现在启动远程模拟器,然后运行HelloWorld。进入到主页鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区那么这个时候Service已经启动了,通过日志来看看,点击编译器下面的HiLog栏目,然后输入Demo,就能找到这个日志了。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

那么现在我们就启动了这个本地的Service,那么如何启动远程的Service呢?

	private void startupRemotelyService() {
        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
                .withDeviceId("deviceId")
                .withBundleName("com.huawei.hiworld.himusic")
                .withAbilityName("com.huawei.hiworld.himusic.entry.ServiceAbility")
                // 设置支持分布式调度系统多设备启动的标识
                .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
                .build();
        intent.setOperation(operation);
        startAbility(intent);
    }

 

远程启动Service可以这么写,但是有一点你要确认,那就是你启动的这个服务是否允许其他应用程序发现?否则你就算知道这个服务的包名和类名也是白搭。还记得刚才在创建Service Ability的时候的Visible吗?勾选就是允许,默认是没有勾选的。那么我又想去勾选了咋办?难道我现在重新创建一个再勾选上?感觉这样是可以的,但是太蠢了。不够优雅。既然你也不知道怎么搞,我也不知道怎么搞,那么就实验一下,比如我再创建一个ServiceAbility。这里设置名为ServiceAbility2,然后勾选一下Visible,然后我们到config.json配置文件中去看之前的没有勾选的Service有啥不同。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

 现在你是不是就有种恍然大明白的感觉了。只要通过加一个visible的属性,设置为true,就可以了,如果没有这个属性,就是默认为false。OK,那么这就解决了这个启动Service的问题。 

 

通过 startAbility() 方法来启动 Service。

 

如果 Service 尚未运行,则系统会先调用 onStart()来初始化 Service,再回调 Service 的 onCommand()方法来启动Service。刚才我们并没有看到有打印onCommand,是因为它里面没有方法。那么现在我在这个onCommand方法里面也加一个日志,然后重新运行一下

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

鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

  • 如果 Service 正在运行,则系统会直接回调 Service 的 onCommand()方法来启动 Service。这个场景需要先返回到设备主页面,然后再打开这个应用,首先返回主页面,点击右边的圆形按钮

鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区设备主页,这时候Service在后台运行,然后再点一下圆形按钮,进入到应用页面。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区这里是应用页面,目前只有一个新增的应用,其他两个是系统应用,这里是一个列表,你可以通过鼠标按住左键上下进行拖动。然后点击这个HelloWorld。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区回到应用的主页面。这个时候你看日志鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区系统直接回调 Service 的 onCommand()方法来启动 Service。这样实际操作一下是不是印象更深刻呢?为了使这个操作更加易懂,我决定安装一个电脑录屏软件,然后再把录得视频转GIF,再贴到文章里,这样看起来就更加的易懂了。刚才说了启动,那么下面说停止。


③ 停止Service Ability

 

  • 停止 Service
      Service 一旦创建就会一直保持在后台运行,除非必须回收内存资源,否则系统不会停止或销毁 Service。开发者可以在 Service 中通过 terminateAbility()停止本 Service 或在其他 Ability调用 stopAbility()来停止 Service。
      停止 Service 同样支持停止本地设备 Service 和停止远程设备 Service,使用方法与启动Service 一样。一旦调用停止 Service 的方法,系统便会尽快销毁 Service。

 

有两种停止Service的方法,在Page Ability中停止,和在本Service中停止,先试一下第一种。
下面我们在MainAbilitySlice中增加一个停止服务的方法。​

 

/** * 停止本地服务 在Page Ability中停止Service */ private void stopLocalService() { Intent intent = new Intent(); //构建操作方式 Operation operation = new Intent.OperationBuilder() // 设备id .withDeviceId("") // 应用的包名 .withBundleName("com.llw.helloworld") // 跳转目标的路径名 通常是包名+类名 .withAbilityName("com.llw.helloworld.ServiceAbility") .build(); //设置操作 intent.setOperation(operation); //停止服务 stopAbility(intent); } ​

然后再点击按钮的时候调用。

鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区然后先运行一下进入到主页面,然后点击Next按钮,看下面的日志。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区可以看到当我们从其他的Page Ability中停止Service时,会先回调onBackground。因为这个时候服务是在前台运行的,系统会把服务放到后台,然后再通过stop来停止这个服务。
下面再看看在本Service中停止这个服务。可以通过一个延时服务来操作,下面来看看代码怎么写的。

	/**
     * 创建一个线程池
     */
    final static ScheduledExecutorService service = Executors.newScheduledThreadPool(4);

    private void stopService() {
        // 延时任务
         service.schedule(threadFactory.newThread(new Runnable() {
            @Override
            public void run() {
                //停止服务当前服务
                terminateAbility();
            }
            //延时三秒执行
        }), 3, TimeUnit.SECONDS);
    }

    /**
     * 线程工厂
     */
    private ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(final Runnable r) {
            return new Thread() {
                @Override
                public void run() {
                    r.run();
                }
            };
        }
    };

 为什么要这么写呢?因为DS里面推荐使用ScheduledExecutorService ,不然我就直接用Timer或者Thread就可以了。创建了一个线程池,然后创建一个线程工厂,在进行延时操作的时候,传入了三个参数,一个是线程工厂,里面有一个Runnable(),第二个参数代表数量,第三个参数是单位,上面的代码就是3秒。
 下面直接运行到模拟器,然后等待三秒就会自动调用terminateAbility();停止Service。你会发现和通过其他的Page Ability停止服务的执行流程是一样的。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

③ 连接Service Ability


  如果 Service 需要与 Page Ability 或其他应用的 Service Ability 进行交互,则应创建用于连接的 Connection。Service 支持其他 Ability 通过 connectAbility()方法与其进行连接。
  在使用 connectAbility()处理回调时,需要传入目标 Service 的 Intent 与 IAbilityConnection的实例。IAbilityConnection 提供了两个方法供开发者实现:onAbilityConnectDone() 用来处理连接的回调,onAbilityDisconnectDone() 用来处理断开连接的回调。

  在MainAbilitySlice中添加如下代码:

	/**
     * 连接服务
     */
    private void connectService(){
        // 连接 Service
        Intent intent = new Intent();
        //构建操作方式
        Operation operation = new Intent.OperationBuilder()
                // 设备id
                .withDeviceId("")
                // 应用的包名
                .withBundleName("com.llw.helloworld")
                // 跳转目标的路径名  通常是包名+类名
                .withAbilityName("com.llw.helloworld.ServiceAbility")
                .build();
        //设置操作
        intent.setOperation(operation);
        //连接到服务
        connectAbility(intent,connection);

    }

    // 创建连接回调实例
    private IAbilityConnection connection = new IAbilityConnection() {
        // 连接到 Service 的回调
        @Override
        public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int i) {
            // 在这里开发者可以拿到服务端传过来 IRemoteObject 对象,从中解析出服务端传过来的信息
        }

        // 断开与连接的回调
        @Override
        public void onAbilityDisconnectDone(ElementName elementName, int i) {
           
        }
    };

然后在点击的时候调用鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区别Service的onConnect方法中加入日志打印鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区下面运行一下:鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

连接成功。

 

④ 断开Service Ability


断开服务其实就比较的简单了,调用**disconnectAbility()**方法即可,而且不用传intent,但是要传IAbilityConnection进入,所以可以可以这样来测试,在连接到Service之后马上断开连接。

//断开服务
disconnectAbility(connection);

鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区然后运行起来,进入应用页面,然后点击Next。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

OK,到这一步,相信你已经会基本操作了。而Service的生命周期根据调用方法的不同,其生命周期有以下两种路径:

 

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

 

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


看一下官网的图片

鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

⑤ 前台Service


  刚才我们说的都是后台的Service,那么怎么到前台来呢?最通用的前台服务就是音乐播放了,用手机的时候它会在通知栏创建,然后播放音乐,那么在鸿蒙中需要怎么使用前台服务呢?使用前台 Service 并不复杂,开发者只需在 Service 创建的方法里,调用keepBackgroundRunning()将 Service 与通知绑定。调用 keepBackgroundRunning()方法前需要在配置文件中声明 ohos.permission.KEEP_BACKGROUND_RUNNING 权限,该权限是 normal 级别,同时还需要在配置文件中添加对应的 backgroundModes 参数。在onStop()方法中调用 cancelBackgroundRunning()方法可停止前台 Service。

  说这么多没啥用,下面来实际操作一下:
在connectService方法中注释断开服务鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区然后进入到ServiceAbility中,新一个启动前台服务的方法。

	/**
     * 启动前台服务
     */
    private void startupForegroundService(){
        //创建通知请求 设置通知id为9527
        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);
        keepBackgroundRunning(1005,request);
        HiLog.error(LABEL_LOG, "ServiceAbility::startupForegroundService");
    }

然后在onStart中调用鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区
别忘了在config.json中给相关的代码配置:鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区然后直接运行到主页面,之后会先启动Service,然后将Service变成前台服务。运行之后如下:鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区说实话目前也就只是日志打印出来了,但是我也不知道当前这个服务是不是在前台。
然后在onCommand中取消前台服务:

	@Override
    public void onCommand(Intent intent, boolean restart, int startId) {
        HiLog.error(LABEL_LOG, "ServiceAbility::onCommand");

        cancelBackgroundRunning();
        HiLog.error(LABEL_LOG, "ServiceAbility::cancelBackgroundRunning");

    }

再运行一次。鸿蒙 Ability 讲解(Service Ability讲解)-鸿蒙开发者社区

结束

分类
已于2021-3-8 09:43:04修改
2
收藏 1
回复
举报
回复
    相关推荐