Android开发四大组件之Service如何使用(与Activity通信)
Android开发学习笔记——四大组件之Service
service
在我们开发过程中,我们可能会遇到应用需要执行后台任务的需求,这些任务往往属于耗时操作,而且不需要与用户进行交互操作,这时,如果我们使用Activity来实现就不太合适了。而Service(服务)就是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。其运行不依赖于任务用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
不过,对于Service的认识,根据官方文档,我们还需要注意以下几点:
- service不是一个独立的进程,除非在AndroidManifest中单独指定进程名,否则service就是和应用程序运行在同一个进程之下的。
- service不是一个独立的线程,service通常是运行在主线程下的,因此如果要在service中执行耗时任务需要在service中启动一个子线程中去执行耗时任务,避免造成ANR。
- service存在的目的是为了告诉系统,应用存在后台任务需要处理,是不依赖于任何Activity的,Service可以不需要UI就在后台运行,不用管开启它的页面是否被销毁,只要进程还在就可以在后台运行。可以通过startService()方式调用,这里需要注意,除非Service手动调用stopService()或者Service内部主动调用了stopSelf(),否则Service一直运行。
- service的bindService方法可以与其它组件实现一个长期的连接和交互,通过调用bindService,应用程序可以将把一些功能暴露给其它应用程序。
service和线程
或许,有人会问,不就是执行后台的耗时任务吗?我们直接开个子线程不也可以实现吗?为什么还要使用service呢?
首先,我们需要了解两者之间的区别:
- Thread(线程):线程是程序执行的最小单位,在Android中包含一个主线程,其中完成了UI操作,为了防止ANR,对于耗时操作需要开启子线程进行异步操作。
- Service(服务):服务是Android开发中的四大组件之一,其一般是运行在主线程中的,我们可以将其看作是一个看不见没有UI的Activity,如果要进行耗时操作,需要在service中开启子线程执行。
我们可以看到,实际上service和thread完全是不同的东西。Thread是完全独立于Activity运行的,在Activity中启动一个子线程后,即使Activity被销毁,如果我们没有主动终止掉Thread,Thread会继续运行直到任务结束。这就会出现一个问题,当Activity被finish掉后,我们无法在去对该Thread进行管理和控制。而与Thread不同的是,service是Android的一个组件,即使用户没有与其进行交互,它也会在后台运行,它具有完整的生命周期,我们可以通过Context.startService、Context.stopService、Context.bindService,Context.unbindService等一系列方法在任何地方对其进行管理,而且在service还能使用BroadcastReceiver等方式与Activity进行通信,这都是Thread无法做到的。
那么我们应该在什么时候使用service,什么时候使用Thread呢?
- 如果耗时任务仅仅在与用户交互的时候才执行,那我们应该直接使用Thread,比如:在进入Activity时播放一些背景音乐,离开时停止。
- 如果需要长时间执行后台任务,不需要进行交互操作时,我们应该使用Service,如果属于耗时操作,应该在Service中启动子线程异步执行。
- 如果需要执行一个或多个后台任务,但是任务执行完毕后自动关闭时,我们应该使用Thread。
- 如果任务占用CPU时间长,资源大的情况下,要使用Thread。
还有个值得注意的地方是,Service能够提高进程的优先级,与Thread相比,在应用退到后台时,包含Service的进程优先级更高,更不容易被杀掉。
基本使用
创建
service的创建方式和Activity的创建方式相似,继承Service类实现自定义Service,也需要在AndroidManifest中进行注册,不同的是Service没有UI界面,因此不需要创建xml布局。
使用AndroidStudio创建Service,只需要选择new->Service->service即可,AS会自动创建Service类并在AndroidManifest中声明。
可以看到,AndroidStudio提供了service和IntentService两个选择,其实二者都是继承自Service类,不过IntentService是Service的一个子类,对其进行了一些封装,下文将对其进行简单介绍,这里我们选择Service即可。
AS自动创建对应的Service类,并在AndroidManifest中进行注册:
class MyService : Service() { override fun onBind(intent: Intent): IBinder { TODO("Return the communication channel to the service.") } }
<!--其中enable属性为是否可被系统实例化,exported为是否暴露给其它应用组件--> <service android:name=".service.MyService" android:enabled="true" android:exported="true" />
这样,我们就创建了一个简单的service。
startService和bindService
创建好service后,那么接下来,我们应该如何去使用一个service呢?Android提供了两种使用service的方式,分别为startService和bindService,这两个方法都能够开启一个Service,但是两者之间存在一些区别,其生命周期也有所不同。
生命周期
首先,我们需要了解下Service的生命周期。上文中,我们已经说过,service是Android中的一个独立组件,其和Activity一样都具有自己的生命周期,但是不同的是,service通过不同的方式启动,其生命周期也有所不同,如下图:
从上图可以看到,使用startService和bindService运行Service其生命周期是不同的。使用startService运行service,service在调用onCreate之后会调用onStartCommand(onStart被弃用),此后service将会一直运行下去,不依赖于任何其它组件,直到在其它地方调用了stopService或是自己调用stopSelf停止service,然后才会调用onDestroy方法继而被销毁;而bindService会在调用的Activity和Service之间创建一个绑定关系,创建后调用onBind方法,然后直到所有绑定该service的client被销毁或是主动调用unBindService解绑,service就会调用onUnBind和onDestroy方法,继而被销毁。
实际上,startService和bindService是可以被同时使用的,当同时使用startService和bindService两种方式来运行service时,只有stopService和unBindService均被调用时,service才会被销毁。
startService
首先,我们来看通过startService方式运行Service,与启动Activity相同,都是使用Intent来启动Service,创建intent指定service类名,然后调用startService方法即可。代码如下:
val intent = Intent(this, MyService::class.java) startService(intent)
启动service,依次调用onCreate和onStartCommand方法,结果如下:
对于同一个Service,我们可以启动多次,但是service只会创建一次,即onCreate方法只会调用一次,但是onStartCommand会调用多次。如下图:
我们可以看到第二次启动service,onCreate没有再次调用。
停止service和启动一样,我们只需要指定service类,然后调用stopService即可正在运行中的service。无论再任何地方,不管是否为启动service的Activity,我们都可以调用stopService将service直接停止。代码如下:
val intent = Intent(this, MyService::class.java) stopService(intent) //或者service类任务执行完毕后自动停止 stopSelf()
从上图可以看到,调用stopService后,service调用onDestroy方法,然后被销毁掉。
那么,对于启动了多次的service呢?其停止有什么不一样吗?我们启动多次Service,然后停止service,结果如下:
我们可以看到,onStartCommand被调用了三次,而onDestory只调用了一次,再次调用stopService也没有任何输出。这说明,无论调用了多少次startService,只要调用一次stopService即可停止Service。这是因为,启动多次service实际上都是同一个service实例,其onCreate方法只调用了一次,因此只需要停止一次即可。
onStartCommand其返回值是一个整型值,其标记了Service被停止后的行为:
常量 | 说明 |
---|---|
START_STICKY | 如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由 于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传 递到service,那么参数Intent将为null |
START_NOT_STICKY | 使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务 |
START_REDELIVER_INTENT | 重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入 |
START_STICKY_COMPATIBILITY | START_STICKY的兼容版本,但不保证服务被kill后一定能重启 |
bindService
与startService相比,bingService相对而言实现要复杂一点。客户端通过调用 bindService() 绑定到服务,绑定是异步的,系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder。要接收 IBinder,客户端必须提供一个 ServiceConnection 实例用于监控与服务的连接,并将其传递给 bindService()。当 Android 系统创建了客户端与服务之间的连接时,会回调ServiceConnection 对象的onServiceConnected()方法,向客户端传递用来与服务通信的 IBinder多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至其他绑定的客户端。当所有客户端都取消了与服务的绑定后,系统会将服务销毁(除非 startService() 也启动了该服务)。
使用bindService启动service主要包含以下几个步骤:
- 自定义IBinder类,用于实现Activity和service之间的交互,并在onBind方法中返回IBinder对象。
- 自定义ServiceConnection类,用于监听service绑定状态,获取通信IBinder。
- 调用bindService方法,绑定service。
首先,创建IBinder类,用于获取到service对象,进而实现activity和service的通信。代码如下:
inner class MyBinder : Binder() { fun getService() : MyService{ return this@MyService//返回外部类service对象 } }
在onBind方法中返回MyBinder对象,如下:
override fun onBind(intent: Intent): IBinder { Log.e("test_service", "onBind") return MyBinder() }
然后,自定义ServiceConnection类,用于监听service的绑定状态,在onServiceConnected中获取service实例。代码如下:
class MyServiceConnection : ServiceConnection { override fun onServiceDisconnected(p0: ComponentName?) { Log.e("test_conn", "onServiceDisconnected") } override fun onServiceConnected(p0: ComponentName?, binder: IBinder?) { Log.e("test_conn", "onServiceConnected") //根据IBinder获取service实例 val service = (binder as MyService.MyBinder).getService() service.test() } }
最后,调用bindService方法,绑定并启动service,代码如下:
val intent = Intent(this, MyService::class.java) bindService(intent, MyServiceConnection(), BIND_AUTO_CREATE)
绑定service,结果如下图所示:
从图中,我们可以看到,调用bindService方法后,service依次调用了onCreate和onBind方法,而onStartCommand方法并没有被调用。绑定成功后,回调了serviceConnection中的onServiceConnected方法,在该方法中,我们获取到了service的实例并调用了service中的test()方法,实现了对service的交互。
如果在同一个Activity中多次调用bindService将不会有任何输出,因为每个Activity只能绑定一次,当多个Activity绑定同一个service时,只有第一次会调用onBind方法,后面只会调用onServiceConntected方法,如下图所示:
从上图我们可以看到,第二个Activity绑定Service时并没有调用onCreate和onBind方法。
对于bindService,当Activity被销毁时service会自动解除绑定,这是因为,service时依赖于其绑定的context的生命周期的,当Activity被销毁时context也被回收,绑定关系自然就解除了。如果Activity绑定了service后直接退出,如下图:
我们发现,service的unBind方法并没有被调用,而是直接抛出异常了,这是因为,在Activity销毁时,我们没有调用unBindService方法解除绑定,从而导致异常。在onDestroy中解除绑定,代码如下:
override fun onDestroy() { super.onDestroy() unbindService(serviceConnection) }
当有多个Activity绑定一个service时,只有所有的Activity都被销毁或是解除绑定后,service的onUnbind方法和onDestroy方法才会被调用。我们启动两个Activity,并都绑定service,然后依次退出,我们会发现,只有当两个Activity都退出后,onUnbind方法和onDestroy方法才会被调用,如下图:
可以看出,与startService不同,如果我们退出Activity到后台,bindService会解除绑定关系,当所有绑定关系都被解除后,service就会自动被销毁。而startService除非调用了stopService或者stopSelf方法,否则即使退到后台,所有Activity都被销毁,service都不会被销毁。
同时,service可以被startService和bindService两种方式启动,此时只有所有绑定关系销毁而且调用了stopService或stopSelf后才会被停止销毁。
与Activity的通信
在开发中,Activity与Service可能存在需要相同通信的情况,此时,我们往往可以采取intent、binder等方法。
intent
其实在上述启动Service方法的讲述中,我们就可以发现,无论是startService还是bindService都是使用intent来确定启动的Service对象的,这和启动Activity很像,我们发现onStartCommand和onBind方法均存在这一个intent参数,这其实就是我们在启动service时传入的intent,通过它,我们就可以从Activity向Service传值。代码如下:
//startService传值 val intent = Intent(this, MyService::class.java) intent.putExtra("start", "start service") startService(intent) ... //bindService传值 val intent = Intent(this, MyService::class.java) intent.putExtra("bind", "bind service") bindService(intent, serviceConnection, BIND_AUTO_CREATE) //获取startService的值 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val str = intent?.getStringExtra("start") Log.e("test_service", "onStartCommand---${str}") return super.onStartCommand(intent, flags, startId) } //获取bindService的值 override fun onBind(intent: Intent): IBinder { val str = intent.getStringExtra("bind") Log.e("test_service", "onBind---${str}") return MyBinder() }
结果如下:
Binder
其实,在之前讲解bindService时,我们就知道,通过bindService启动service我们可以获取到service的对象,既然获取到了service的对象,那么在Activity中,我们就可以很容易就可以对service进行管理。那么如果我们想要实现在service中更新Activity的数据或者是UI呢?这时,我们可以使用回调的方法来实现。
比如,我们需要在service被销毁时,为Activity返回一个参数,我们可以在service中创建一个接口,然后再onDestroy时调用接口方法,再在Activity中实现这个接口并传入到service即可。代码如下:
在service中创建接口,并调用接口方法
override fun onDestroy() { Log.e("test_service", "onDestroy") destroyListener?.onDestroyed("the service was destroy") super.onDestroy() } var destroyListener : OnDestroyListener ?= null interface OnDestroyListener{ fun onDestroyed(str : String) } fun setOnDestroyListener(destroyListener : OnDestroyListener){ this.destroyListener = destroyListener }
Activity中在onServiceConnected方法中获取到service对象后,实现接口并传入service。
override fun onServiceConnected(p0: ComponentName?, binder: IBinder?) { Log.e("test_conn", "onServiceConnected") val service = (binder as MyService.MyBinder).getService() service.setOnDestroyListener(object : MyService.OnDestroyListener{ override fun onDestroyed(str: String) { Log.e("test_listener", "message:${str}") } }) }
结果如下:
其它方法
如果我们需要Service主动向Activity发送消息时,我们还可以使用BroadcastReceiver机制,在Activity中注册广播监听,在Service中发送广播;除此之外,我们还能够使用EventBus等方法。
ForegroundService
在之前的篇幅中,我们提到过Service能够提高进程的优先级,使进程处于后台的时候更难被杀掉。但实际上,后台运行的service的进程优先级还是相对较低的,还是存在着在内存不足时被杀掉的情况,如果我们需要开发一个长时间在后台运行的服务,那很显然,service的保活将是一个重要问题。比如,我们要开发一个音乐播放器,音乐往往需要在后台播放的,这时如果进程突然被杀掉,这无疑是很影响用户体验的。这时,我们就需要将service设置为前台服务,即ForegroundService。和一般的service完全不可见不同,前台服务会在状态栏提供一个通知Notification。
其实,创建一个前台服务与创建一个普通的Service基本没有区别,我们只需要在service中创建一个notification对象,然后调用startForeground方法即可将其设置为一个前台服务,在onDestroy中调用stopForeground。代码如下:
val intent = Intent(this, MainActivity::class.java) val notification = NotificationCompat.Builder(this, "channel_id") .setContentIntent(PendingIntent.getActivity(this,0,intent,0)) .setContentText("foreground service") .setContentTitle("service title") .setSmallIcon(R.mipmap.ic_launcher) .setWhen(System.currentTimeMillis()) .build() startForeground(1001, notification) override fun onDestroy() { Log.e("test_service", "onDestroy") stopForeground(true) super.onDestroy() }
结果如下图:
当然,关于notification的设置还有很多内容,包括自定义notification的布局等,这里就不多做介绍了。
本文地址:https://blog.csdn.net/qq_36425800/article/details/107892108