一、Service 简介
很多情况下,一些与用户很少需要产生交互的应用程序,我们一般让它们在后台运行就行了,而且在它们运行期间我们仍然能运行其他的应用。为了处理这种后台进程,Android 引入了 Service 的概念。
# Service 在 Android 中是一种长生命周期的组件,它不实现任何用户界面,是一个没有界面的 Activity
# Service 长期在后台运行, 执行不关乎界面的一些操作比如: 网易新闻服务,每隔 1分钟去服务查看是否有最新新闻
# Service 和 Thread 有点相似,但是使用 Thread 不安全, 不严谨
# Service 和其他组件一样,都是运行在主线程中,因此不能用它来做耗时的操作
二、Android 中的进程
1)Android 中进程的种类
进程优先级由高到低,依次为:
1.Foreground process 前台进程
2. Visible process 可视进程, 可以看见, 但不可以交互.
3. Service process 服务进程
4. Background process 后台进程
5. Empty process 空进程(当程序退出时, 进程没有被销毁, 而是变成了空进程)
2)进程的回收机制
Android 系统有一套内存回收机制,会根据优先级进行回收。Android 系统会尽可能的维持程序的进程, 但是终究还是需要回收一些旧的进程节省内存提供给新的或者重要的进程使用。
# 进程的回收顺序是:从低到高
# 当系统内存不够用时, 会把空进程一个一个回收掉
# 当系统回收所有的完空进程不够用时, 继续向上回收后台进程, 依次类推
# 但是当回收服务, 可视, 前台这三种进程时, 系统非必要情况下不会轻易回收, 如果需要回收掉这三种进程, 那么在系统内存够用时, 会再给重新启动进程;但是服务进程如果用户手动的关闭服务, 这时服务不会再重启了。
3)为什么用服务而不是线程
进程中运行着线程, Android 应用程序刚启动都会开启一个进程给这个程序来使用。Android 一个应用程序把所有的界面关闭时, 进程这时还没有被销毁, 现在处于的是空进程状态,Thread 运行在空进程中, 很容易的被销毁了。
服务不容易被销毁, 如果非法状态下被销毁了, 系统会在内存够用时, 重新启动。
三、Service 的生命周期
service 的生命周期,从它被创建开始,到它被销毁为止,可以有两条不同的路径。
被开启的 service 通过其他组件调用 startService()被创建。这种 service 可以无限地运行下去,必须调用 stopSelf()方法或者其他组件调用 stopService()方法来停止它。当service 被停止时,系统会销毁它。A bound service (绑定模式)被绑定的 service 是当其他组件(一个客户)调用 bindService()来创建的。客户可以通过一个 IBinder 接口和 service 进行通信。客户可以通过 unbindService()方法来关闭这 种连接。一个 service 可以同时和多个客户绑定,当多个客户都解除绑定之后,系统会销毁service。
Tips: Service 的这两中生命周期并不是完全分开的。
也就是说,你可以和一个已经调用了 startService()而被开启的 service 进行绑定。比如,一个后台音乐 service 可能因调用 startService()方法而被开启了,稍后,可能用户想要控制播放器或者得到一些当前歌曲的信息, 可以通过 bindService()将一个 activity 和 service 绑定。 这种情况下, stopService()或 stopSelf()实际上并不能停止这个 service,除非所有的客户都解除绑定。
Service 的生命周期回调函数
和 activity 一样,service 也有一系列的生命周期回调函数,你可以实现它们来监测 service 状态的变化,并且在适当的时候执行适当的工作。
下面的 service 展示了每一个生命周期的方法:
这个图说明了 service 典型的回调方法, 尽管这个图中将开启的 service 和绑定的service 分开,但是你需要记住,任何 service 都潜在地允许绑定。所以,一个被开启的 service 仍然可能被绑定。实现这些方法,你可以看到两层嵌套的 service 的生命周期。
积极活动的生命时间
service 积极活动的生命时间(active lifetime)是从 onStartCommand() 或onBind()被调用开始,它们各自处理由 startService()或 bindService()方法传过来的 Intent 对象。
如果 service 是被开启的,那么它的活动生命周期和整个生命周期一同结束。
如果 service 是被绑定的,它们它的活动生命周期是在 onUnbind()方法返回后结束。
Tips :尽管一个被开启的 service 是通过调用 stopSelf() 或 stopService()来停止的,没有一个对应的回调函数与之对应,即没有 onStop()回调方法。所以,当调用了停止的方法,除非这个 service 和客户组件绑定,否则系统将会直接销毁它,onDestory()方法会被调用,并且是这个时候唯一会被调用的回调方法。
管理生命周期
当绑定 service 和所有客户端解除绑定之后,Android 系统将会销毁它, (除非它同时被 onStartCommand()方法开启)。因此,如果你的 service 是一个纯粹的绑定 service,那么你不需要管理它的生命周期。、
然而,如果你选择实现 onStartCommand()回调方法,那么你必须显式地停止service,因为 service 此时被看做是开启的。这种情况下,service 会一直运行到它自己调用 stopSelf()或另一个组件调用 stopService(),不论它是否和客户端绑定。
另外,如果你的 service 被开启并且接受绑定,那么当系统调用你的 onUnbind()方法时,如果想要在下次客户端绑定时候接受一个 onRebind()的调用(而不是调用 onBind()),你可以选择在 onUnbind()中返回 true。onRebind()的返回值为 void,但是客户端仍然在onServiceConnected()回调方法中得到 IBinder 对象。
四、远程服务调用实例
在 Android 平台中,各个组件运行在自己的进程中,他们之间是不能相互访问的,但是在程序之间是不可避免的要传递一些对象, 在进程之间相互通信。 为了实现进程之间的相互通信, Android采用了一种轻量级的实现方式RPC(Remote Procedure Call 远程进程调用)来完成进程之间的通信,并且 Android 通过接口定义语言(Android Interface DefinitionLanguage ,AIDL)来生成两个进程之间相互访问的代码,例如,你在 Activity 里的代码需要访问 Service 中的一个方法,那么就可以通过这种方式来实现了。
AIDL 是 Android 的一种接口描述语言; 编译器可以通过 aidl 文件生成一段代码, 通过预先定义的接口达到两个进程内部通信进程的目的. 如果需要在一个 Activity 中, 访问另一个Service 中的某个对象, 需要先将对象转化成 AIDL 可识别的参数(可能是多个参数), 然后使用 AIDL 来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象。
AIDL RPC 机制是通过接口来实现的,类似 Windows 中的 COM 或者 Corba,但他是轻量级的, 客户端和被调用实现之间是通过代理模式实现的, 代理类和被代理类实现同一个接口 IBinder 接口。
下面是案例-商城支付的步骤:
需求:分别创建两个工程,模拟一个支付平台,暂且叫支付宝,模拟一个商户端,叫商户。商户可以调用支付宝发布的远程服务进行收款操作。
1)新创建一个 Android 工程《支付宝》。在 src 目录下创建 com.share.alipay.aidl 包,然后在该包下创建 AlipayRemoteService.aidl 文件。在该文件中只声明一个接口,在接口里声明一个方法
package com.share.alipay.aidl;
interface AlipayRemoteService{
boolean forwardPayMoney(float money);
}
Tips:当该 aidl 文件创建好以后 ADT 会自动在 gen 目录下创建对应的类。
2)在《支付宝》src 目录下创建 com.share.alipay.service 包,在该包中新建一个Service,叫 AlipayService,该类实现付款功能。代码清单如下:
3)在《支付宝》工程的 AndroidManifest.xml 中注册该 AlipayService。
4)创建一个新 Android 工程,名字叫《商户》,包名:com.share.shop。使用默认的布局文件和默认的 MainActivity 类。
将《支付宝》工程中的 AlipayRemoteService.aidl 文件拷贝到《商户》工程的 src 目录下,同时注意添加对应的包名,要求包名必须跟该文件在原工程中的包名严格一致。
5)编辑 activity_main.xm 布局文件。
6)编写 MainActivity 类,在该类中实现核心方法。
7)先将《支付宝》部署到模拟器,然后将《商户》部署到模拟器,然后在《商户》界面输入一个金额, 然后点击确定支付, 发现 《商户》 工程已经成功通过远程服务调用了 《支付宝》中的服务。