[译]Service

##Services

Service是一种Application组件,可以在后台执行长时间的操作且不提供UI。其他的Application组件可以启动它并且它会一直在后台持续运行即使是用户切换到了另一个Application。同时,一个组件可以bind一个Service并与之交互甚至是进程间的通信(IPC)。例如:一个Service可以处理网络事务,播放音乐,执行文件I/O,或者与Content Provider交互,所有这些都是在后台执行。

Service可以两种方式运行:

  • Started
    当一个Application组件(比如一个Activity)通过调用startService方法启动一个Service,我们称之为“Started”。一旦Started,Service可以在后台无限运行,即使是这个启动它的组件已经销毁。通常,一个Started的Service执行单一的操作且不为它的调用者返回结果。例如,它可以通过网络下载或上传文件。当一个操作执行完成,Service应当Stop自己。

  • Bound
    当一个Application组件通过调用bindService绑定一个Service,我们称之为“Bound”。一个Bound Service会提供一个Client-Server的接口来允许其他组件与之交互,发送请求,获取结果,甚至是通过跨进程的方式(IPC)。一个Bound Service仅与绑定它的Application组件的运行时间一样。多个组件可以同时绑定一个Service,但是所有绑定项unbind时,该Service才可以被销毁。

虽然此文档基本上都是单独讨论这两种类型的Service,你的Service可以同时以两种方式工作(Started:无限运行,允许绑定)。这是很简单的事,你只需要实现两个回调方法:onStartCommand允许其他组件Start它,onBind允许bind它。

不管你的Service是Started,Bound或者二者皆是,任何其他Application都可以使用这个Service(即使是另外的Application),就像其他组件使用你的Activity那样(通过Intent)。当然,你也可以在manifest文件中私有化声明你的Service,从而阻止别的Application访问。这个会在Declaring the service in the manifest中讨论。

注意:Service运行在它所在process的主线程(main thread)中——-Service不会创建它自己的Thread,且不会运行在另一个单独的process(除非你特别声明)。这意味着如果你的Service要做任何耗CPU或阻塞操作(例如MP3播放,网络获取等),你应该在Service中新建一个Thread来处理这些事。通过使用单独的Thread,你可以减少ANR的风险,而Application的主线程(main thread)可以关注于你的Activity与用户的交互。

The Basics

要创建一个Service,你必须创建一个Service.java的子类(或其子类的子类)。在你的实现里,你需要override Service生命周期中一些处理关键环节的回调方法,和提供一种其他组件bind此Service的机制。你需要override的最重要的回调如下:

  • onStartCommand
    当其他组件,比如一个Activity调用startService方法来请求启动该Service时,系统会调用此方法。一旦此方法执行,Service已经被Started且可以无限在后台运行。如果你实现了此方法,你有责任在任务完成后调用stopSelf或stopService来stop此Service(如果你仅仅是想提供来bind,则无需实现此方法)。

  • onBind
    当其他组件通过调用bindService想要与此Service绑定(比如执行RPC)时,系统会调用此方法。如果你实现了此方法,你必须提供一个接口(返回一个IBinder)供用户用来与Srevice交流。你必须总是实现此方法,但是如果你不想Service可以Bind,在此方法中返回null即可。

  • onCreate
    当Service第一次创建时会调用,用来执行一次性的初始化程序(在onStartCommand和onBind之前)。如果此Service已经在运行了,该方法不会被调用。

  • onDestroy
    在Service不再使用将要销毁时系统调用此方法。你的Service应该实现该方法去清除释放资源,比如线程,注册的监听,receiver等等。这是Service接收到的最后一个方法。

如果一个组件通过调用startService启动一个Service(这会导致onStartCommand的调用),此Service会持续运行直到它自己stopSelf或是其他组件调用stopService来stop它。

如果一个组件通过调用bindService创建一个Service(onStartCommand不会调用),此Service的运行时间与Bind它的组件一样。一旦所有客户端(client)都unBound此Service,系统会销毁它。

Android系统只有在低内存且必须为与用户交互(user focus)的Activity回收系统资源时才会强制stop Service。如果此Service与当前用户focus的Activity绑定,它不太可能被kill。如果此Service被声明为run in the foreground(之后会讲到),它几乎绝不会被kill。
另外,如果Service被start后长时间运行,系统会随着时间的推移降低其在后台Task列表中位置,从而此Service会变得高概率被kill。如果你的Service启动起来,你应该设计一种良好的方式去处理系统去重启它。因为如果系统kill你的Service,它会在资源可用之后尽快的重新启动(虽然它也依赖与你从onStartCommand返回的值,后续讨论)。更多系统销毁Service的信息,参加Processes and Threading文档。

下面章节将讨论如果创建不同类型的Service和其他组件如何去使用它。

Declaring a service in the manifest

和Activity(以及其他组件)一样,你必须在你的manifest文件中声明所有的Service。
为了声明Service,要在元素中添加一个的子元素,如:

1
2
3
4
5
6
7
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>

还有一些属性你可以在中定义,比如启动此Service的权限(permission),Service应该运行的process。android:name是唯一必须要有的属性,它指定此Service的class name。一旦你发布你的应用,你就不应该改变它的名字,因为如果你改变了,一些使用显式Intent关联你的Service的功能可能阻塞。

和Activity一样,Service可以定义intent filter来允许其他组件通过隐式Intent的方式启动它。通过声明Intent filter,用户设备中安装的任何应用的组件都可以潜在的启动你的Service。

如果你计划仅仅本地使用此Service(其他Application不能使用),你不需要且不应该提供任何intent filter。没有任何intent filter,你必须使用显式class name的Intent来启动Service。更多信息将在以下starting a service中说到。

另外,你可以设置Service的android:exported属性为false来确保你的Service对你的Application私有化。此属性同样作用于你提供了intent filter的情况。

更多Service的intent filter信息,参见Intents and Intent Filters文档。

Creating a Started Service

另一个Application组件调用startServie启动的Service称之为Started Service,会调用Service的onStartCommand方法。

一旦Service Started,它的生命周期不依赖于启动它的组件,它可以在后台无限运行,即使启动它的组件已经销毁。同样,Service可以在工作完成后调用stopSelf来stop自己,或是其他组件可以通过调用stopService来stop此Service。

一个Application组件(比如Activity)可以调用startService来启动Service(传送一个Intent指定Service,包含Service需要使用的数据)。Service在它的onStartCommand方法中接收此Intent。
注意:Android1.6以及之前,你应该实现onStart, Android2.0后使用onStartCommand代替onStart。

例如,假设一个Activity需要往数据库中保存一些数据。Activity可以启动一个Service,并将需要保存的数据通过intent传送。Service在onStartCommand中接收此Intent,连接到互联网并执行数据库事务。当事务执行完毕,Service自己Stop且销毁。

默认情况下,Service运行在声明它的Application所在的process,且在Application的主线程(main thread)运行。所以,如果你的Service在用户与Activity(同一个Application的)交互时执行高密度和阻塞操作,将会减慢Activity的执行。为了避免影响Application的运行,你应该在Service中启动一个新的Thread。

通常,你可以继承以下两个Class来创建一个Started Service。

  • Service
    所有Service的基类。当你继承此class,重要的是你要创建一个新的Thread来做Service的工作,因为此Service使用你Application的main thread,默认,这会减慢你的Application的所有Activity的运行。

  • IntentService
    这是一个Service的子类,它使用一个工作线程来处理启动请求,每次一个。如果你不想你的Service同时处理多个请求,这是最好的选择。所有你要做的就是实现onHandleIntent方法,它会接收每个启动请求的Intent,所以你可以做后台处理。

下面的章节将描述你的Service如何实现其中任一种。

Extending the IntentService class

因为绝大多数Started Service不需要同时处理过个请求(有多线程风险),也许最好的方式是继承IntentService实现你的Service。

IntentService做以下工作:

  • 创建一个默认的工作线程(与你的Application的主线程分离)来处理onStartCommand分发的所有Intent。
  • 创建一个工作队列,一次传送一个Intent给你的onHandleIntent实现,所以你不用担心多线程。
  • 在所有请求处理完成后Stop Service,所以你不需要调用stopSelf。
  • 提供一个默认返回null的onBind实现。
  • 提供一个默认的onStartCommand实现,它会传送intent给工作队列,最终给onHandleIntent处理。

所有你需要做的其实就是实现onHandleIntent方法。(虽然你也需要提供一个简单的构造)
以下是一个实现IntentService的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
}

所有你需要做的:一个onHandleIntent的实现和一个构造函数。

如果你决定也override其他的回调方法,例如onCreate、onStartCommand或onDestroy,确保你调用了父类(super)的实现,这样IntentService才能妥善处理工作线程的生命周期。

例如,onStartCommand必须返回一个默认的实现(Intent被发送到onHandleIntent):

1
2
3
4
5
@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方法(但是仅当你的Service可以被bind的时候才需要实现此方法)。

在下一章节中,你将看到如何通过继承Service来实现同类的Service(Started Service),如果你要同时处理启动请求,这个是比较适合的。(虽然有较多代码要写)

Extending the Service class

如你所见,上一章节中使用IntentService来实现一个Started Service是非常简单的。但是,如果,你需要你的Service可以处理多线程(而不是通过一个工作队列来处理请求),你可以继承Service来处理每个Intent。

比较起见,底下的代码继承Service实现的功能与以上继承IntentService的一样。也就是说,对于每一个请求,使用工作线程来处理,且每次一个请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
// 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;
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做了更多的工作。

然而,因为你可以自己调用onStartCommand来处理每个请求,你可以同时执行多个请求。如果这是你的需求,你可以为每个请求创建一个线程去正确的运行(而不是等待前一个请求处理完成)。

需要注意是onStartCommand必须返回一个integer。此整形值描述了系统在kill掉Service后该如何持续此Service(如上讨论,IntentService的默认实现会处理这些,虽然你也可以改变此默认实现)。onStartCommand的返回包括以下几种:

  • START_NOT_STICKY
    如果系统在onStartCommand返回之后kill Service,不重新创建Service。除非还有pending的Intent要deliver(分发)。这是最安全的选项去避免运行你的Service,当不必要或是当你的Application可以简单的恢复任何未完成的任务时。

  • START_STICKY
    如果系统在onStartCommand返回之后kill Service,重建该Service并调用onStartCommand,但是不重新分发(redeliver)最后的Intent。相反的,系统以一个null Intent调用onStartCommand,除非还有pending的Intent去启动此Service,此情况下,所有的Intent都会被delivered。这个比较适合媒体播放器(或类似的服务),他们不执行命令,而是无限运行等待一个任务。

  • START_REDELIVER_INTENT
    如果系统在onStartCommand返回之后kill Service,重建该Service并以最后一个deliver给此Service的Intent调用onStartCommand。每个pending的Intent都以顺序分发给此Service。这个适用于积极执行一个应该被立即resume的任务的Service,例如下载一个文件。

Starting a Service

你可以从一个Activity或其他Application组件启动一个Service,通过传送一个Intent(指定要启动的Service)给startService。Android系统会调用Service的onStartCommand方法并传送给它该Intent(你绝不应该直接调用onStartCommand)。

如下,Activity可以通过显式的Intent启动一个Service。

1
2
Intent intent = new Intent(this, HelloService.class);
startService(intent);

startService方法会直接返回而Android系统会调用Service的onStartCommand方法。如果此Service尚未运行,系统会先调用onCreate,然后调用onStartCommand。

如果此Service没有也提供bind。发送给startService的Intent是Application组件与Service的唯一模式。当然,如果你想你的Service返回一个结果,启动Service的客户端可以为一个Broadcast(使用getBroadcast())创建一个PendingIntent,且将此Intent传送给Service。Service可以使用此Broadcast来分发结果。

多个启动Service的请求导致多个对应的onStartCommand的调用。但是,只有一个请求可以stop此Service(通过stopSelf和stopService)。

Stopping a service

一个Started Service必须管理自己的生命周期。也就是说,系统不会stop或是destroy此Service除非必须回收系统内存且此服务持续运行到onStartCommand返回之后。所以,Service必须通过调用stopSelf来Stop自己,会是其他组件调用stopService方法。

一旦调用stopSelf或stopService,系统会尽快destroy掉该Service。

然而,如果你的Service在onStartCommand中同时处理多个请求,你不应该在start请求完成后stop你的Service,因为你可能又收到了一个新的start请求(第一次请求后的Stop可能会终结第二个请求)。为了避免这种问题,你可以使用stopSelf(int)来确保你请求stop此Service总是基于最近的start请求。也就是说,当你调用stopSelf(int),你可以传一个与你的Stop请求相对应的start请求ID(onStartCommand有此startId)。这样,如果Service在你可以调用stopSelf(int)之前接收到一个新的start请求,由于ID不匹配所有Service不会stop。
(此段需要再根据实际代码理解)

注意:你的Application在Service不工作时stop它是非常重要的,为了避免浪费系统资源和消耗电池电量。如果必要,其他组件可以调用stopService来stop此Service。即使你为Service开启绑定(理解为Bound Service),如果Service曾经接收过调用onStartCommand的请求,你都必须自己手动stop这个Service。

Service生命周期的更多信息,参见Managing the lifecycle of a Service章节。

Creating a Bound Service

Bound Service允许Application的组件通过调用bindService来绑定它,以创建一个长期的连接(且通常不允许组件调用startService来启动它)。

当你想要Service与你的Application中的Activity或其他组件交互,或是通过IPC暴露你的Application的功能给其他应用,此时你应该创建一个Bound Service。

要创建一个Bound Service,你必须实现onBind回调并返回一个IBinder(定义Service的通信接口)。其他Application组件可以调用bindService来获取此接口并调用Service的方法。此Service仅为服务于绑定它的Application组件而生存,所以当没有任何组件绑定此Service时,系统会销毁它(你不需要stop一个Bound Service,但是你必须stop当此Service是通过onStartCommand启动时)。

要创建一个Bound Service,第一件必须要做的事是定义一个客户端如何于此Service交互的接口。此Service与Client间的接口必须实现IBinder,且你的Service必须在onBind中返回此接口。一旦Client接收到此接口,它就可以开始通过此接口与你的Service进行交互。

有很多方式去实现一个Bound Service,而且实现起来比Started Service复杂得多,所以我们在一个单独的文档讨论Bound Service。参见Bound Services文档

Sending Notifications to the User

一旦Service运行起来,它可以通过Toast Notification和Status Bar Notification两种方式向用户通知事件。

Toast Notification是一种短时间显示在当前窗口的消息,而Status Bar Notification在StatusBar上提供一个icon和message(用户可以选择它来执行一个Action,例如启动一个Activity)。

通常,当一些后台工作完成(文件下载完成)时提供一个Status Bar Notification是最好的方式,此时用户可以采取一些行动。当用户在下拉通知栏(Expand View)选择此通知时,此Notification可以启动一个Activity(例如查看此下载完的文件)。

Toast Notification和Status Bar Notification参见其他开发文档。

Running a Service in the Foreground

Foreground Service被认为是用户actively aware of(理解为保持积极工作意识)因此不作为系统低内存下kill候选的Service。Foreground Service必须提供一个Status Bar Notification(放置一个“Ongoing/正在进行”的标题),这意味着除非此Service stop或是从前台移除此Notification才会消失。

例如:一个音乐播放器的播放音乐的Service应该设定为Foreground Service,因为用户明确的知道它的操作。在Status Bar上的Notification必须指明当前播放的曲目,且允许用户启动一个Activity来与音乐播放器交互。

调用startForeground可以让你的Service在前台运行。此方法有两个参数:一个唯一的整型标识Notification和一个StatusBar上要显示的Notification。如下:

1
2
3
4
5
6
7
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, notification);

可以调用stopForeground来将此Service从前台移除。此方法有一个boolean参数,指明是否也移除Status Bar上的Notification。此方法不会stop Service。当然,如果你在Service仍旧运行于前台时stop它,Notification也会被移除。

注意:startForeground和stopForeground是在Android2.0中提出的,在之前的平台,你必须使用setForeground(参见startForeground的API文档)。

Managing the Lifecycle of a Service

Service的生命周期相比于Activity要简单很多。但是,你要特别关注你的Service的创建与销毁,因为Service可以在用户无意识的状态下在后台运行。

Service的生命周期(从创建到销毁)有两种不同的路径:

  • Started Service
    通过其他组件调用startService创建的。此类Service无限期运行所以必须手动stop,(stopSelf或是其他组件调用stopService)。当Service stop后,系统会销毁它。

  • Bound Service
    通过其他组件(client)调用bindService创建的。该Client通过一个IBinder接口与Service交互。此Client也可以调用unbindService来关闭此链接。多个client可以绑定同一个Service,且当所有client都unbind后,系统才会销毁Service(此种Service不需要自己stop)。

这两条路径不是完全独立的,也就是说,你可以Bind一个已经通过startService启动的Service。比如:一个后台播放音乐的服务可能被一个指定音乐播放的Intent起来(startService)。之后,可能用户可能想要对播放器进行一些控制或是获取当前播放音乐的信息,可以使用一个Activity来bind此Service(bindService)。在这种情况下,调用stopService或是stopSelf都不会实质上stop此Service直到所有client都与Service unbind。

Implementing the lifecycle callbacks

与Activity类似,Service也拥有一些生命周期的回调函数,你可以去实现来监听Service的状态变化或是在合适的时间做合适的事。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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中的回调不同的是,你不需要必须调用父类的实现。

通过实现以上的回调方法,你可以监听Service生命周期中的两个嵌套循环:

  • Entire lifetime
    onCreate和onDestroy之间。与Activity类似,Service在onCreate中初始化而在onDestroy中释放所有保留的资源。例如,一个音乐播放的Service可以在onCreate中创建播放音乐的Thread,而在onDestroy中stop此Thread。

  • Active lifetime
    从onStartCommand或是onBind开始,分别处理从startService或bindService中传过来的Intent。如果是一个Started Service,它的Active Lifetime的结束与Entire lifetime的结束是一样的(即使在onStartCommand返回之后此Service还是Active)。如果是一个Bound Service,在onUnbind返回后Active lifetime就结束了。

注意:虽然一个Started Service可以通过调用stopService或是StopSelf来stop,他们没有对应的回调(没有诸如onStop之类的)。所以,除非Service绑定了一个client,系统在Service stopped之后销毁它(onDestroy是唯一接收到的回调)。

下图展示了Service的典型回调。虽然图示将Service以创建方式(startService或bindService)分开。但是要记住,不管一个Service如何启动,都潜在的允许client去bind它。所以,一个onStartCommand的Service(startService)仍然可以调用onBind(当其他client调用bindService时)。

service_lifecycle

图解:左边是Started Service,右边是Bound Service。
更多Bound Service的信息,参见Bound Service文档