原文地址:Service
service没有界面,通常用于在后台进行耗时操作。其他应用组件可以启动一个service,此service在启动后可以长时间运行在后台,即使用户切换到了其他应用。另外,应用组件可以绑定service并与它通信,甚至进行进程间通信。service的使用场景有:网络操作、播放音乐、文件IO、content provider操作等。
service有两种形式:
虽然本文档是将两种形式的service分开描述的,但是你的service可以同时支持两种形式。你的service支持的形式仅仅取决于你实现的回调方法:实现onStartCommand()允许其他组件start,实现onBind()允许其他组件绑定。
不管你的service是否已经被started或者bound,其他应用组件仍然可以使用你的service,即使是其他应用。但是,你可以将service声明为私有(在manifest中设置),从而阻止其他应用的访问。
注意:service运行在宿主进程的主线程——它不会创建自己的线程,也不是在分离出来的进程中运行(除非你指定)。这意味着,你的service需要在子线程进行CPU高消耗的操作或者阻塞操作,从而减少ANR并优化用户体验。
通过继承Servie类或者它的子类可以创建一个service。在你的service实现中,你需要重写几个回调方法,以处理service的生命周期并提供启动你的service的机制。重要的几个回调方法如下:
如果其他组件通过startService()启动你的service,那么这个service会在后台一直运行,直到它停止自己-stopSelf(),或者其他组件停止它-stopService()。
如果其他组件通过bindService()启动你的service,那么这个service只有在被绑定期间运行,只要所有的绑定方都解绑,那么service就会被销毁。
Android系统可能会在内存过低时为了回收内存而停止service。如果此service是被有用户焦点的activity绑定的,那么它被销毁的可能性会小一点。如果此service被声明为“前台服务”(run in the foreground),那么它将几乎不会被销毁。其他情况的service,如果在后台停留了很长时间,那么系统会降低它在后台task的优先级,从而很可能被系统销毁。如果你的service已经启动了,那么你需要用某种方式处理由系统发起的重启。如果系统销毁了你的service,那么它会在资源可用的时候尽快重启你的service。
下面的部分将会叙述怎样创建各种类型的service和怎样使用它。
像其他组件一样,你需要在manifest文件中声明service。
声明方法是在
下添加
标签,如下所示:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
application>
manifest>
在
标签下你可以添加一些属性,以控制service的一些行为,如要求启动service的权限、指定service运行的进程等。android:name是必须的,它指定了service的类名。在发布你的应用后,你不应该再改变此属性,以防依赖于此属性的显式intent被影响。
不为service声明intent filter可以保证你的service的安全,你可以使用显式intent启动或绑定你的service。如果你决定允许通过非显式intent启动你的service,可以为service添加intent filter,这样就可以不用在intent中添加组件名称了,但是你必须在intent中通过setPackage()指定包名,以提供足够详细的信息来找到目标service。
另外,如果你希望你的service只能在本应用使用,可以将android:exported属性设置为false。
通过startService()启动的service即started形式的service,其对应service的onStartCommand()方法。
当service通过start启动后,它的生命周期就独立于启动它的组件了,它将在后台持续运行,即使启动它的组件被销毁。因此,这种service应该在工作完成后停止自己(通过stopSelf()方法),或者其他应用通过stopService()停止它。
应用组件,如activity,可以通过startService()启动service,并可以在intent中添加service需要的数据。service将会在onStartCommand()方法中收到此intent。
举个例子,某个activity需要将数据保存到云端数据库。那么此activity可以start一个service,并且将数据传给它(在startService()时通过intent添加数据)。service通过onStartCommand()收到intent并取出数据,然后连接网络、操作数据库。当操作结束后,service就stopSelf,被系统销毁。
注意:默认情况下,service运行在声明它的应用的进程的主线程中。所以,如果在service进行复杂或者阻塞操作会影响用户与activity的交互,在这种情况下,你应用新建一个线程进行这些操作。
通常,你可以继承以下两个类来创建service:
下面的部分描述了通过以上两个类创建service的方法。
大多数started形式的service不需要同时处理多个请求,那么你可以使用IntentService来实现你的service。
IntentService做了以下事情:
你需要做的,只是在onHandleIntent()方法中处理调用方传来的intent。当然,你也需要提供一个简单的service构造方法。
下面是一个IntentService的例子:
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) {
}
}
}
}
}
如果你想要重写其他的回调方法,比如onCreate()、onStartCommand()、onDestroy(),请保证调用对应超类的实现。
例如,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()中,你也不需要调用超类实现,但是如果你不允许绑定你的service,最好不实现此方法。
在前面我们可以看到,通过IntentService可以很简单的实现一个started形式的service。但是,如果你想要你的service可以处理多线程,而不是通过work队列依次处理请求,可以继承Service类处理每个intent。
作为比较,下面使用Service类实现了与使用IntentService具有相同功能的service。下面的代码逻辑为:将每个start的请求加入到工作线程中,每次处理一个请求。
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()方法必须返回一个整数。这个整数表示,如果系统kill了你的service将会怎样重启你的service(之前也提到,IntentService已经为你做了这些,你不需要做这些)。onStartCommand()的返回值必须为以下值:
你可以在某个activity或其他应用组件中启动service,调用startService()并传入指定了目标service的intent即可。系统会调用目标service的onStartCommand()方法并传入你的intent(你不应该直接调用onStartCommand()方法)。
下面的例子说明了怎样使用显式intent启动前面提到的service:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
startService()方法会立刻返回,之后系统会调用目标service的onStartCommand()方法。如果目标service并没有在运行,那么系统会先调用onCreate(),然后调用onStartCommand()。
如果service不支持绑定方式,那么通过startService()传递intent就是应用组件和service通信的唯一方式了。但是,如果你需要service向应用组件返回结果,那么应用组件可以先创建一个广播的PendingIntent并传递给service,然后service可以用这个广播发送结果。
多次start一个service会调用多次onStartCommand()。stop此service只需调用一次stopSelf()或stopService()即可。
started形式的service必须自己管理自己的生命周期。这是因为在onStartCommand()方法返回后,service会持续运行,除非系统需要回收内容,否则系统不会停止或销毁service。所以service必须通过stopSelf()停止自己,或者其他组件通过stopService()停止它。
调用stopSelf()或stopService()后,系统会尽快销毁此service。
如果service在onStartCommand()中同时处理多个请求,那么你不应该在某个请求被处理完成后停止这个服务,因为可能会有新的请求进来。要避免这种情况,你可以使用stopSelf(int)方法,它会保证你的stop请求总是基于最近的start请求。当你调用stopSelf(int)时,你需要传入start请求的id(通过onStartCommand()的startId获得),以指定你的stop请求对应了哪个start请求。这样的话,当service收到新的start请求,之前的stopSelf(int)中的ID就会与新的start请求不匹配,service就不会被停止了。
注意:你的应用应该在service完成工作后停止它,以防浪费系统资源、消耗电量。如果必要的话,其他应用组件可以通过stopService()停止此service。即使你使用的是bound形式的service,你也得在收到onStartCommand()调用后停止你的service
bound形式的service允许应用组件通过bindService()绑定它,以创建一个持续的连接。通常bound形式的service不允许用于组件通过startService()启动它。
如果你希望service能与其他应用组件互动或者通过进程间通信分享功能,可以使用bound形式的service。
要创建bound形式的service,你必须实现onBind()方法,并返回一个定义了通信接口的IBinder。其他应用组件通过bindService()获取接口,并通过其中的方法与service通信。bound形式的service只在有应用组件绑定它时运行,如果没有组件绑定到此service,系统会销毁它(你不必像started形式的service那样自己停止service)。
如果有多个应用组件绑定了service,那么应用组件在结束通信后,需要通过unbindService()解绑,当所有的应用组件都解绑了,系统会销毁此service。
bound形式的service比started形式的service难的多,所以这部分内容另写一篇文档。
service在运行时,可以通过Toast Notification或者Status Bar Notification通知用户一些信息。
Toast Notification是在桌面上显示一段时间的信息,Status Bar Notification会在通知栏显示图标和信息,用户可以点击此通知来进行某个动作。
通常,Status Bar Notification是后台工作完成后进行通知的最好方式,用户可以看到并与它互动。当用户点击了通知,通知可以进行某些动作。
前台service指用户可以“看到”的service,系统不会在内存紧张的时候kill前台service。前台service必须在状态栏显示一个通知,此通知位于顶栏下面。除非service停止或者从前台移除,否则此通知不会被关闭,。
例如,音乐应用会在service中播放音乐,此service一般为前台service,用户可以明确的看到此service。状态栏中的通知表示当前播放歌曲,并且允许用户点击以打开音乐应用。
你可以通过startForeground()让你的service在前台运行。这个方法有两个参数:一个是整数,表示通知的id,另一个是要显示的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()方法将service从前台移除。这个方法需要一个boolean参数,表示是否将状态栏通知一起移除。这个方法不会停止service。当然,如果你直接停止了service,那么它也会从前台移除(包括状态栏通知)。
service的生命周期类似于activity。但是你需要特别注意service创建和销毁过程,因为service可以在用户不知情的情况下运行在后台。
service的生命周期从创建到销毁有两条路线:
这两条路线不是完全分离的,你可以绑定已经通过startService()启动的service。例如,后台音乐service可能被startService()启动(传入要播放的音乐的信息),之后,用户可能想要获取当前音乐的信息,那么就会有一个activity通过bindService()绑定此service。在这个例子中,stopService()或者stopSelf()在解绑以前不能真正的停止此service。
类似于activity,你可以实现service的一些生命周期回调方法以监控service状态,并在适当时候进行一些工作。下面是生命周期方法的示例:
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生命周期的两个环:
注意:虽然started形式的service是通过stopSelf()或stopService()停止的,但是没有对应的回调方法,即没有onStop()方法。所以,除非service被某个组件绑定,否则当此service进入stopped状态,系统便会销毁它——onDestroy()是唯一会被调用的方法
下图1表示了service的典型回调方法。虽然图中将started形式的service与bound形式的service分开了,但是注意,不管service是怎样被启动的,都可以进行bind或start。所以,通过onStartCommand()初始化的service仍然可以收到onBind()的调用。