1.3 Services - 服务
服务是一种应用组件,它可以在后台执行耗时的操作,它是没有用户界面的。其它的应用组件都可以开启一个服务,服务开启后,即使用户离开了应用,服务仍然可以在后台运行。此外,绑定到服务的组件可以与服务进行交互,甚至执行跨进程的通信(IPC)。例如,服务可以处理网络事务,播放音乐,执行文件I/O操作,或者和内容提供器进行交互,这一切的一切都是在后台运行的。
从本质上说,服务可以有两种形式:
started:当某个应用组件(例如activity)调用了 startService() 方法,那这个服务就是"started" 的。一旦服务被启动,它就可以立即在后台运行,即使启动服务的组件在启动服务后被销毁了。一般来说,一个启动的服务执行的是单一的操作,并且不会把结果返回给启动者。例如,基于网络的上传/下载服务。当服务执行的操作结束以后,服务应该停止自己。
bound:当某个应用组件通过调用bindService() 方法绑定了某个服务,那这个服务就是"bound"的。一个绑定的服务可以提供一个客户端的接口,应用组件可以通过该接口来和服务进行交互,比如说发送请求,获取结果,甚至做一些跨进程通信(IPC)的工作。绑定的服务与绑定服务的组件存活时间一样长。可以给多个组件绑定同一服务,只有当绑定同一服务的所有组件都与服务解绑了,服务才会被销毁。
尽管本文档里分开的讨论了服务的两种类型,但是在实际的开发中,你可以任意使用某种类型的服务。这其实是一个你是否实现一组回调方法的简单问题:bindService() 方法允许应用组件启动服务,onBind() 方法允许应用组件绑定服务。
不管你的应用是否启动或绑定了服务,任何应用组件都可以使用服务,任何组件都可以使用同样的方式来开启activity--通过使用intent来开启它。但是,你可以在清单文件里把服务申明为私有的,这样就阻止了其它应用来访问该服务。
警告:服务会运行在它宿主进程的主线程里--服务不会创建它自己的线程,也不会运行在一个单独的进程里(除非你指定其他)。这也就着,如果你的服务打算做任何占用或阻塞CPU的工作或操作(例如MP3播放或是联网操作),你应该创建一个新的线程,让服务在新的线程里做这些工作。通过使用一个单独的线程,你可以减少应用无响应的错误风险(ANR),并且应用的主线程可以用来专门和用户做交互方面的工作。
The Basics - 基本原理
你必须创建一个的 Service 子类来创建服务。在你的实现里,你需要重写一些回调方法来处理服务生命周期的一些关键点,如果需要的话,你还需要为组件提供绑定服务的机制。应该重写的重要回调方法如下:
当组件(例如activity)请求一个通过startService() 方法启动的服务时,系统会调用该方法。一旦该方法被调用,服务就会立即启动并在后台运行。如果你实现了它,那么当服务的工作完成后,你也必须通过调用stopSelf() 方法或stopService() 方法把服务停止掉。(如果你只想提供服务的绑定,那么你不需要实现该方法)
当应用组件想通过bindService() 方法绑定服务时(例如要执行RPC--远程过程调用,译者注),系统调用该方法。在你的实现里,你必须提供接口返回IBinder 来当作与服务进行交互的客户端程序。你必须得实现这些方法,但是,如果你不允许绑定时,那你可以返回null。
当服务被首次创建时,系统会调用该方法来执行只有一次的设置程序(在系统调用onStartCommand() 或是onBind() 方法之前)。如果服务已经在运行,那么该方法不会被调用。
当服务不再使用并将要被销毁时,系统调用该方法。服务应该使用它来清理任何资源,例如线程,注册的监听,接收器等等。这也是服务所能接收到的最后的调用了。
如果组件通过调用startService() 方法启动了一个服务(onStartCommand() 方法调用后的结果),那么服务会持续运行的,除非服务使用stopSelf() 方法停止自己,或者其它的组件通过调用stopService() 方法停止该服务。
如果组件通过调用bindService() 方法来创建服务(onStartCommand() 方法没有被调用),那么服务的生命和绑定它的组件的生命一样长。一旦服务从所有的组件上解绑了,系统就会销毁它的。
当系统内存很少,并且获取用户焦点的activity需要更多的系统资源时,Android系统就会强制停止某些服务。如果服务被绑定在正获得用户焦点的activity上,那么它不可能被杀死,并且如果服务被申明为run in the foreground (稍后会做讨论),那它几乎不会被杀死。否则,如果开启一个长时间运行的服务,随着时间的推移,系统会降低它在后台任务列表中的位置,并且该服务会变得更容易被杀死。如果系统杀死了你的服务,那么当资源再次可用时,它会立即重启(这也依赖于onStartCommand() 方法里返回的值,这在随后讨论)。系统什么时候会销毁一个服务的更多细节,请参见Processes and Threading 章节。
下面的内容描述了如何创建不同类型的服务,描述了应用组件如何使用服务。
Declaring a service in the mainfest - 在清单文件里申明service
和activity及其它组件一样,你必须在清单文件里申明所有的service。
为了申明服务,在<application> 元素里添加一个<service> 子元素,如:
<manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application> </manifest>
你可以在<service> 元素里定义其它的属性,例如启动服务的权限,服务应该运行在哪个进程中等等。唯一的必需的属性是android:name --它指定了服务的类名。一旦你发布了应用,你就不要去改变它,因为如果你改变了它,你可能会破坏一些功能。
关于在清单文件里申明<service> 元素的更多信息请查看<service> 章节。
与activity类似,服务也可以定义intent过滤器,这些过滤器可以让其它组件使用隐式的intent来执行服务。通过申明intent过滤器,安装在同一设备的其它应用的任何组件都有可能开启你的服务,这是因为如果在清单文件里该服务申明的intent过滤器与其它应用传递给startService() 方法的intent相匹配的话,就会发生上述情况。
如果你仅仅只是计划本地使用服务(其它应用不能使用),那你就不需要(也不应该)提供任何intent过滤器。没有intent过滤器,你必须显式的使用服务的类名来调用服务。
此外,当你设置服务的android:exported 属性值为false。那么你的服务就是私有的。即使你给服务提供了intent过滤器,那其它应用还是不能调用该服务。
为服务创建intent过滤器的更多详情请参见 Intents and Intent Filters 章节。
Creating a Started Service - 创建一个启动的服务
某个组件调用了startService() 方法,它就启动了一个服务,启动的结果会在onStartCommand() 方法里看到。
如果服务被启动了,那它就有与启动它的组件相对独立的生命周期,并且服务可以立即在后台运行起来,即使启动它的组件已经被销毁了。服务完成它的工作后应该调用stopSelf() 方法结束自己,或是其它组件通过调用stopService() 方法来关闭它。
应用组件(如activity)可以通过startService() 方法启动服务并在启动时通过传递intent参数来指定启动哪个服务,和给服务传递哪些数据。服务会在onStartCommand() 方法接收到intent。
例如,假设一个服务需要往在线数据库里保存数据,可以使用activity启动这个服务,在启动时,给startService() 方法传递一个intent,在intent里包含了要保存的数据。服务会在onStartCommand() 方法里接收到intent,然后就开始连接网络、传输数据。当事务结束后,服务停止自己,于是它就被销毁了。
警告:默认地,在应用里申明的服务会运行在主线程的某些进程中。因此,如果用户和应用里某些activity进行交互时,应用里的服务正在执行某些耗时的、阻塞的操作,那么该服务就会降低应用的性能。为了避免影响应用的性能,你应该在服务里开启一个新的线程。
一般来说,你可以继承两种类来创建一个启动的服务
它是所有服务的基类。如果你继承了这个类,那么你需要做的一个很重要的工作是开启一个新的线程来执行服务里的操作。这是因为,服务默认会使用应用的主线程,这样做会影响应用里正在运行activity的性能。
它是Service 的子类,它会在同一时间使用worker线程来处理所有的启动请求。如果你不需要服务同时处理多个请求时,这是最佳的选择。你所需要做的只是实现 onHandleIntent() 方法,它会接收到为每次启动请求传递的intent,这样你就可以处理一些后台的工作。
Extending the IntentService class - 继承IntentService类
因为大多数开启的服务都不需要同时处理多个请求(多线程是一种危险的方案),那么你的服务继承IntentService 就是最好的选择。
IntentService 会完成下述的工作:
1. 创建一个默认的worker线程,在这个线程里,可以执行所有分发给onStartCommand() 方法的所有intent,该worker线程是和应用的主线程是分离的。
2. 会创建一个intent的序列,可以保证某一时刻传递给onHandleIntent() 实现方法的intent只会有一个,因此你不必担心会出现多线程。
3. 在请求被处理完成后,完全停止服务,因此你也不需要手动的调用stopSelf() 方法。
4. 默认提供了onBind() 方法的实现,并返回null。
5. 默认提供了onStartCommand() 方法的实现,并先把请求的intent发送到队列中,然后再发送到你的onHandleIntent() 实现方法里。
事实上,你所需要做的就是实现onHandleIntent() 方法,完成服务要做的事情(虽然这样,但是,你也可以提供一个服务的构造方法)。
public class HelloIntentService extends IntentService { /** * 必须的构造方法,方法里必须调用父类的构造方法 IntentService(String),并传递一个worker线程的名称 */ public HelloIntentService() { super("HelloIntentService"); } /** * 在默认的workers线程里,IntentService 调用该方法,并把启动服务时传递的intent传递进来。 * 当方法返回时,IntentService会在适当的时候停止服务 */ @Override protected void onHandleIntent(Intent intent) { // 通常,我们需要做一些工作,例如下载文件。 // 在示例中,我们只是让线程沉睡了5秒 long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } } }
如上例所示,你要做的仅仅是提供一个构造方法,并实现onHandleIntent()方法。
如果你决定要重写其它回调方法,比如: onCreate(), onStartCommand(), or onDestroy(),那么,在重写的方法里请确保调用父类的方法,这样的话,IntentService就可以合适的处理worker线程的生命周期了。
例如,自己实现的onStartCommand() 方法里就必须返回默认的实现(这样就可以把intent分发给onHandleIntent() 方法了):
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
除了onHandleIntent() 方法,还有一个方法里,你不需要调用父类的实现,那就是onBind() 方法(但是如果你的服务允许被绑定,那么你需要实现它)。
Extending the Service class - 继承Service类
正如你前面看到的内容所描述的那样,使用IntentService会让你更简单的实现一个启动的服务。但是,如果你需要你的服务执行多线程(而不是通过序列的进程来开始请求),那么,你就需要继承Service 类来处理每一个intent。
为了更好的对比,下面例子实现的内容与上面IntentService的例子实现的内容差不多。也就是说,对于每次的启动请求,会使用worker线程来执行工作,并每次只处理一个请求。
public class HelloService extends Service { private Looper mServiceLooper; private ServiceHandler mServiceHandler; // 处理来自线程的信息 private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { // 通常,我们需要做一些工作,例如下载文件。 // 在示例中,我们只是让线程沉睡了5秒 long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } // 使用开始的ID来停止服务,因此在处理其它工作的过程中,我们不需要消息服务 stopSelf(msg.arg1); } } @Override public void onCreate() { // 在服务里运行一个线程。注意:因为默认地,服务会运行在主线程里,而我们不希望阻塞它, / / 因此,我们创建了一个独立的线程。 // 我们让它优先在后台运行,这样,耗费CPU的工作就不会影响我们的UI了 HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // 获取HandlerThread's Looper,这样就可以用它了 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(); // 对于每次的启动请求,都发布一个message来开始工作和分发开始的ID, / / 这样的话,当完成工作后,我们就知道应该停止哪些服务了 Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; mServiceHandler.sendMessage(msg); // If we get killed, after returning from here, restart return START_STICKY; } @Override public IBinder onBind(Intent intent) { // 我们不需要绑定服务,因此返回null return null; } @Override public void onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); } }
正如你所见,使用比使用完成同样的工作要写更多的代码。
但是,因为你在onStartCommand() 方法里自己处理了每个调用,因此你可以同意执行多个请求。你可以为每次请求都创建新的线程并立即运行它们(而不是等待前一个服务执行完成)。
注意:onStartCommand() 方法必须返回一个integer类型的值,该值描述了如果系统要杀死服务,系统应该如何继续这个服务(正如上面讨论的那样,尽管你也可以修改onStartCommand() 方法的返回,但是IntentService的默认实现已经帮你把它处理了)。
onStartCommand() 方法的返回值必须是下面的某个常量:
如果在onStartCommand() 方法返回后系统杀死了服务,那么系统不会再重新创建服务,除非分发了pending intent。这是最安全的选项,这样做可以避免运行不需要的服务。
如果在onStartCommand() 方法返回后系统杀死了服务,系统在调用onStartCommand() 方法重新创建服务时,不会把最后的intent参数传递给该方法。相反,系统会给它传null,除非使用intent重新启动服务时才会给它传递intent参数。这种情况适合于多媒体播放(或者是类似的服务),不执行命令,但是无限运行并等待工作。
如果在onStartCommand() 方法返回后系统杀死了服务,系统在调用onStartCommand() 方法重新创建服务时,会把最后的intent参数传递给该方法。所有的intent会依次传递给它。这种情况特别适合于立即恢复来执行操作的服务,比如说是下载文件。
Starting a Service - 开启服务
你可以使用activity或是其他组件来一个服务,并给startService() 方法传递intent参数(用来指定要启动的服务)。Android系统就会调用onStartCommand() 方法并把intent参数传递给它(你不能直接调用onStartCommand() 方法)
例如,在前一节的示例中,你可以在调用startService() 方法传递一个明确的intent来启动相应的服务:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
startService() 方法会立即返回,接着,系统就调用服务的onStartCommand() 方法。如果启动时服务没有在运行,那么系统会先调用onCreate() 方法,然后再调用onStartCommand() 方法。
如果服务没有提供绑定,那么传递给startService() 方法的intent是服务与应用组件相互联系的唯一方式。但是,如果你想让服务返回一个结果,那么启动服务的客户端可以创建一个 PendingIntent 作为广播(使用getBroadcast() 方法)并把它和启动服务的intent绑在一起发送给服务。这样,服务就可以使用广播来分发结果了。
多次启动服务的请求相应的会导致服务的onStartCommand() 方法被多次调用。但是,停止服务只需要一个命令(stopSelf() 方法或stopService() 方法)。
Stopping a Service - 停止服务
启动的服输必须自己管理生命周期。也就是说,系统除了在恢复资源时可能会关闭某些服务外,其它情况下系统是不会停止或销毁服务的,服务在onStartCommand() 方法返回后就会持续运行下去。因此,服务必须自己通过调用stopSelf() 方法来停止自己,或是其它组件通过调用stopService() 方法来停止服务。
不论执行了stopSelf() 方法或stopService() 方法,系统都会马上销毁服务的。
但是,如果服务同时处理了多个到达onStartCommand() 方法的请求,那么当你处理完一个启动请求时,你就不应该停止服务,这是因为你可能又收到一个新的开始请求了(在第一个请求的最后停止服务会中断第二个请求的)。为了避免这样的问题,你应该使用stopSelf(int) 方法来确保你停止服务的请求总是基于最近的开始请求的。也就是说,当你调用stopSelf(int) 方法时,会传递开启服务请求的ID(开启服务的ID会传递给onStartCommand() 方法),这样,你就可以停止相应的服务了。在你调用stopSelf(int) 方法之前,如果服务就收到了一个新的启动服务的请求,那么ID就不再匹配了,服务也不会被停止。
警告:当服务完成工作后,应用应该停止它,这点是非常重要的,这样做是为了避免浪费系统资源和消耗电量。如果有必要的话,其它组件也可以通过调用stopService() 方法来停止服务。即使你的服务提供了绑定功能,如果它曾经收到了onStartCommand() 方法的调用,那你也应该自己去停止服务。
Creating a Bound Service - 创建一个绑定服务
绑定服务允许应用组件通过调用bindService() 方法来建立一个长时间有效的连接(一般来说不允许组件通过调用startService() 方法来启动它)。
当你想让服务和你应用里的activity或组件交互,或是通过进程间的通信来让其它应用使用你应用的某些功能时,你就应该创建一个绑定服务。
要创建绑定服务,你必须实现onBind() 方法返回一个IBinder ,IBinder 定义了与服务通信的接口。随后,其它应用组件可以通过启用bindService() 方法来取回该接口并开始调用服务的方法。绑定服务就是为与它绑定的组件存活的,因此,如果没有组件绑定到服务上,系统就会销毁服务。
要创建绑定服务,你首先需要做的是定义一个与服务交互的接口。该接口必须是IBinder的实现,也就是你服务必须在onBind() 回调方法里返回的东东。一旦客户端收到了IBinder,它就通过该接口开始与服务进行交互了。
当客户端完成与service的交互后,客户端就可以调用unbindService() 方法与服务解绑。
实现绑定服务有多种方法,并且它的实现也远远比启动服务复杂,因此专门有一章来讨论绑定服务:Bound Services 。
Sending Notifications to the User - 给用户发送通知
在服务运行时,可以通过使用Toast Notifications or Status Bar Notifications 来通知用户。
toast通知会在用户当前界面上出现一个信息的提示,在出现后会维持一段时间;而状态栏通知则会出现在设备的状态栏上,该通知有消息也有图标,用户在点击状态栏通知后可能会触发某种动作(如打开activity)。
一般来说,当后台的工作完成时(例如完成文件下载),状态栏通知是通知的最佳方式,并且用户也可以与其进行交互。当用户选择了通知的扩展视图后,通知就可以打开一个activity(例如下载文件的视图)。
Running a Service in the Foreground - 运行在前台的服务
前台服务会被用户注意到,因此当内存低时,系统不会考虑杀死这种类型的服务。前台服务必须提供一个状态栏通知,该服务会被放在“正在运行”的程序下,这就意味着通知栏内容不会消失,直到服务停止或是从前台移除。
例如,使用服务播放音乐的播放器就运行在前台,很明显,这是因为用户会注意它的操作的。这样的通知栏也可能标识当前播放的音乐,也允许用户加载一个activity来和播放器进行交互。
为了让你的服务运行在前台,请调用startForeground() 方法。该方法有两个参数:唯一标识通知的整形值,状态栏里的Notification 对象,例如:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
警告:startForeground() 方法的第一个参数--ID不能为0。
调用stopForeground() 方法就可以从前台移除服务。该方法返回一个布尔值,标识了是否从状态栏成功移除。该方法不会停止服务。但是,如果你停止了正在前台运行的服务,那么通知也被移除了。
Managing the Lifecycle of a service - 管理服务的生命周期
服务的生命周期比activity的简单多了。但是,你也需要对服务的创建和销毁给予足够的重视,这是因为服务可以运行在后台,这些是用户不知道的。
从创建到销毁,服务可以沿着两种途径进行。
启动的服务:当其它组件调用startService() 方法时,服务启动。服务会立即启动,并且只能通过stopSelf() 方法停止自己。其它的组件可以通过调用stopService() 方法停止服务。当服务停止了,系统会销毁服务。
绑定的服务:当其它组件(客户端)调用bindService() 方法时,服务被创建。客户端可以通过IBinder接口与服务进行通信。客户端可以通过调用unbindService() 方法关闭它与服务的连接。多个客户端可以绑定相同的服务,当所有与服务绑定的客户端解绑服务时,系统销毁服务(这种服务不能自己停止自己)。
这两种方式不是完全独立的。也就是说,你完全可以绑定一个已经使用startService() 方法启动的服务。例如,后台播放音乐的服务可能已经通过startService() 方法启动了。随后,用户可能想尝试控制播放器或是想要获取当前播放歌曲的信息时,就可以通过调用bindService() 方法来把服务绑定到某个activity上。在这种情况下,stopService() 方法或是stopSelf() 方法不能真正的停止服务,除非所有的客户端都与该服务解绑。
Implementing the lifecycle callbacks - 实现生命周期回调
与activity类似,服务也有一些生命周期的回调方法,你实现了这些方法,就可以监听服务状态的改变,就可以在适当的时候完成合适的工作。下面的框架演示了服务的每一个生命周期的方法:
public class ExampleService extends Service { int mStartMode; // 如果服务被杀死,标识应该采取哪种行为 IBinder mBinder; // 客户端绑定的接口 boolean mAllowRebind; // 标识onRebind是否应该被使用 @Override public void onCreate() { // 服务被创建 } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 该服务是由 startService()方法启动的 return mStartMode; } @Override public IBinder onBind(Intent intent) { // 客户端使用 bindService()方法与服务绑定 return mBinder; } @Override public boolean onUnbind(Intent intent) { // 所有的客户端已经使用unbindService()方法与服务解绑了 return mAllowRebind; } @Override public void onRebind(Intent intent) { // 在onUnbind()被调用后,客户端又使用方法与服务重新绑定 bindService() } @Override public void onDestroy() { // 服务不再使用并即将被销毁 } }
注意:与activity的生命周期回调方法不一样,你不需要调用父类的方法。
图1 服务的生命周期。当服务使用startService()方法启动时,服务的生命周期如左边的图表所示。当服务使用 bindService()方法创建时,服务的生命周期如右边的图表所示。
通过实现这些回调方法,你可以监测生命周期的两个内嵌的循环:
1. 服务完整的生命周期发生在onCreate() 和onDestroy() 方法之间。与activity类似,在onCreate() 方法里进行服务的初始化工作,大onDestroy() 方法里释放资源。例如,音乐播放器在onCreate() 方法里创建线程来播放音乐,在onDestroy() 方法里停止线程。onCreate() 和onDestroy() 方法可以被所有服务调用,不论该服务是以startService()方法启动还是以bindService()方法创建的。
2. 服务被激活的生命周期开始于onStartCommand() 或onBind() 方法,这两个方法都可以处理由startService()或bindService()方法传递进来的intent。如果服务是被启动的,那它生命周期的结束时间与完整生命周期的结束时间一致(即使onStartCommand() 方法返回后,服务仍然是活的)。如果服务是被绑定的, 那么服务的生命周期在onUnbind() 方法返回后就结束了。
注意:尽管启动的服务可以通过stopService() 方法或是stopSelf() 方法来停止,但是这两个方法没有相应的回调函数(没有onStop()回调)。
图1说明了服务典型的回调方法。尽管图解把使用startService()方法启动的服务与使用bindService()方法创建的服务分开了,但是你一定得清楚,无论服务是怎么被创建的,都可以允许客户端绑定它。因此,最初使用onStartCommand()方法启动的服务(客户端通过调用startService()方法启动服务的)仍然也可以接收onBind() 方法的回调。
创建一个提供绑定服务的更多信息,请参见 Bound Services 章节。