Android 学习笔记(十):Service的生命周期与实现方式

startService的生命周期以及实现方式

[外链图片转存失败(img-QKcIG2EQ-1565706668816)(en-resource://database/395:1)]

可以通过一个简单的音乐播放器来测试startService的生命周期

  1. MainActivity中放三个按钮,分别代表播放,暂停,停止
    [外链图片转存失败(img-PQi9FGAn-1565706668819)(en-resource://database/396:1)]
    将这三个按钮分别绑定监听器,播放按钮监听器中开启服务并播放歌曲,停止按钮停止服务并停止播放,暂停按钮暂停播放。
  • 播放事件中使用startService(Intent intent)方法开启服务,停止事件中会调用服务中stopSelf(int startId)结束服务。
  1. 自定义MyService,继承自Service,然后分别重写onCreate(),onStartCommand(),onDestory(),onBind()方法,然后在方法中进行相关操作,并记录日志。其中onStartCommand()在服务被调用时会被执行,故在此方法进行操作。
  2. 最后在AndroidManifest.xml中注册该Service。label标签给该service取个名字便于区分
<service
    android:name=".MyService"
    android:label="@string/service_test_worktwo">
</service>

开始测试

  1. 点击播放音乐,观察日志,发现进入了onCreate()onStartCommand()状态,开始播放音乐,服务被开启,点击多次时,只调用onStartCommand()方法,onCreate()方法只有第一次会被调用。
  2. 点击暂停播放,然后再点播放音乐,观察日志,发现只从onStartCommand()进入,说明只有第一次打开应用时才走onCreate()
  3. 点击停止播放,观察日志,发现从onStartCommand()进入,然后调用onDestroy()方法,服务被关闭
    在这里插入图片描述
总结:第一次调用startService()方法时,才会走onCreate()方法,每次调用startService()方法时,都会走onStartCommand()方法。每次回调onStartCommand()方法时,startId都会递增,这个是用于标记每次用户发起的服务。当离开应用时,服务还在后台运行。只有显式的调用stopself()或者stopService()方法才会结束服务,结束服务时会调用onDestroy()方法结束服务。

bindService的生命周期以及三种实现方式

在这里插入图片描述

通过继承Binder类来实现bindService

  1. MainActivity中放三个按钮,分别代表绑定服务,解除绑定,绑定服务后客户端使用服务中的方法。MainActivity中提供ServiceConnection用于连接服务,绑定服务中使用bindService()方法进行绑定。解除绑定中使用unbindService()方法进行解除绑定。
    在这里插入图片描述
  2. 自定义MyBindService,继承自Service,然后分别重写onCreate(),onDestory(),onBind(),onUnBind()方法,然后在方法中进行相关操作,并记录日志。MyBindService中提供一个内部类,并提供一个getService()方法用于返回这个Service类。
  3. 注册

测试

  1. 点击开启服务按钮,观察日志,发现经过了onCreate()onBind()状态,Activity和服务被绑定。此时继续点击开启服务按钮,发现无日志,说明Activity被绑定且未销毁前,只会onBind()(绑定)一次。
    在这里插入图片描述
  2. 点击测试按钮,获得了随机值,符合预期,说明服务以及绑定成功。
  3. 点击取消绑定按钮,观察日志,发现经过了onUnBind()onDestroy()状态,服务解绑后被销毁,由于只有一个Activity绑定了服务,所以解绑后服务被销毁,符合预期。
    在这里插入图片描述
  4. 退出应用时,发现经过了onUnBind()onDestroy()状态,说明服务会随着Activity的销毁而销毁。
    在这里插入图片描述

通过使用Messenger类来实现bindService

使用了一个小demo测试了一下,这种方法的生命周期与第一种相同。记录一下使用方式。

  • 服务实现一个 Handler,由其接收来自客户端的每个调用的回调
  • Handler 用于创建 Messenger 对象(对 Handler 的引用)
  • Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
  • 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务
  • 服务在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message。

通过AIDL来实现bindService

  • Messenger的底层就是使用AIDL实现其底层结构的,都能够实现IPC,但是使用Messenger不需要考虑多线程问题,使用AIDL可以同时让服务处理多个请求,但是此时必须要考虑线程安全问题。

通过一个简单的图书存储demo测试AIDL,分为客户端和服务端

  • 服务端:

    1. 创建aidl文件夹
      在这里插入图片描述
      其中Java类为自定义的Book实体类,需实现Parcelable接口,这样才能被AIDL支持
      在这里插入图片描述
      Book.aidl中需要声明Book
      在这里插入图片描述
      IBookManager.aidl文件相当于一个接口,提供图书管理的系统的两个简单操作
      在这里插入图片描述
      然后直接将aidl包整个拷贝到客户端main下即可
    2. 创建远程服务类RemoteService类,继承自Service,重写onBind()方法,这个方法返回一个Binder类用于客户端使用。在这个类中各个生命周期中记录日志,方便记录生命周期的变化。
      在这里插入图片描述
  • 客户端:

    1. 创建四个按钮,方便记录生命周期和测试。分别添加绑定事件。
      在这里插入图片描述
    2. MainActivity中在点击事件中进行绑定和解绑,并进行添加书籍和查看书籍操作(远程服务调用)。

测试

  1. 先打开服务端,然后打开客户端
  2. 点击绑定服务,观察服务端日志,发现经过了onCreate()onBind()状态,绑定服务成功。
    在这里插入图片描述
  3. 点击解绑,观察服务端日志,发现经过了onUnbind()onDestroy()状态,绑定解绑成功,由于只有一个应用绑定该服务,故解绑后被销毁。
    在这里插入图片描述
  4. 测试添加学生和查看所有学生,观察日志均成功,远程服务调用成功。
    在这里插入图片描述
  5. 退出应用,进入下图状态,说明服务的生命周期与Activity绑定了
    在这里插入图片描述

异常记录

  1. 创建好aidl文件后进行编译,报以下错误
    在这里插入图片描述
    原因:下图是创建后自动创建的接口和方法,
    入参类型不是AIDL支持的,需要对入参加上定向tagin或out或inout。加了后会编译正常。
    在这里插入图片描述
    修改后:在这里插入图片描述

  2. 运行时找不到Book类
    项目结构如下图:在RemoteService中引用Book类时编译报错,显示找不到这个类。
    在这里插入图片描述
    解决办法:在build.gradle中添加以下参数,将aidl添加为Java文件夹。

   sourceSets {
           main {
               manifest.srcFile 'src/main/AndroidManifest.xml'
               java.srcDirs = ['src/main/java', 'src/main/aidl']
               resources.srcDirs = ['src/main/java', 'src/main/aidl']
               aidl.srcDirs = ['src/main/aidl']
               res.srcDirs = ['src/main/res']
               assets.srcDirs = ['src/main/assets']
           }
       }
  1. 测试的时候,点击绑定服务时,应用Crash,观察日志报如下错误
Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit

原先是通过action直接绑定。
在这里插入图片描述
谷歌后找到问题和解决方案:
原因:非法意图行为,Android 5.0要用显式intent去访问Service
1. intent改成显式访问,但是是两个app应用,找不到另外一个类,故这种方法不行。
2. intent添加setPackage方法,将隐式调用改成显示调用。
4. 测试的时候,没有先绑定服务,直接远程调用服务查看所有书籍时,应用Crash,观察日志报以下错误

java.lang.NullPointerException: Attempt to invoke interface method 'java.util.List com.example.worktwoaidlserver.IBookManager.getBooks()' on a null object reference

错误原因:服务还未绑定,所有客户端的服务为空,直接调用导致空指针异常。
解决办法 : 使用一个boolean变量isBound,每次调用服务前先判断是否已经绑定。未绑定不能调用服务。

总结

  • startServicebindService生命周期上,只有显式调用stopSelf()或者其他应用调用stopService()时才会被销毁,否则一直在后台运行。而bindService与绑定的应用生命周期相同,退出应用服务就会被解绑,当没有应用绑定到服务时,服务会被销毁(未使用startService()方法)。

  • startServicebindService使用上,startService通过onStartCommand()方法直接在里面进行各种操作。bindService通过重写onBind()方法,返回一个IBinder接口实例给客户端,然后通过这个实例得到相应的Service,只有有了这个Service才能进行各种操作。

  • bindService三种实现方式的区别:

  1. 扩展Binder类:服务供自己的应用使用时,并且与客户端在相同的进程中工作,这个情况也是最常见的情况。
  2. 使用Messenger类:需要让接口跨不同的进程时使用,这也是IPC最简单的使用方式,因为Messenger会在单一线程中创建多个请求的队列,就无需考虑线程安全问题。
  3. 使用AIDLAIDLMessenger的底层数据结构,也是用来执行IPC操作,一般只有需要服务同时处理多个请求时才会使用,必须要采用线程安全式设计。

在这里插入图片描述

发布了167 篇原创文章 · 获赞 230 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_39240270/article/details/99485567