前言
Thread和Service
如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。
因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService
、Context.stopService
、Context.bindService
,Context.unbindService
,来控制它,你也可以在 Service
里注册 BroadcastReceiver
,在其他地方通过发送 broadcast
来控制它,当然这些都是 Thread
做不到的。
service可以通过两种方式创建:startService()
和bindService()
.
- startService():一般用于在后台上传文件或者下载文件等,不跟其他组件通信,就算启动它的应用被销毁了,它仍然会欢快的在后台执行,直到完成任务的时候自刎(自己调用stopSelf())或者被其他人下黑手(调用stopService()).
- bindService():允许其他组件跟它进行通信,允许多个客户端绑定到同一个service上,当所有的客户端都解除绑定后,该service就销毁了。
Service在清单文件中的声明
前面说过Service分为启动状态和绑定状态两种,但无论哪种具体的Service启动类型,都是通过继承Service基类自定义而来,也都需要在AndroidManifest.xml中声明,那么在分析这两种状态之前,我们先来了解一下Service在AndroidManifest.xml中的声明语法,其格式如下:
1 | <service android:enabled=["true" | "false"] |
配置项
android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。
android:name:对应Service类名
android:permission:是权限声明
android:process:是否需要在单独的进程中运行,当设置为android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以
remote
和:remote
不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote
。android:isolatedProcess :设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。
android:enabled:是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。
方式一(startService)
startService开启服务和结束服务稍微简单一点。
1 | <service android:name=".DBService"/> |
服务类
1 | public class DBService extends Service { |
开启服务
1 | //开启服务 |
结束服务
1 | //结束服务 |
开启服务时,调用一次startService,生命周期执行的方法依次是:
onCreate() ==> onStartCommand();
调用多次startService
,onCreate
只有第一次会被执行,而onStartCommand
会执行多次。
结束服务时,调用stopService
,生命周期执行onDestroy
方法,并且多次调用stopService
时,onDestroy
只有第一次会被执行。
方式二(bindService)
定义Service
1 | import android.app.Service; |
定义的DBBinder
就是能让Activity和Service交互
bingService开启服务时,根据生命周期里onBind方法的返回值是否为空,有两种情况。
- onBind返回值是null;
调用bindService开启服务,生命周期执行的方法依次是:
onCreate() ==> onBind();
调用多次bindService,onCreate和onBind也只在第一次会被执行。
调用unbindService结束服务,生命周期执行onDestroy方法,并且unbindService方法只能调用一次,多次调用应用会抛出异常。使用时也要注意调用unbindService一定要确保服务已经开启,否则应用会抛出异常。 - onBind返回值不为null;
看一下android对于onBind方法的返回类型IBinder的介绍,字面上理解是IBinder是android提供的进程间和跨进程调用机制的接口。而且返回的对象不要直接实现这个接口,应该继承Binder这个类。
那么我们就在自己写的DBService里创建一个内部类DBBinder,让他继承Binder,并在onBind方法里返回DBBinder的对象。
Activity绑定
bindService()
1 | public class MyApplication extends Application { |
绑定成功后就会调用onServiceConnected
这时候调用bindService开启服务,生命周期执行的方法依次是:
onCreate() ==> onBind() ==> onServiceConnected();
可以发现我们自己写的DBConn类里的onServiceConnected方法被调用了。调用多次bindService,onCreate和onBind都只在第一次会被执行,onServiceConnected会执行多次。
并且我们注意到onServiceConnected方法的第二个参数也是IBinder类型的,不难猜测onBind()方法返回的对象被传递到了这里。打印一下两个对象的地址可以证明猜测是正确的。
也就是说我们可以在onServiceConnected方法里拿到了DBService服务的内部类DBBinder的对象,通过这个内部类对象,只要强转一下,我们可以调用这个内部类的非私有成员对象和方法。
调用unbindService结束服务和上面相同,unbindService只能调用一次,onDestroy也只执行一次,多次调用会抛出异常。
区别
接下来我们说一下startService和bindService开启服务时,他们与activity之间的关系。
- startService开启服务以后,与activity就没有关联,不受影响,独立运行。
- bindService开启服务以后,与activity存在关联,退出activity时必须调用unbindService方法,否则会报ServiceConnection泄漏的错误。
注意
最后还有一点,同一个服务可以用两种方式一同开启,没有先后顺序的要求,DBService的onCreate只会执行一次。
关闭服务需要stopService和unbindService都被调用,也没有先后顺序的影响,DBService的onDestroy也只执行一次。但是如果只用一种方式关闭服务,不论是哪种关闭方式,onDestroy都不会被执行,服务也不会被关闭。这一点需要注意。