Android Service详解

一、service是什么?

二、如何使用service

三、startService的启动流程

四、onStartCommand的返回值

五、stopService流程

六、bindService的启动和结束流程

七、后台service和前台service

八、IntentService

九、service拓展


一、service是什么

service是一个可以在后台执行、长时间运行而不提供用户界面的应用组件,也可为其他应用提供一些功能接口。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。所谓的后台是相对前台而言的,具体就是说不依赖于用户界面。

service不是进程,不是线程。但service运行于所在进程的主线程,它有独立的生命周期。


二、如何使用service

实现一个音乐播放功能

Android Service详解_第1张图片

如果在activity里面实现,程序退出后,音乐播放也停止了,不能后台播放。

service就可以满足这种情况,service有两种启动方式,startService、bindService。startService启动服务后,service和client端就完全没联系了,bindService就是这种情况的补充,通过bindService可实现client和service的交互。


下图是service的基本代码

Android Service详解_第2张图片


启动service后,通过下面的代码可以查看系统已经启动的service

从Log也可以看出,的确生成了新的service,如下图

Android Service详解_第3张图片

启动后抓取dumpstate log也生成了一个service的记录,如下图

Android Service详解_第4张图片


下面简述一下service的使用步骤

1、AndroidManifest.xml声明服务

description=“string resource描述service的信息
         android:
directBootAware=[“true”|“false”]Android7.0新特性,用户解锁设备前是否可以运行
         android:
enabled=[“true”|“false”]是否能被系统实例
         android:
exported=[“true”|“false”]其他应用的组件能否start或者bindservice
         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启动流程

Android Service详解_第5张图片


startService流程的进程示意图

Android Service详解_第6张图片


AMS中service的相关类

1. /android/frameworks/base/core/java/android/app(Android6.0)

ActivityManagerNative:客户端与AMS进行通信的binder调用

ActivityManagerProxy:AMS代理,供客户端调用


ActivityThread:应用程序主线程


ApplicationThreadNative:AMS与客户端进行通信的binder调用

ApplicationThreadProxy:AMS调用客户端的代理

上面这些类的关系如下图

Android Service详解_第7张图片


2. /android/frameworks/base/service/core/java/com/android/server/am(Android 6.0)

ActivityManagerService:四大组件管理的核心类,同时管理和调度用户进程

ServiceRecord:在AMS中用来保存一个service的信息

ActiveServices:管理ServiceRecord的核心类

StarItem:ServiceRecord里面的内部类,代表一次startService的动作


这些类的大概关系如下:

Android Service详解_第8张图片


启动流程简要说明

第一步: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 pendingStarts  
ArrayList deliveredStarts  
ArrayMap bindings  
ArrayMap> connections  


一个startItem表示一次startService的动作,每startService一次,只生成一个ServiceRecord实例,但会生成多个StartItem,并且每次StartItem.id自增1

Class Field
StartItem ServiceRecord sr
boolean taskRemoved
int id
Intent intent
ArrayList services
……


第三步: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进程

Android Service详解_第9张图片

结论:进程异常退出时,进程里面的所有服务都会发出重启的消息,但是对于返回值是START_NOT_STICKY的服务,会再发出移除重启消息的消息,所以服务才不会重启


上图说明了进程异常关闭时服务是否重启的区别,然后START_STICKY和START_REDELIVER_INTENT服务都会重启,唯一的区别就是调用onStartCommand时传入的intent是否为空,那么这又是怎么运作的呢?


当返回值是START_STICKY时

Android Service详解_第10张图片


当返回值是START_REDELIVER_INTENT时

在启动流程图的47步可以知道,当返回值是START_STICKY时,调用onStartCommand后,会在serviceDoneExecutingLocked中清除deliveredStarts,而返回值是START_REDELIVER_INTENT时,不会清除deliveredStarts,正由于没有清除,在服务重启的过程中便会从deliveredStarts中取出startItem,放入到pendingStarts的最前面,以便后面用来启动。

Android Service详解_第11张图片


五、stopService流程

通过调用stopService或者stopSelf可以停止服务,无论startService多少次,只需要stopService一次就可以停止服务。当然,如果服务是通过bindService启动的,那么只能通过unbindService停止,stopService就不能停止服务了。

stopService流程图

Android Service详解_第12张图片


stopSelf流程图

Android Service详解_第13张图片


六、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的实例

Android Service详解_第14张图片


client端代码,在onServiceConnected中获取service端传递过来的binder,通过该binder对象可以获取service的实例对象,通过这个对象可以调用service的公开方法,这样就实现了和service的交互。

Android Service详解_第15张图片


bindService的流程

Android Service详解_第16张图片


unbindService流程

Android Service详解_第17张图片


bindService时会回调onServiceConnected,为何unbindService的流程中没有onServiceDisconnected呢?这是因为onServiceDisconnected是在进程异常关闭时调用的,如下图

Android Service详解_第18张图片


bindService启动的service的生命周期跟随client端,client端销毁,service就销毁,这是怎么实现的呢?在client端(如activity)销毁后(参考Android Activity启动流程中的finishActivity流程图),会紧跟着调用unbindService,流程图如下

Android Service详解_第19张图片


扩展Binder类的bindService,要求service和client处于同一个进程,如果不同进程会如何呢?把arg1强制转换为ServiceBinder,会出现FC

Android Service详解_第20张图片

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消息处理机制(简单)

Android Service详解_第21张图片


对每个进程来说,都有一个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源码)


基于Messenger消息机制的进程间通信模型
Android Service详解_第22张图片
(1)service端创建handler,client端创建handler
(2)service端通过client端的handler发送消息
(3)client端通过service端的handler发送消息
那么问题就来了, service端如何获取client端的handler?client端如何获取service端的handler?Messenger就是解决这个问题的。下面是Messenger的基本使用

service端代码,根据handler创建messenger,onBind中返回messenger的binder对象
Android Service详解_第23张图片

client端代码,在onServiceConnected的时候根据新建messenger,通过messenger发送消息给service,消息中的replyTo包含client端的messenger,以便service处理这个消息时根据这个replyTo发送消息到client端。
Android Service详解_第24张图片
这样就实现了跨进程的通信,messenger的源码主要涉及binder通信,这里不作分析。

3、返回AIDL binder对象的bindService
整个流程与messenger的方式相差不大,较少使用,也主要涉及binder通信,不作分析。

关于service在AndroidManifest.xml的属性
了解了整个流程后,现在回过头看service在AndroidManifest中的一些属性是如何影响service的运行的?

android:description=“string resource 描述service的信息
         
android:directBootAware=[“true” | “false”]Android7.0新特性,用户解锁设备前是否可以运行
         
android:enabled=[“true” | “false”]是否能被系统实例。如果为FALSE,在启动过程的retrieveServiceLocked中,获取的ServiceInfo为空,直接返回
         
android:exported=[“true” | “false”]其他应用的组件能否start或者bindserviceservice启动过程的retrieveServiceLocked中,会检查启动者的权限,如果和service不是同一个应用(包名),并且exported属性是FALSE,则返回PERMISSION_DENIED,最后抛出异常java.lang.SecurityException: Not allowed to bind to serviceIntent
         
android:icon=“drawable resource
         
android:isolatedProcess=[“true” | “false”]service将运行在一个单独的进程,进程中只有一个serviceservice启动过程的bringUpServiceLocked中,如果isolatedProcesstrue,则会新创建一个进程,如果指定了android:process属性,则新创建的进程名为process指定的名字,否则,名字和包名相同,这会导致存在两个名字相同的进程。
         
android:label=“string resource
         
android:name=“string必须要指定的属性,service的名字
         
android:permission=“string需要的权限,使用该服务的应用需要在manifest中使用
         
android:process=”string”>所在进程名
    . . .


七、前台service
service默认为后台service,前台service的本质是调整service所在进程的优先级,进程的优先级管理将会在后续的文章分享。
一般如下这样使用,在onStartCommand中调用startForeground,onDestroy中调用stopForeground
Android Service详解_第25张图片

在Android 8.0中,引入了一种全新的方式启动前台service,NotificationManager.startServiceInForeground(),调用此方法等同于调用startService()在后台创建一个服务,然后立即调用该服务的startForeground()将其推到前台。

八、IntentService
IntentService继承了service,是特殊的service,内部使用了HandlerThread,使用单独的looper,用户无需新建线程去完成耗时工作,只需要实现onHandleIntent方法,在该方法里面完成需要的工作,无需主动停止service,onHandleIntent完成后便自动停止service。
只能通过startService启动IntentService,bindService启动无效。
大多数服务不需要同时处理多个请求,继承IntentService是比较好的选择。

九、service拓展
(1)没有startService,直接调用stopService,不会产生任何异常,stopService如果发现service不在运行,则直接返回。
(2)service中启动activity必须制定FLAG_ACTIVITY_NEW_TASK标志。service中启动service与activity中启动service无异
(3)service与activity的交互。①bindService。②service发送广播,activity处理广播。③使用文件共享,如sharePreference。……
(4)broadcast中不能bindService,因为一般broadcast的生命周期比较短,service的生命周期比较长。
(5)service如何不被kill掉?部分来源于网络(未验证)
①onStartCommand返回START_STICKY或者START_REDELIVER_INTENT,在内存不足杀死进程时可以重启服务
②使用前台service,前台service具有更高的优先级,不会轻易被kill掉
③在onDestroy方法里面重启service。onDestroy时,发送广播,收到广播后,启动service。或者直接在onDestroy中startService。但有些路径并不会调用onDestroy,如强制停止、通过安全软件kill掉都不会调用onDestroy
④在FW中加入白名单,这种一般是大厂app和手机厂商合作的
⑤双service守护
两个service相互定时监视另一个service,如果一个service被kill掉,另一个马上启动被kill掉的service
⑥双进程守护
使用NDK开发,利用Linux的进程监视。

你可能感兴趣的:(Android Service详解)