一、service是什么?
二、如何使用service
三、startService的启动流程
四、onStartCommand的返回值
五、stopService流程
六、bindService的启动和结束流程
七、后台service和前台service
八、IntentService
九、service拓展
一、service是什么
service是一个可以在后台执行、长时间运行而不提供用户界面的应用组件,也可为其他应用提供一些功能接口。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。所谓的后台是相对前台而言的,具体就是说不依赖于用户界面。
service不是进程,不是线程。但service运行于所在进程的主线程,它有独立的生命周期。
二、如何使用service
实现一个音乐播放功能
如果在activity里面实现,程序退出后,音乐播放也停止了,不能后台播放。
service就可以满足这种情况,service有两种启动方式,startService、bindService。startService启动服务后,service和client端就完全没联系了,bindService就是这种情况的补充,通过bindService可实现client和service的交互。
下图是service的基本代码
启动service后,通过下面的代码可以查看系统已经启动的service
从Log也可以看出,的确生成了新的service,如下图
启动后抓取dumpstate log也生成了一个service的记录,如下图
下面简述一下service的使用步骤
1、AndroidManifest.xml声明服务
android:directBootAware=[“true”|“false”],Android7.0新特性,用户解锁设备前是否可以运行
android:enabled=[“true”|“false”]是否能被系统实例
android:exported=[“true”|“false”]其他应用的组件能否start或者bind该service
android:icon=“drawable resource”
android:isolatedProcess=[“true”|“false”]service将运行在一个单独的进程,进程中只有一个service
android:label=“string resource”
android:name=“string”必须要指定的属性,service的名字
android:permission=“string“需要的权限,使用该服务的应用需要在manifest中使用
android:process=”string”>所在进程名
. . .
2、创建服务,继承service类,实现onCreate、onBind、onStartCommand、onDestroy方法
onCreate:服务首次创建时调用,如果服务已经运行,则不会调用
onStartCommand(Intent intent, int flags, int startId):当其他组件通过调用startService请求启动服务时,系统将调用此方法,并把启动服务端的intent传递进来。每startService一次,onStartCommand就调用一次,每次传递进来的startId自增1
onDestroy:当服务被销毁时(stopSelf或者stopService或者unbindService),onDestroy将被调用
3、startService启动服务,stopService停止服务。
下面将详细讲述service的启动和停止流程。
三、startService启动流程
startService流程的进程示意图
AMS中service的相关类
1. /android/frameworks/base/core/java/android/app(Android6.0)
ActivityManagerNative:客户端与AMS进行通信的binder调用
ActivityManagerProxy:AMS代理,供客户端调用
ActivityThread:应用程序主线程
ApplicationThreadNative:AMS与客户端进行通信的binder调用
ApplicationThreadProxy:AMS调用客户端的代理
上面这些类的关系如下图
2. /android/frameworks/base/service/core/java/com/android/server/am(Android 6.0)
ActivityManagerService:四大组件管理的核心类,同时管理和调度用户进程
ServiceRecord:在AMS中用来保存一个service的信息
ActiveServices:管理ServiceRecord的核心类
StarItem:ServiceRecord里面的内部类,代表一次startService的动作
这些类的大概关系如下:
启动流程简要说明
第一步:1~6,客户端调用startService,通过binder进入到AMS所在进程
第二步:7~10,调用resolveService匹配intent获取ServiceInfo,创建ServiceRecord结构,在FW中表示一个service,并把该ServiceRecord加入到ActiveServices的ServiceMap中管理,然后创建一个StartItem(表示一次startService的动作),把startItem添加到ServiceRecord中的pendingStarts中,最后把该ServiceRecord加入到pendingServices中
ResolveInfo rInfo =
AppGlobals.getPackageManager().resolveService(
service, resolvedType,
ActivityManagerService.STOCK_PM_FLAGS, userId);
ServiceInfo sInfo =
rInfo != null ? rInfo.serviceInfo : null;
if (sInfo == null) {
Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId +
": not found");
return null;
}
返回来的ServiceInfo结构如下,包含service的一些基本信息
ServiceInfo | applicationInfo | taskAffinity:默认为包名 |
processName: 进程名 | ||
className: application的类名 | ||
dataDir: 数据存放路径/data/user/0/com.bookstore.main | ||
packageName: 包名 | ||
targetSdkVersion | ||
…… | ||
enabled | ||
exported | ||
name:服务名com.example.testservice.MusicService | ||
processName: 进程名,默认为包名,可自定义com.example.testservice:service | ||
packageName:包名com.example.testservice | ||
…… |
根据ServiceInfo创建ServiceRecord
Class | Field | value |
ServiceRecord | ActivityManagerService service | |
ServiceInfo info | ||
Application appInfo | ||
long createTime | ||
ComponentName name | 包名/Service名字,com.example.testservice/com.example.testservice.MusicService | |
String packageName | com.example.testservice | |
String processName | com.example.testservice:service | |
boolean stopIfKilled | ||
boolean exported | FALSE | |
Runnable restarter | ||
ProcessRecord app | 进程数据 | |
boolean isForeground | FALSE | |
boolean startRequested | TRUE | |
ArrayList |
||
ArrayList |
||
ArrayMap |
||
ArrayMap |
一个startItem表示一次startService的动作,每startService一次,只生成一个ServiceRecord实例,但会生成多个StartItem,并且每次StartItem.id自增1
Class | Field |
StartItem | ServiceRecord sr |
boolean taskRemoved | |
int id | |
Intent intent | |
ArrayList |
|
…… |
第三步:11~15,创建进程,切换到主线程,创建主线程looper
第四步:21~24、37~38,发送BIND_APPLICATION消息并处理,最终调用Application.onCreate()方法。这里可以看出,如果一个应用有多个进程,当多个进程启动时,Application.onCreate()会调用多次。
第五步:25,从pendingService中把ServiceRecord取出来运行。
第六步:27~30、39~41,发送CREATE_SERVICE消息,最终调用Service.onCreate()方法
第七步:31~35、42~43,从ServiceRecord的pendingStarts中把startItem移出来,发送SERVICE_ARGS消息,并把startItem加入到ServiceRecord的deliveredStarts中,最终调用Service.onStartCommand()。
第八步:44~47,根据onStartCommand的返回值设置ServiceRecord的stopIfKilled标志,该标志指示进程异常退出时(内存不足被杀死)是否重启service
上面步骤是首次启动service的流程,如果service已经启动了,则直接从10跳转到31。
由上面可以大概总结一下,启动的service会创建ServiceRecord暂时放到pendingServices中,当进程创建后再拿出来运行。并且对同一个service多次startService,每一次都会打包一个StartItem到ServiceRecord的pendingStarts中,处理后就从pendingStarts移到deliveredStarts中,那么deliveredStarts中的StartItem什么时候会清除呢?
(1)service重启的时候
(2)stopService的时候
(3)当onStartCommand的返回值是START_STICKY时,调用onStartCommand后就会清除deliveredStarts。如果返回值是START_REDELIVER_INTENT,则不会清除。这个是否清除会影响后面进程异常关闭时,service重启传递进来的intent是否为空。这里说的onStartCommand的返回值,到底怎么影响service的重启情况?
四、onStartCommand的返回值
onStartCommand(Intent intent, int flags, int startId)
START_STICKY,进程异常终止时(内存不足被kill掉),服务重启,传进intent为空
START_NOT_STICKY,进程异常终止时,服务不会重启
START_REDELIVER_INTENT,进程异常终止时,服务重启,传进最后一次的intent(这个intent存放在上述的deliveredStarts中,正由于返回值是START_REDELIVER_INTENT,没有清除deliveredStarts)
注意:
这里的服务重启,并不是创建新的service实例,而是沿用原来的实例,重新跑一次生命周期,但进程是新创建的进程。
这里的服务重启,只针对内存不足被系统kill掉再重启,如果用户主动kill掉,服务不会重启
当返回值是START_STICKY时
这里可以模拟进程异常退出时service重启:adb kill pid或者DDMS上面stop进程
结论:进程异常退出时,进程里面的所有服务都会发出重启的消息,但是对于返回值是START_NOT_STICKY的服务,会再发出移除重启消息的消息,所以服务才不会重启
上图说明了进程异常关闭时服务是否重启的区别,然后START_STICKY和START_REDELIVER_INTENT服务都会重启,唯一的区别就是调用onStartCommand时传入的intent是否为空,那么这又是怎么运作的呢?
当返回值是START_STICKY时
当返回值是START_REDELIVER_INTENT时
在启动流程图的47步可以知道,当返回值是START_STICKY时,调用onStartCommand后,会在serviceDoneExecutingLocked中清除deliveredStarts,而返回值是START_REDELIVER_INTENT时,不会清除deliveredStarts,正由于没有清除,在服务重启的过程中便会从deliveredStarts中取出startItem,放入到pendingStarts的最前面,以便后面用来启动。
五、stopService流程
通过调用stopService或者stopSelf可以停止服务,无论startService多少次,只需要stopService一次就可以停止服务。当然,如果服务是通过bindService启动的,那么只能通过unbindService停止,stopService就不能停止服务了。
stopService流程图
stopSelf流程图
六、bindService
bindService基本使用流程
1、AndroidManifest.xml声明服务
2、创建服务,继承service类,实现onCreate、onBind、onStartCommand、onDestroy方法。onBind方法中需要返回具体的Binder对象
3、客户端自定义ServiceConnection,实现onServiceConnected方法,获取service端的binder对象,方便使用该对象进行客户端和service的通信。
4、bindService绑定服务,unbindService解绑服务
第2步中,onBind方法需要返回具体的binder对象,根据这个binder对象的定义方式的不同,分为三种:扩展Binder类、使用Messenger、使用AIDL
1、返回扩展Binder类对象的bindService
如果服务供自有应用使用,并且在与客户端相同的进程中运行,这个服务只服务于自身应用的后台,则优先采用这种方法。这种方法也是常有的方法,不采用这种方法的唯一理由是,服务需要用于其他应用或不同的进程。
Service端代码,onBind中返回ServiceBinder,该类扩展于Binder类,类中的接口可以返回service的实例
client端代码,在onServiceConnected中获取service端传递过来的binder,通过该binder对象可以获取service的实例对象,通过这个对象可以调用service的公开方法,这样就实现了和service的交互。
bindService的流程
unbindService流程
bindService时会回调onServiceConnected,为何unbindService的流程中没有onServiceDisconnected呢?这是因为onServiceDisconnected是在进程异常关闭时调用的,如下图
bindService启动的service的生命周期跟随client端,client端销毁,service就销毁,这是怎么实现的呢?在client端(如activity)销毁后(参考Android Activity启动流程中的finishActivity流程图),会紧跟着调用unbindService,流程图如下
扩展Binder类的bindService,要求service和client处于同一个进程,如果不同进程会如何呢?把arg1强制转换为ServiceBinder,会出现FC
E/AndroidRuntime(3648): FATAL EXCEPTION: main
E/AndroidRuntime(3648): Process: com.example.testservice, PID: 3648
E/AndroidRuntime(3648): java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.example.testservice.MusicService$ServiceBinder
E/AndroidRuntime(3648): at com.example.testservice.MainActivity$1.onServiceConnected(MainActivity.java:30)
E/AndroidRuntime(3648): at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1339)
E/AndroidRuntime(3648): at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1356)
E/AndroidRuntime(3648): at android.os.Handler.handleCallback(Handler.java:739)
E/AndroidRuntime(3648): at android.os.Handler.dispatchMessage(Handler.java:95)
E/AndroidRuntime(3648): at android.os.Looper.loop(Looper.java:148)
E/AndroidRuntime(3648): at android.app.ActivityThread.main(ActivityThread.java:7325)
E/AndroidRuntime(3648): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(3648): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
E/AndroidRuntime(3648): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
W/ActivityManager(9108): Force finishing activity com.example.testservice/.MainActivity
2、返回messenger的binder类对象的bindService
为其他进程提供服务,则可使用Messenger为该服务提供接口。Messenger是基于消息的进程间通信的方式。什么是基于消息的进程间通信方式?要了解这个机制,先要清楚Android的消息处理机制。
Android消息处理机制(简单)
对每个进程来说,都有一个looper(线程的looper除外)和一个消息池,looper可处理无限个消息,消息池可保存50个可用的空消息对象,通过new Message()或者Message.obtain()的方式创建消息,通过handler.sendMessage()或者handler.post()的方式发送消息,具体解释参考上图注释。
结论:哪个looper创建的handler,就在哪个looper处理消息。这是理解基于消息的进程间通信方式的关键。比如在进程1创建handler,在进程2使用该handler发送消息,最终是在进程1的looper中处理消息的。因为在进程1创建handler的时候,已经在handler构造函数内部指定了该handler的looper和MessageQueue,后面无论在哪里使用该handler,都是将消息发送到该Looper和MessageQueue(参考Handler.java源码)
android:directBootAware=[“true” | “false”] ,Android7.0新特性,用户解锁设备前是否可以运行
android:enabled=[“true” | “false”]是否能被系统实例。如果为FALSE,在启动过程的retrieveServiceLocked中,获取的ServiceInfo为空,直接返回
android:exported=[“true” | “false”]其他应用的组件能否start或者bind该service。在service启动过程的retrieveServiceLocked中,会检查启动者的权限,如果和service不是同一个应用(包名),并且exported属性是FALSE,则返回PERMISSION_DENIED,最后抛出异常java.lang.SecurityException: Not allowed to bind to serviceIntent
android:icon=“drawable resource”
android:isolatedProcess=[“true” | “false”]service将运行在一个单独的进程,进程中只有一个service。在service启动过程的bringUpServiceLocked中,如果isolatedProcess为true,则会新创建一个进程,如果指定了android:process属性,则新创建的进程名为process指定的名字,否则,名字和包名相同,这会导致存在两个名字相同的进程。
android:label=“string resource”
android:name=“string”必须要指定的属性,service的名字
android:permission=“string“需要的权限,使用该服务的应用需要在manifest中使用
. . .