明天又要出差一天。。。
今天整理的这篇文章大部分内容来自Google的官方指南。官方文档比市面上大部分文章都要齐全,当然也更权威。但是就不如那些文章那么浅显易懂了,比如郭霖,鸿洋的文章等。大家结合着看吧。
以下内容基于7.0系统
简介
服务是一个在后台进行耗时操作的应用程序组件,它并不提供UI。服务可由另一个应用程序组件启动,并且在后台持续运行,即使用户切换至其他的应用程序。应用程序组件可以与服务进行绑定以便与之交互,甚至进行IPC。
服务分为三种类型:
Scheduled(定时服务)
使用JobScheduler(5.0及以上)注册Job,指明对网络和时间的要求,系统将会自行执行这些任务。
Started(启动的服务)
一旦一个服务通过startService()启动后,这个服务便可以不停地运行下去,即使启动它的组件被摧毁。started service一般用来执行单一,且不需要给调用者返回结果的操作。如下载或上传一个文件到云。当这种Service的任务完成后,需要停止该服务。
Bound(绑定的服务)
一旦一个服务通过bindService()与一个应用程序组件绑定后,这个组件便可以与之交互,例如发送请求,接收结果甚至IPC。bound service的生命周期取决于它所绑定的组件的生命周期。多个组件可同时与一个服务绑定,一旦它们全部解绑,服务将会被系统回收。
启动的服务和绑定的服务看起来好像不相关,实际上一个服务可以既是started也是bound的。
任何一个组件,甚至外部程序的组件都可以通过一个Intent来使用一个服务,就如同通过Intent启动一个activity一样。但如果你不想你的服务被外部程序所用,你可以在Manifest文件中将其声明为private。
使用服务还是后台线程?
明白这个问题首先应该清楚Service和Thread的定位。
首先Service是系统组件,Thread是程序执行的最小单位。就大部分场景而言,我们要讨论的问题实际上是在Activity中创建Thread呢还是在Activity中创建Service然后再去Service中创建Thread。
对于前者,在Activity中创建Thread时,将不得不在onDestroy()方法中关闭线程,否则当Activity销毁后Thread还在后台跑,会出现内存泄露和僵尸线程。因此,Activity中Thread的生命周期是和Activity绑定的,也就是说当Activity退出后Thread中的工作也无法执行了。而且Thread无法和Activity进行方便的交互,同时该Thread无法被其他的组件调用。
而后者则不会出现这种情况。首先,started service有自己的生命周期,即使Activity被销毁,也不影响Service的运行,实际上Service的优先级要高于后台挂起的Activity。而bound service虽然生命周期与绑定的组件有关,但是其提供了方便交互的接口。这都是Service优于在Activity中直接创建Thread的地方。
因此,使用服务还是使用线程这个问题的答案取决于具体的场景需求。
这一个小节官方解释的也不是特别清楚,我加入了较多自己的见解,参考了这篇文章,感谢原作者。
经过demo试验,发现如果不再onDestroy中关闭线程,那么即使Activity被销毁,只要其进程还没来得及被系统销毁,那么线程中的任务还是会得到执行。但这无疑是不安全且可能造成内存泄露的。
几个重要方法和服务须知
onStartCommand()
如果是通过onStart()方法启动的服务,服务在创建之后会调用该方法。started service在完成工作之后需要调用stopService()或者stopSelf()来停止。注意,不是在服务的onDestroy()方法中调用停止服务,而是在onStartCommand()方法中的逻辑完成之后,返回之前。
如果是单绑定的服务,则不需要实现该方法。因为反正不会调用。
onBind()
通过onBind()方法绑定的服务会调用该方法。为了使服务和与之绑定的组件之间进行交互,必须要在onBind()方法中return一个IBinder对象。
如果不需要绑定服务,return null就行。
onCreate()
系统执行该方法来进行服务的一次性初始化工作。一次性的意思是:如果一个服务已经在running了,那么这个方法不会再被调用。
onDestroy()
当一个服务被回收的时候会调用该方法,在该方法中应该进行一些资源回收工作,比如线程,监听,广播接收器等。
当系统内存很低并且需要为当前获得用户焦点的活动腾资源时,系统会强制停止一个服务。但如果该服务是和获得用户焦点的Activity相绑定的,那它被杀掉的可能性会低很多。如果该服务是一个前台服务,那基本上不太可能被杀掉。当服务started之后,并且进行长时间的耗时操作时,系统会随着时间的推移降低其在后台任务中的优先级,那么服务就极有可能被干掉,因此如果启动了一个started服务,必须想好如何让它优雅地面对系统对其的重启。因为当系统干掉一个服务后,它会在资源变得不那么紧张时重启该服务,当然重启的模式也取决于onStartCommand()方法的返回值。
注意:只有started service才有重启这一说,bound service的生命取决于其绑定的组件。
在Manifest中声明一个服务
所有的服务都必须在Manifest文件中声明。(好像广播就不是,静态注册的需要,动态注册的广播不需要在Manifest中声明)
<manifest ... >
...
<application ... >
<!--service标签有很多属性,具体的去看文档-->
<service android:name=".ExampleService" />
...
</application>
</manifest>
不要使用隐式Intent,而要使用显式Intent来启动服务,并且不要给服务指定标签。之所以不用隐式Intent启动服务是因为你不知道除了你想要启动的服务,还有没有别的服务也能响应这个隐式Intent,而服务又是没有UI的,用户根本不知道是否启用了对应的服务,还是意外启动了别的服务。API21以上,在bingService()中传入一个隐式Intent会抛出异常。
如果不希望本服务被其他程序所启动,将其exported属性置false即可。
虽然服务并不提供UI,但用户是可以看到有哪些服务在运行的,最好在服务的description属性对该服务进行一些描述,以免被用户视为可疑服务而将其杀掉。
创建一个started service
所谓started service即通过startService()方法启动的服务,通过该方式启动的服务在完成onCreate()方法后会立即调用onStartCommand()方法。
当一个服务被启动后,它就有了独立的生命周期(不依赖与启动它的组件)。启动服务是在startService()方法中可以传入一个Intent参数用来携带数据,服务会在onStartCommand()方法中收到该Intent。当服务完成工作后,应当停止自身,然后等待系统回收。
注意:服务运行在其声明的程序所在的进程,并且默认运行在主线程中。因此如果服务中进行的是密集型操作或可能阻塞的操作,则有可能影响UI的响应。这时候最好开一个新线程。访问网络或进行IO读写更不用说了,在主线程中出现这类代码直接抛异常。
通常来讲,有两种方法来实现一个started service
继承IntentService
大部分的服务不需要能够同时处理多个请求,因此使用IntentService会比较简单。
IntentService会:
- 自动创建子线程,在onHandlIntent()方法中处理传到onStartCommand()中的Intent。
- 创建一个队列来保存传入的Intent,每次只派发一个Intent对象到onHandleIntent()方法中,即每次只运行一个任务,没有多线程问题。
- 在所有的Intent处理完之后自动停止服务。
- 对onBind()方法进行了默认实现:return null。因为不需要绑定嘛。
- 对onStartCommand()方法进行了默认实现:将Intent传到一个工作队列中,然后一个个传给onHandleIntent()方法。
例:
public class HelloIntentService extends IntentService { /** * A constructor is required, and must call the super IntentService(String) * constructor with a name for the worker thread. */ public HelloIntentService() { super("HelloIntentService"); } /** * The IntentService calls this method from the default worker thread with * the intent that started the service. When this method returns, IntentService * stops the service, as appropriate. */ @Override protected void onHandleIntent(Intent intent) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. try { Thread.sleep(5000); } catch (InterruptedException e) { // Restore interrupt status. Thread.currentThread().interrupt(); } } }
就是这么简单,只需要提供一个构造方法,然后复写onHandleIntent()来执行业务逻辑即可。IntentService其实就是Service的一个子类,如果你要重写Service的其他方法,一定要super一下父类的该方法,不然IntentService中的一些代码就被冲掉了。
继承Service类
这种方式比较复杂,代码量也更多,但是如果需要同时handle多个start request,即同时handle多个Intent的话,则只能选择该方式。
下面这段代码的和上面的功能一样,使用Handler强行实现了一个队列。按照继承Service来创建服务的场景来看,应该在onStartCommand()方法中针对每个Intent都开一个线程,才能实现同时处理多个start requests。
public class HelloService extends Service { private Looper mServiceLooper; private ServiceHandler mServiceHandler; // Handler that receives messages from the thread private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. try { Thread.sleep(5000); } catch (InterruptedException e) { // Restore interrupt status. Thread.currentThread().interrupt(); } // Stop the service using the startId, so that we don't stop // the service in the middle of handling another job stopSelf(msg.arg1); } } @Override public void onCreate() { // Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. We also make it // background priority so CPU-intensive work will not disrupt our UI. HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // Get the HandlerThread's Looper and use it for our Handler mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); // For each start request, send a message to start a job and deliver the // start ID so we know which request we're stopping when we finish the job Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId;//在这里将startId传过去,原因下面的部分有介绍。 mServiceHandler.sendMessage(msg); // If we get killed, after returning from here, restart return START_STICKY; } @Override public IBinder onBind(Intent intent) { // We don't provide binding, so return null return null; } @Override public void onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); } }
我个人认为上面这段代码纯粹为了展现IntentService的优越性而刻意在Service中实现了一个消息队列(使用Handler的机制)。直接继承Service的优势在于能够创建多个线程同时处理requests。
started service的重启
如果查看过API的话,会发现onStartCommand()方法有一个int类型的返回值,这个返回值决定了started service被系统干掉后将以什么样的方式重启。该返回值必须是以下三种的其中之一:
START_NOT_STICKY
如果系统干掉了该服务,在资源足够的情况下不会立即重启它,除非有一个Intent被传过来了。这种方式可以避免服务在不需要的时候也在后台跑。
START_STICKY
如果系统干掉了该服务,那么在资源可用时重启它,并且调用onStartCommand()方法,但是不要把上一个Intent传来了。实际上,系统会在调用onStartCommand()的时候传一个null进去。这种模式适合媒体播放类的服务,这类服务在一直在后台运行,并且等着分配工作。相比上一种模式,这种模式可以减去等待服务启动的时间,响应上会更快。
START_REDELIVER_INTENT
如果系统干掉了该服务,那么在资源可用时重启它,并且调用onStartCommand()方法,并且把上一个Intent传进来。其他Intent则依次等候或者传进来按照相应逻辑处理。这种模式比较适合下载类服务。当一个后台下载服务由于长期运行被系统干掉的时候,它可能还没下载完,那么当其重启的时候,应当能自动开始之前的下载。这个时候接收之前的Intent,并对下载进度进行判断之后就可以实现断点续传了。
开启一个服务
上面说了这么多,都是在创建一个started service,那如何开启它呢?
//上面说了,不要用隐式Intent
Intent intent = new Intent(this, HelloService.class);
startService(intent);
started service和开启它的组件之间的唯一纽带就是startService()传g进去的Intent参数。如果想让服务在完成工作后返回一个结果,那么可以在Intent中封装一个PendingIntent,那么当服务完成工作后可以使用这个PendingIntent对象创建一个广播来传递结果。
PendingIntent与广播???
对同一个服务的多次启动请求会调用多次onStartCommand()方法,但是停止该服务只需要调用一次stop方法。
停止一个服务
started service需要自己管理生命周期,系统不会去管它的生命周期,只会在它stopSelf()或者被其他组件stopService()之后去销毁它。
如果服务需要处理多个request,那什么时候调用stopSelf()才是合适的呢?因为总有可能在处理完一个request之后准备停止服务的时候,服务中还在handle另一个request,这时候停止服务会漏掉request。这个时候可以在onStop(int)方法中传入onStartCommand(Intent,int,int)方法参数中的第三个参数:startId,这个参数我没看到在哪里可以指定,应该是系统分配的。这样就能保证一定会在最后一个request被处理完之后才真正调用stopSelf()。
创建一个绑定服务
绑定服务比较复杂,另开章节介绍
给用户发通知
- 使用Toast
- 使用Status Bar
不多做介绍
前台服务
前台服务是一种告诉用户『我』在运行的服务,这种服务几乎不太可能被系统干掉。但同时这个服务也必须在状态栏显示一个图标并且显示一条通知。除非该前台服务被停止,否则该通知不会消失。
使用范例:
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new Notification.Builder(this)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
注意:startForeground()方法传入的id不能为0。
要移除这个通知,使用stopForeground()方法,该方法并不会停止这个服务,只是将其从前台移除。如果直接调用stopService之类的方法,那么会既移除这个通知也会停止这个服务。
管理服务的生命周期
服务的生命周期比较简单,但是因为服务不可见,所以处理起来也要格外小心。
服务的生命周期分两路:
A started service
这种服务的生命周期从别的组件调用startService()方法那一刻开始,直到stopSelf()或者stopService()执行。
A bound service
这种服务的生命周期取决于其所绑定的组件的生命周期。一旦和任何一个组件绑定,这个服务的生命周期开始;一旦和最后一个组件解绑,这个服务的生命周期结束。
注意:
以上两条生命周期路线并不是完全分开的,因为一个started service完全可以提供绑定服务。比如可以通过startService()方法启动一个服务,那这个时候该服务是一个started service。当如果这个服务提供了绑定机制,在其已经启动的情况下,另一个组件再去绑定它,那么它又成了一个bound service。这个时候它的生命周期的结束已经不仅仅取决于stopService()或stopSelf()的调用了,还要看其上是否有组件与之绑定。如果所有的绑定组件都已解绑,这个时候stopService()或stopSelf()才能真正停止这个服务。
服务的生命周期方法
Just show me the code!
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
不同于Activity的生命周期方法,不需要在这里使用super调用父类的实现,因为父类的实现是空的。
服务的生命周期图(来自官网)
和Activity一样,Service的生命周期始于onCreate()方法终于onDestroy()方法。在onCreate()中可以进行一些初始化工作,比如开启一个线程播放音乐。在onDestroy()中进行资源回收工作,如关闭线程。
一旦一个服务调用了onStartCommand()方法或者onBind()方法,其就处于活跃状态了,
started service直到onDestroy()方法调用才退出活跃状态。也就是说即使onStartCommand()方法返回,started service也处于活跃状态。而bound service则不同,一旦bound service的onUnbind()方法return,其活跃状态便结束了,接着便等待系统回收。