将Android进行到底之服务(service)

前语

咱们都知道,字节最近发布了PICO4VR眼镜,我买了一个,体会还行。因为我也是做VR眼镜的Android运用层开发的,所以想把自己项目中遇到的一些Android技术分享给读者。近些年随着VR眼镜的鼓起,Android的的服务(Service)和播送(Broadcast)以及内容供给者(Content Provider)越来越被重用,相反Activity这个从前很吃香的组件在VR眼镜的开发过程中却用的不多。本节我会介绍Android的服务在VR眼镜中的运用,服务(Service)是Android的四大组件之一,许多Android的萌新在开发的时分肯定知道这个组件可是用得不是许多,究竟刚入行更多的时分是写界面。很少会有时机去触摸服务这种没有用户界面的组件。所以,让咱们一同看看服务的运用吧。


一、服务是什么?

服务(Service)是一种能够在后台长时间履行而没有用户界面的组件。需求留意的是因为它运转在UI线程,因而不能做耗时操作,不然或许会引起ANR(Application Not Response: 运用无呼应)。

留意:服务在其托管进程的主线程中运转,它既不创立自己的线程,也不在独自的进程中运转(除非另行指定)。假设服务将履行任何 CPU 密集型作业或阻止性操作(例如 MP3 播放或联网),则应经过在服务内创立新线程来完结这项作业。经过运用独自的线程,您能够下降产生“运用无呼应”(ANR) 过错的危险,而运用的主线程仍可持续专注于运转用户与 Activity 之间的交互。

简单说便是服务运转在主线程,不要在服务中做耗时操作,不然或许会引起ANR

二、服务(Service)的生命周期

服务的生命周期有两种办法,。因为服务能够和Activity绑定,也能够不绑定。当咱们的activity需求和服务通讯的时分,是需求把服务和Activity进行绑定的,因而服务的生命周期分为未绑定Activity的和绑定Activity的。咱们能够用以下两张图来描绘这两种绑定办法的生命周期:

(1)未绑定Activity的服务生命周期图

将Android进行到底之服务(service)

(1)onCreate() 当咱们运用Intent和startService()办法发动了一个服务时,就会履行onCreate()办法,它会在onStartCommand()或许时onBind()办法之前被体系调用,效果是做一些初始化相关的操作。假设服务已经在运转,则onCreate()办法不会被调用 (2)onStartCommand() 当一个组件(如Activity)经过startService()恳求发动服务时,体系会调用onStartCommand()办法,这个办法一旦履行,就会在后台无限期履行。假设完结了这个办法,那么就需求咱们调用stopSelf()或许时stopService()来中止服务。

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

咱们能够看到这个办法有个回来值,这个回来值代表什么意思呢?咱们一同来看看。这个回来值是指用于描绘体系应如安在体系中止服务的情况下持续运转服务。从 onStartCommand() 回来的值有必要是以下常量之一:

START_NOT_STICKY

假设体系在 onStartCommand() 回来后中止服务,则除非有待传递的挂起 Intent,不然体系不会重建服务。这是最安全的选项,能够防止在不必要时以及运用能够轻松重启一切未完结的作业时运转服务。

START_STICKY

假设体系在 onStartCommand() 回来后中止服务,则其会重建服务并调用 onStartCommand(),但不会从头传递最终一个 Intent。相反,除非有挂起 Intent 要发动服务,不然体系会调用包含空 Intent 的 onStartCommand()。在此情况下,体系会传递这些 Intent。此常量适用于不履行命令、但无限期运转并等候作业的媒体播放器(或类似服务)。

START_REDELIVER_INTENT

假设体系在 onStartCommand() 回来后中止服务,则其会重建服务,并经过传递给服务的最终一个 Intent 调用 onStartCommand()。一切挂起 Intent 均依次传递。此常量适用于主动履行应立即康复的作业(例如下载文件)的服务。

其实简单说便是体系应该以哪种办法发动服务,我在开发中经常运用的是START_STICKY这个值,它的意思是发动了服务后,当服务被杀死的时分,体系会尽最大了的尽力重启服务。其他的值读者能够去看API阐明,这儿纷歧一列举了。

(3)onDestroy() 当某个操作导致服务中止,比方履行了stopService(),那么服务接下来会履行onDestroy()办法。服务一般应在这个办法中履行资源的开释操作

留意:用户能够查看其设备上正在运转的服务。假设他们发现自己无法识别或信任的服务,则能够中止该服务。为防止用户意外中止您的服务,您需求在运用清单的 <service> 元素中添加 android:description。请在描绘顶用一个短句解说服务的效果及其供给的好处。

(2)绑定Activity的服务生命周期图

将Android进行到底之服务(service)

上图中的onCreate()和onDestroy办法都和前面讲的相同,这儿咱们只讲onBind()和onUnbind()办法

(1)onBind()

当一个组件想经过调用bindService()与服务绑定,比方咱们运用AIDL做进程间通讯时,体系会调用这个办法,在这个办法的完结中,咱们有必要经过回来IBinder供给一个接口,来供客户端和服务器端用来与服务进行通讯。

(2)onUnbind 当某个操作导致服务器免除绑定,比方履行了unbindService(),那么服务接下来会免除与当前Activity的绑定,接下来服务将面对毁掉


三、服务的相关操作

(1)发动服务

咱们能够经过将 Intent 传递给 startService() 或 startForegroundService(),从 Activity 或其他运用组件发动服务。Android 体系会调用服务的 onStartCommand() 办法,并向其传递 Intent,然后指定要发动的服务

留意:假设您的运用面向 API 等级 26 或更高版别,除非运用本身在前台运转,不然体系不会对运用或创立后台服务施加约束。假设运用需求创立前台服务,则其应调用 startForegroundService()。此办法会创立后台服务,但它会向体系宣布信号,标明服务会将自行提升至前台。创立服务后,该服务有必要在五秒内调用自己的 startForeground() 办法。

(2)中止服务

发动服务有必要办理自己的生命周期。换言之,除非有必要回收内存资源,不然体系不会中止或毁掉服务,而且服务在 onStartCommand() 回来后仍会持续运转。服务有必要经过调用 stopSelf() 自行中止运转,或由另一个组件经过调用 stopService() 来中止它。

一旦恳求运用 stopSelf() 或 stopService() 来中止服务,体系便会赶快毁掉服务。假设服务一起处理多个对 onStartCommand() 的恳求,则不应在处理完一个发动恳求之后中止服务,因为或许已收到新的发动恳求(在第一个恳求结束时中止服务会中止第二个恳求)。为防止此问题,咱们能够运用 stopSelf(int) 保证服务中止恳求一直根据最近的发动恳求。

换言之,在调用 stopSelf(int) 时,咱们需传递与中止恳求 ID 相对应的发动恳求 ID(传递给 onStartCommand() 的 startId)。此外,假设服务在您能够调用 stopSelf(int) 之前收到新发动恳求,则 ID 不匹配,服务也不会中止。

留意:为防止糟蹋体系资源和消耗电池电量,请保证运用在作业完结之后中止其服务。如有必要,其他组件可经过调用 stopService() 来中止服务。即使为服务启用绑定,假设服务收到对 onStartCommand() 的调用,您一直仍须亲身中止服务

(3)创立绑定服务

绑定服务允许运用组件经过调用 bindService() 与其绑定,然后创立长时间衔接。此服务通常不允许组件经过调用 startService() 来发动它。

如需与 Activity 和其他运用组件中的服务进行交互,或需求经过进程间通讯 (IPC) 向其他运用揭露某些运用功能,则应创立绑定服务。

如要创立绑定服务,需经过完结 onBind() 回调办法回来 IBinder,然后界说与服务进行通讯的接口。然后,其他运用组件可经过调用 bindService() 来检索该接口,并开端调用与服务相关的办法。服务只用于与其绑定的运用组件,因而若没有组件与该服务绑定,则体系会毁掉该服务。咱们不必像经过 onStartCommand() 发动的服务那样,以相同办法中止绑定服务。

如要创立绑定服务,咱们有必要界说指定客户端如何与服务进行通讯的接口。服务与客户端之间的这个接口有必要是 IBinder 的完结,而且服务有必要从 onBind() 回调办法回来该接口。收到 IBinder 后,客户端便可开端经过该接口与服务进行交互。

多个客户端能够一起绑定到服务。完结与服务的交互后,客户端会经过调用 unbindService() 来撤销绑定。假设没有绑定到服务的客户端,则体系会毁掉该服务。

(4)在前台运转服务

前台服务是用户主动意识到的一种服务,因而在内存不足时,体系也不会考虑将其中止。前台服务有必要为状态栏供给告诉,将其放在运转中的标题下方。这意味着除非将服务中止或从前台移除,不然不能清除该告诉。

留意:假设运用面向 Android 9(API 等级 28)或更高版别并运用前台服务,则其有必要恳求 FOREGROUND_SERVICE 权限。这是一种普通权限,因而,体系会主动为恳求权限的运用颁发此权限。假设面向 API 等级 28 或更高版别的运用试图创立前台服务但未恳求 FOREGROUND_SERVICE,则体系会抛出 SecurityException。

如要恳求让服务在前台运转,请调用 startForeground()。此办法选用两个参数:仅有标识告诉的整型数和用于状态栏的 Notification。此告诉有必要拥有 PRIORITY_LOW 或更高的优先级。下面是官网示例:

val pendingIntent: PendingIntent =
        Intent(this, ExampleActivity::class.java).let { notificationIntent ->
            PendingIntent.getActivity(this, 0, notificationIntent, 0)
        }
val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
        .setContentTitle(getText(R.string.notification_title))
        .setContentText(getText(R.string.notification_message))
        .setSmallIcon(R.drawable.icon)
        .setContentIntent(pendingIntent)
        .setTicker(getText(R.string.ticker_text))
        .build()
startForeground(ONGOING_NOTIFICATION_ID, notification)

留意:供给给 startForeground() 的整型 ID 不得为 0。

如要从前台移除服务,则调用 stopForeground()。此办法选用布尔值,指示是否需一起移除状态栏告诉。此办法不会中止服务。可是,假设您在服务仍运转于前台时将其中止,则告诉也会随之移除。

(5)服务的容错处理

这儿的容错处理主要是用在咱们运用服务来做一些IPC通讯的时分,比方有这样一个比如,咱们需求将一个投屏的SDK接入到项目中,这时咱们能够将投屏的SDK做二次封装,然后再给项目用,可是咱们又怕这个投屏的SDK写的BUG影响咱们的APP,所以为了防止投屏SDK崩溃而导致咱们的运用崩溃,咱们挑选将投屏的SDK再开一个进程去完结,也便是将投屏的SDK做成一个服务端,咱们的项目做客户端。客户端和服务端能够经过IPC来通讯。这样投屏SDK就算崩溃也是在它自己的进程崩溃,不会影响咱们的APP进程:

将Android进行到底之服务(service)
当咱们将投屏SDK放到服务端的时分,假设服务端因为反常重启了,咱们需求做一些容错处理让客户端能够不受服务端反常的影响持续运用投屏服务。一般情况下,假设咱们客户端因为反常重启了,是能够持续正常运用服务端的投屏服务的,因为咱们的服务端一直都活着。可是假设服务端因为反常重启了,咱们的客户端是不知道的,因为C-S架构便是客户端恳求,服务端呼应。

这种情况下咱们的容错方案便是:当服务端产生反常重启的时分,经过播送的办法,像一切衔接它的客户端宣布自己重启的播送,让一切的客户端从头衔接一下它,然后就能够正常运用投屏服务了。是不是很简单,原理如上图所示。


总结

以上便是今日要讲的内容,本文介绍了服务的生命周期,运用办法以及留意事项,而且结合实际的运用场景解析服务。包含服务的创立和中止,容错处理。因为篇幅的原因,本文的介绍无法详尽一切有关的常识,感兴趣的伙伴能够移步官网。欢迎评论区一同讨论,一同进步。