安卓基础 - 关于Service

1. 什么是Service,Service是否在主线程中执行?

1
2
3
4
5
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >
</service>

​ Service是Android中经常使用的四大组件之一,它是Android中实现程序后台执行的解决方式,非常适用于执行那些不需要和用户交互并且还要长期执行的任务。

​ 这里的后台并非子线程的意思。Service默认执行在UI线程中,因此,不要在Service中执行耗时的操作。如有必要,需要在Service中创建子线程来完成耗时操作。

​ Service的执行不依赖于用户界面,即使应用被切换到后台或者用户打开了另外一个应用程序,Service仍然能够保持正常执行,这也是Service的使用场景。当某个应用程序进程被杀掉时,全部依赖于该进程的Service也会停止运行。

​ Service的优先级高于后台挂起的Activity,当然也高于Activity所创建的Thread,因此,系统可能在内存不足时优先杀死后台的Activity或者Thread,而不会轻易杀死Service组件,即使被迫杀死Service,也会在资源可用是重启被杀死的Service。

2. Service与Thread的区别,什么场景应该使用Service

Thread是程序执行的最小单元,可以用Thread执行一些异步任务。

Servie是系统的组件,它由系统进程托管(ServiceManager);它们之间的通信类似于client和server,是一种轻量级的IPC通信,这种通信的载体是Binder,它是在linux层交换信息的一种IPC。而Thread是由本应用程序托管。

thread与activity不存在依赖关系,即activity被销毁,在activity中启动的thread有可能还在继续运行,鉴于activity已经销毁,则肯定也失去了对thread的控制权,并且在一个activity中创建的子线程,另一个activity无法对其进行操作。但是Service就不同了,所有的activity都可以与service进行关联,然后可以很方便的操作其中的方法,即使activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中的Binder实例。因此使用Service来处理后台任务,activity就可以放心的finish,完全不需要担心无法对后台任务进行控制的情况。

其实Service和Thread根本不是一个级别的东西,Service是系统四大组件之一,Thread只是一个用来执行后台任务的工具类,它可以在Activity中被创建,也可以在Service中被创建。因此我们不应该讨论该使用Service还是Thread,而应该讨论在什么地方创建Thread。

典型的应用中,它可以在以下三个位置被创建,不同的位置,其生命周期也不一样,所以,我们应该根据该Thread的目标生命周期来决定是在Service中创建Thread还是在Activity中创建它。

  1. 在Activity中被创建。这种情况下,一般在onCreate时创建,在onDestroy中销毁,否则Activity销毁后,Thread会依然在后台运行。这种情况下,Thread的生命周期为整个Activity的生命周期。所以,在Activity中创建的Thread只适合完成一些依赖Activity本身有关的任务,比如定时更新一下Activity的控件状态等。

    核心特点:该Thread就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和事件,Activity销毁后,该Thread也就没有存活的意义了。

  2. 在Application中被创建。这种情况一般在自定义的Application类中,onCreate方法,在其中创建Thread,当然,也需要在onTerminate方法中销毁Thread,否则,如果Thread没有退出的话,即使整个Application退出了,线程依然会在后台运行。这种情况下,Thread的生命周期即为整个Application的生命周期。所以,在Application中创建的Thread,可以执行一些整个应用级别的任务,比如定时检查一下网络连接状态等。

    核心特点:该Thread的终极目标是为整个APP各个Activity服务的,包括完成某个Activity交代的任务,主动通知某个Activity一些消息和事件等,APP退出之后该Thread也就没有存活的意义了。

    以上两种情况,Thread的生命周期都不应该超出整个应用程序的生命周期,也就是说,整个APP退出之后,Thread都应该完全退出,这样才不会出现内存泄漏或者僵尸线程。那么如果你希望整个APP退出之后依然能够运行该Thread,那么就应该把Thread放到Service中去创建和启动了。

  3. 在Service中被创建。这是保证最长生命周期的Thread的唯一方式,只要整个Service不退出,Thread就可以一直在后台执行,一般在Service的onCreate中创建,在onDestroy中销毁。所以在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比如在Service中保持与服务端的长连接。

3. Service生命周期

​ Service有绑定和非绑定模式,以及这两种模式的混合使用。不同的使用方法生命周期也不同。

非绑定模式:当第一次调用startService的时候,执行的方法依次为onCreate() onStartCommand(),当Service关闭时候掉用onDestroy()

绑定模式:第一次调用bindService()的时候,执行的方法为onCreate() onBind() ,解除绑定(掉用unBindService())的时候会执行onUnbind() onDestroy()方法。

上面的两种生命周期是在相对单纯的模式下的情形。在开发过程中必须注意Service的实例只会有一个,也就是说如果当前要启动的Service已经存在了那么就不会再次创建该Service当然也不会再次掉用onCreate()方法。

客户端通过bindService()方法绑定服务端。当调用这个方法时,必须要提供一个ServiceConnection,用它来建立和服务端的绑定。虽然bindService()方法没有返回值,但是当Android系统建立服务端和客户端的连接时,会调用ServiceConnection对象的onServiceConnected方法来传递一个IBinder到客户端,用于和服务端通信。服务异常终止时系统会回调这个方法onServiceDisconnected()。当客户端取消绑定时,系统“绝对不会”调用该方法。

一个Service 可以被多个客户端绑定,但是Android系统只会在第一个客户端绑定服务端时,调用service的onBind()方法去获取IBinder对象。之后系统会传递这个IBinder到绑定服务端的其他客户端(这时是不会再次调用onBind()方法的)。如果服务不是通过startService()方法启动的,那么当最后一个客户端和服务端解绑时,系统就会摧毁掉这个服务。

Service的生命周期图如下所示:

4. Service客户端与服务端进行交互的编程接口定义方式

  1. 扩展Binder类

    ​ 当我们的服务属于本应用的一部分,和应用运行在同一个进程中时,我们采用这种方式来创建这个交互的接口,继承Binder类,并在onBind()返回一个它的实例对象。这样客户端就可以接收到这个IBinder对象,并用它直接访问Binder实现类或者Service中的公有方法。我们往往在服务仅仅作为应用的一部分运行在后台时采用这种方式。但是如果我们需要服务能被其他应用或者其他线程访问时,就不能采用这样的方法了。

  2. 使用Messenger

    ​ 当我们需要跨进程通信(IPC)时采用这种方式,这种方式需要在Service中定义一个Handler,用这个Handler去响应不同的Message。这样的一个Handler是基于Messenger的,可以通过IBinder传递到客户端,客户端可以利用这个Messenger向服务端发送命令。同样的客户端也可以定义一个Messenger,传递给服务端让其可以发送消息回客户端。这是一种最便捷的跨进程通讯方式,使用Messenger实现时,所有的请求都被放在单一的线程中,这样服务就是线程安全的。Messenger内部实现采用的是AIDL,因此传递的消息对象必须是可序列化的。

  3. 使用AIDL

    ​ AIDL(Android Interface Definition Language 安卓接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些源于并将它们编组到各进程中,以执行IPC。上述采用的Messenger方法实际是以AIDL作为其底层结构。如上所述,Messenger会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果你想让服务同时处理多个请求,则可直接使用AIDL。在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。如需直接使用AIDL,需要创建一个定义变成接口的.aidl文件。Android SDK工具利用该文件生成一个实现接口并处理IPC的抽象类,随后可在服务内对其进行扩展。注:大多数应用都不会使用AIDL来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL并不适合大多数应用。

5. 什么是IntentService,典型使用场景?

IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程(Worker Thread)来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们手动去控制。另外,可以启动多次IntentService,而每一个耗时操作会以工作队列的方式在IntentService和onHandlerIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。

还有一种说明是:所有请求都在一个单独的工作线程中,不会阻塞应用程序的主线程(UI Thread),同一时间之处理一个请求。使用IntentService省去了我们在Service中手动开线程的麻烦,并且当操作完成时,无需手动停止Service。

​ 典型使用场景:比如一项任务分成几个子任务,子任务按照顺序先后执行,子任务全部执行完后,这项任务才算成功。那么,利用几个子线程顺序执行时可以达到这个目的,但是每个线程必须去手动控制,而且得在一个子线程执行完后,再开启另一个子线程。或者,全部放到一个线程中让其顺序执行,这样都可以做到。但是如果这是一个后台任务,就得放到Service里面,由于Service和Activity是同级的,所以,要执行耗时任务,就得在Service里开子线程来执行,此时最合适的方式就是使用IntentService。

2019-2021 Lison Liou