秒懂Android Service组件(1)

迈不过友情╰ 2022-05-28 01:49 284阅读 0赞

Android出来这么些年,发现国内程序员关于Android开发的文章写得越来越好了,有的真让人赞叹。不过再好也是别人的,自己只有亲自实践亲自总结,才能夯实基础,提升自我,尽力向大神的水平靠拢。

  • 概述
  • 如何使用

    • 启动模式使用方法

      • 1.声明标签
      • 2.创建服务
      • 3.启动服务
    • 绑定模式使用方法

      • 1.声明标签
      • 2.创建服务
      • 3.绑定服务
  • 总结

概述

真正做Android开发算来应该有两年多了,说实话自己写Service的时候还是比较少的。最近使用到了,刚好对其做个总结,我这边主要侧重于应用层面的,其实是因为我比较菜,深入原理的话怕讲不好。

首先我们应该清楚Service是Android四大组件之一,下面是官方网站对Service的定义,非常清楚

Service 是一个可以在后台执行长时间运行而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

服务分为两种形式:

  • 启动

当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后,服务会自行停止运行。

  • 绑定

当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

如何使用

对于一项技术首先要清楚什么场景下应该使用,这个技术是不是解决了我们的问题,然后才是如何使用

使用之前要明白Service有两种使用方式,启动模式绑定模式,如概述所示,这两种方式适用于不同的使用场景。

启动模式使用方法

这种模式启动的Service,一旦启动则与启动者毫无关系了,这个服务会一直运行在后台,直到服务完成任务后自己结束自己,或者其他组件主动结束它,或者操作系统强制结束它。

1.声明标签

AndroidManifest.xml文件中声明Service标签,如下面代码所示

  1. <service android:name=".your.services.name" android:enabled="true" android:exported="true">
  2. </service>

其实一个service只有name属性是必须的,其他都是可选的。
name: 这个Service 的唯一标识
enabled:此服务是否可以被系统实例化
exported:此服务是否可以被本应用以外的其他应用调起。

下面是其完整的代码清单,具体可参考 Service

  1. <service android:description="string resource"
  2. android:directBootAware=["true" | "false"]
  3. android:enabled=["true" | "false"]
  4. android:exported=["true" | "false"]
  5. android:icon="drawable resource"
  6. android:isolatedProcess=["true" | "false"]
  7. android:label="string resource"
  8. android:name="string"
  9. android:permission="string"
  10. android:process="string" >
  11. . . .
  12. </service>

2.创建服务

创建一个Service子类(Kotlin代码)

  1. package top.ss007.servicedemo.services
  2. ...
  3. class Service1 : Service() {
  4. companion object {
  5. private val TAG = Service1::class.java.simpleName
  6. }
  7. //Service 的抽象方法,子类必须实现,虽然在启动模式下无需写具体实现代码
  8. override fun onBind(intent: Intent): IBinder? {
  9. Log.d(TAG, "onBind")
  10. return null
  11. }
  12. override fun onCreate() {
  13. super.onCreate()
  14. Log.d(TAG, "onCreate")
  15. }
  16. override fun onDestroy() {
  17. super.onDestroy()
  18. Log.d(TAG, "onDestroy")
  19. }
  20. override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  21. if (intent != null) {
  22. Log.d(TAG, "onStartCommand:" + intent.getStringExtra("param1") + " 开始id: " + startId)
  23. }
  24. Log.d(TAG,"Thread id:"+ Thread.currentThread().id+" Thread name:"+Thread.currentThread().name)
  25. return super.onStartCommand(intent, flags, startId)
  26. }
  27. }

我们的服务必须继承Service类,并重写onBind方法,该方法是抽象方法必须重写,但是由于是启动模式可以不实现,返回null即可。此类还重写了onCreate、onStartCommand、onDestroy三个主要的生命周期方法,几个方法说明如下:

onBind()
在当前的启动模式下无需实现,直接返回null即可。 

onCreate()
  首次创建服务时,系统将调用此方法来执行初始化(在调用 onStartCommand() 或onBind() 之前)该方法只调用一次。

onStartCommand()
在当前启动模式下,当另一个组件(如 Activity)调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 我们需要在服务工作完成后,调用 stopSelf() 或 stopService() 来停止此服务。

onDestroy()
  当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。

3.启动服务

  1. ...
  2. class ActMainActivity : AppCompatActivity() {
  3. companion object {
  4. private val TAG = ActMainActivity::class.java.simpleName
  5. }
  6. override fun onCreate(savedInstanceState: Bundle?) {
  7. super.onCreate(savedInstanceState)
  8. setContentView(R.layout.act_main)
  9. btnStartService1.setOnClickListener(clickListener)
  10. btnStopService1.setOnClickListener(clickListener)
  11. ...
  12. }
  13. private val clickListener = View.OnClickListener { v ->
  14. val intent = Intent(this@ActMainActivity, Service1::class.java)
  15. intent.putExtra("param1", "hello server")
  16. when (v.id) {
  17. R.id.btnStartService1 -> {
  18. startService(intent)
  19. }
  20. R.id.btnStopService1 -> {
  21. stopService(intent)
  22. }
  23. ...
  24. else -> {
  25. }
  26. }
  27. }
  28. ...
  29. }

如上代码是一个Activity,上面有两个按钮,一个启动服务,一个结束服务。点击启动服务按钮启动服务,Logcat输出如下

  1. 03-28 01:01:06.282 4485-4485/top.ss007.servicedemo D/Service1: onCreate
  2. 03-28 01:01:06.283 4485-4485/top.ss007.servicedemo D/Service1: onStartCommand:hello server 开始id: 1
  3. Thread id:2 Thread name:main

从上面的log可以获得如下几个信息

  1. 以启动模式开始服务不会执行onBind方法
  2. 会依次执行onCreateonStartCommand两个方法
  3. service是运行在自己的进程中的主线程中的,不会单开线程,所以不能做耗时操作。

多次点击启动服务按钮,会多次执行onStartCommandonCreate方法只会执行一次,如下log所示

  1. 03-28 01:04:47.321 4485-4485/top.ss007.servicedemo D/Service1: onCreate
  2. 03-28 01:04:47.322 4485-4485/top.ss007.servicedemo D/Service1: onStartCommand:hello server 开始id: 1
  3. Thread id:2 Thread name:main
  4. 03-28 01:10:37.857 4485-4485/top.ss007.servicedemo D/Service1: onStartCommand:hello server 开始id: 2
  5. Thread id:2 Thread name:main
  6. 03-28 01:10:50.372 4485-4485/top.ss007.servicedemo D/Service1: onStartCommand:hello server 开始id: 3
  7. Thread id:2 Thread name:main

从上面的log可以看出,startId是不断增加的,而所有的线程id都是一样的,都是主线程。

绑定模式使用方法

在此模式下,Service可以看做是服务端,绑定的组件,例如Activity,看做是客户端,他们之间是通过Binder来实现通信的。我们此处集中在同一进程间的通信,不涉及跨进程通信(IPC)的讨论,所以采用扩展 Binder 类 的方法即可

实现步骤:

  • 1.新建一个自定义的名为LocalService的类,此类继承至Service
  • 2.新建一个内部类LocalBinder,此类继承自Binder类,并在里面提供一个public方法给客户端调用,例如getService()方法。

    inner class LocalBinder : Binder() {

    1. fun getService(): LocalService {
    2. return this@LocalService
    3. }
    4. }
  • 3.在LocalService里面的onBind的方法里面将LocalBinder的实例返回。

  • 4.在客户端中,从 onServiceConnected() 回调方法接收 Binder实例对象,那样就可以调用绑定服务了。

下面具体实现上面的步骤:

1.声明标签

AndroidManifest.xml文件中声明Service标签,与前述一致

2.创建服务

创建一个Service子类(Kotlin代码)

  1. ...
  2. class LocalService : Service() {
  3. companion object {
  4. private val TAG: String = LocalService::class.java.simpleName
  5. }
  6. private var quit: Boolean = false
  7. private var count: Int = 0
  8. override fun onBind(intent: Intent): IBinder? {
  9. Log.i(TAG, "Service is invoke onBind");
  10. return LocalBinder()
  11. }
  12. override fun onUnbind(intent: Intent?): Boolean {
  13. Log.i(TAG, "Service is invoke onUnbind");
  14. return super.onUnbind(intent)
  15. }
  16. override fun onCreate() {
  17. super.onCreate()
  18. Log.d(TAG, "Service is invoke Created");
  19. val thread = Thread {
  20. while (quit.not()) {
  21. Thread.sleep(1000)
  22. count++
  23. if (count > 1000) {
  24. quit = true
  25. break
  26. }
  27. }
  28. }
  29. thread.start()
  30. }
  31. fun getCount(): Int {
  32. return count
  33. }
  34. override fun onDestroy() {
  35. super.onDestroy()
  36. Log.i(TAG, "Service is invoke Destroyed");
  37. quit = true;
  38. }
  39. inner class LocalBinder : Binder() {
  40. fun getService(): LocalService {
  41. return this@LocalService
  42. }
  43. }
  44. }

我们的服务必须继承Service类,并重写onBind方法,该方法是抽象方法必须重写,在当前绑定状态的情况下需要实现该方法并返回一个IBinder的实现类。此类还重写了onCreateonUnbindonDestroy三个主要的生命周期方法,几个方法说明如下:

onBind()
在当前绑定模式下,当其他组件通过调用bindService() 与服务绑定时系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。

onCreate()
  首次创建服务时,系统将调用此方法来执行初始化(在调用 onStartCommand() 或onBind() 之前)该方法只调用一次。

onUnbind
客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。 (服务不必自行停止运行。)

onDestroy()
  当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。
  
这这个服务中,我们有一个公共方法 getCount(),外界通过这个方法可以获取到 count的值,而这个count在服务创建之初会每秒自增1直到1000

3.绑定服务

  1. package top.ss007.servicedemo
  2. ...
  3. class ActMainActivity : AppCompatActivity() {
  4. companion object {
  5. private val TAG = ActMainActivity::class.java.simpleName
  6. }
  7. var localService: LocalService? = null
  8. override fun onCreate(savedInstanceState: Bundle?) {
  9. super.onCreate(savedInstanceState)
  10. setContentView(R.layout.act_main)
  11. ...
  12. btnStartServiceB1.setOnClickListener(clickListener)
  13. btnStopServiceB1.setOnClickListener(clickListener)
  14. btnShowCounter.setOnClickListener(clickListener)
  15. ...
  16. }
  17. private val clickListener = View.OnClickListener { v ->
  18. ...
  19. val bindIntent = Intent(this@ActMainActivity, LocalService::class.java)
  20. when (v.id) {
  21. ...
  22. R.id.btnStartServiceB1 -> {
  23. Log.d(TAG, "绑定调用:bindService");
  24. bindService(bindIntent, serviceCon, Service.BIND_AUTO_CREATE)
  25. }
  26. R.id.btnStopServiceB1 -> {
  27. Log.d(TAG, "解除绑定调用:unbindService");
  28. if (localService != null) {
  29. localService = null;
  30. unbindService(serviceCon);
  31. }
  32. }
  33. R.id.btnShowCounter -> {
  34. btnShowCounter.text = localService?.getCount().toString()
  35. Log.d(TAG, "服务中的计数器:"+localService?.getCount().toString());
  36. }
  37. ...
  38. else -> {
  39. }
  40. }
  41. }
  42. private val serviceCon: ServiceConnection = object : ServiceConnection {
  43. override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
  44. Log.d(TAG, "绑定成功调用:onServiceConnected "+" "+name?.className)
  45. // 获取Binder
  46. val binder = service as LocalService.LocalBinder
  47. localService = binder.getService()
  48. }
  49. override fun onServiceDisconnected(name: ComponentName?) {
  50. localService = null
  51. }
  52. }
  53. }

如上代码是一个Activity,上面有3个按钮,一个启动服务,一个结束服务,一个获取服务中count的值。点击绑定服务按钮绑定服务,Logcat输出如下

  1. 03-28 02:33:31.576 6631-6631/top.ss007.servicedemo D/ActMainActivity: 绑定调用:bindService
  2. 03-28 02:33:31.580 6631-6631/top.ss007.servicedemo D/LocalService: Service is invoke Created
  3. 03-28 02:33:31.581 6631-6631/top.ss007.servicedemo I/LocalService: Service is invoke onBind
  4. 03-28 02:33:31.597 6631-6631/top.ss007.servicedemo D/ActMainActivity: 绑定成功调用:onServiceConnected top.ss007.servicedemo.services.LocalService

可见,当绑定成功后,activity中的ServiceConnection 里面的onServiceConnected就会收到回调,从回调里面就获取到了服务端传过来的Binder实例,进而可以通过其调用服务里的方法与服务交互了。

点击获取服务中计数的按钮,输出log如下

  1. 03-28 02:57:44.871 6631-6631/top.ss007.servicedemo D/ActMainActivity: 服务中的计数器:3
  2. 03-28 02:57:54.127 6631-6631/top.ss007.servicedemo D/ActMainActivity: 服务中的计数器:12

可见从Activity里面确实调用到了服务里面的函数。

总结

上面就是在同进程下,服务Service的两种使用方法。相关的话题还涉及到运行在前台的服务IntentService 等,大家可以搜索相关内容学习。

本文源代码下载地址

参考文章
Google官方网站
关于Android Service真正的完全详解,你需要知道的一切

发表评论

表情:
评论列表 (有 0 条评论,284人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android属性动画

    > 自从Android3.0 版本加入属性动画后,在平时的开发中多多少少也使用了,但是从来没有对其做一个系统的分析和总结,最近刚好有点时间,来对这个话题做一个分析和总结。