创建started service
应用组件(例如Activity)调用startService()来启动一个Service,将需要的参数通过Intent传给Service,Service将会在onStartCommand函数中获得Intent。
有两种方式可以创建started service,一种是扩展Service类,另外一种是扩展IntentService类
扩展Service
这是所有服务的基类。扩展这个类的时候,特别重要的一点是,需要创建一个新的线程来做服务任务,因为service默认是运行在你的主线程(UI线程)中的,它会使你的主线程运行缓慢。
扩展IntentService
这是一个service的子类,他可以在一个工作线程中处理所有的启动请求。如果你不需要同一时刻出来所有的服务请求,使用IntentService是一个很好的选择。你需要做的仅仅是实现onHandlerIntent()方法,通过这个函数处理接受的每一个启动请求。
下面我们学习如何扩展IntentService类和Service类
扩展IntentService类
IntentService做了什么?
1.创建一个独立于主线程的工作线程,用于执行通过onStartCommand()传来的所有的intent。
2.创建一个工作队列,将接受的所有intent一个一个的传给onHandlerIntent(),所以同一时间内你只处理一个intent,不用担心多线程的问题。
3.当所有的请求都处理完后,停止服务,所以你不需要手动调用stopSelf()。
4.提供onBind()函数的默认实现,返回null
5.提供onStartCommand()函数的默认实现,它把intent发送到工作队列中去,然后工作队列再发送到你的onHandlerIntent()函数中。
有了上面的基础,你仅仅要做的就是实现onHandlerIntent()。并且实现一个小小的构造函数。
参考下面的例子:
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 一定记得调用父类相应的函数,这样IntentService才能正确的处理工作线程。
例如,你需要在onStartCommand函数中弹出一个提示,那么你可以这样写:
@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);
}
有一点例外的是,如果你需要其他组件绑定服务,那么你的onBind函数不需要调用父类的onBind。
在下一节中,你将会看到通过扩展service类来实现与本节相同的服务,所不同的是代码会更多,但是同样意味着更灵活,特别是你需要同时处理多个请求时,比较适合直接扩展Service。
扩展Service类
如同你在上一节看到的一样,使用IntentService来实现一个started service非常简单。如果你需要你的service来执行多个线程,那么你需要扩展Service类去处理每个intent。
作为对比,下面的例子用Service类实现了和上一节中一样功能的服务。
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()函数必须返回一个整数。这个整数描述了当系统杀死该服务时将要如何处理该服务。返回值必须是下面几个值:
START_NOT_STICKY
在onStartCommand()返回后,系统杀死服务,服务将不会重启,除非有pending intents(目前笔者还不懂什么是pending Intents)要投递。这比较适合非常容易就可以重新启动未完成任务的情况。
START_STICKY
在系统杀死服务后,重启服务并且调用onStartCommand(),但是不重新投递上一次的intent。系统会传给onStartCommand()函数null,除非有pending intent来启动服务,会传给onStartCommand()相应的intent。这种做法比较适合媒体播放服务,不需要执行命令,但是独立运行,常处于等待任务的服务。
START_REDELIVER_INTENT
在系统杀死服务后,重启服务并且调用onStartCommand(),参数传入上一次的intent,然后接下来是pending intent传入onStartCommand()。这比较适合于正在执行一项工作,它不能被立刻恢复,例如下载文件。
启动Service
你可以从一个Activity或者其他组件调用startService(intent)来启动服务。Android系统会调用服务的onStartCommand()函数并且传给它intent。
用上一节的HelloService来做个例子:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
startService()会立刻返回,Android系统会调用服务的onStartCommand()函数。如果这是服务没有在运行,系统会首先调用onCreate()函数,然后会紧接着调用onStartCommand()函数。
如果服务没有提供绑定,那么通过startService()函数传入的intent就是应用组件和服务进行交互的唯一途径。如果你想服务发送结果给客户组件,那么客户组件需要在启动服务时,创建一个用于广播的PendingIntent,通过intent投递给服务。这样,服务就可以利用广播把结果发送给客户组件。
多个请求会导致服务的onStartCommand()被调用多次。但是,只需要一个停止请求(stopSelf() 或 stopService())就会使服务停止。
停止服务
一个启动的服务必须管理自己的生命周期。因为除非系统需要回收资源的时候,系统不会停止或者销毁服务。所以,服务必须通过调用stopSelf()来停止自己,或者有其他组件调用stopService()。
一担收到stopSelf()或stopService()的停止请求,系统会立刻销毁服务。
如果你的服务同时处理多个服务请求,当某个请求完成时就不能马上停止服务,因为你可能已经又接受了一个新的请求(在第一个请求完成时结束服务会终止第二个请求)。为了解决这个问题,你可以调用stopSelf(int)函数来保证你的停止请求总是基于最近的一次服务请求。这是因为,调用stopSelf(int)函数
会把onStartCommand()函数传入的startId传给停止请求。当你的startId和最近一次接受的服务请求不匹配时,服务不会结束。
注意:stopSelf(int)函数只是简单的与最近一次收到的startId比较,如果你的服务处理过程是多线程的,可能后收到的服务请求先完成,那stopSelf(int)的方案不适合你,你应该手动管理接受到的服务请求和完成的服务,比如在onStartCommand()函数中把startId记录到一个表中,在完成服务任务时在表中记录完成状态,在确保表中任务都完成的情况下直接调用stopSelf()来停止服务。
在前台运行服务
一个前台服务是用户能够很明显意识到的服务,当可用内存比较低的时候,系统不会杀掉前台服务。一个前台服务必须提供一个状态栏通知,放在正在运行条目里,这意味着只要服务没有停止或者一直是前台服务,这个通知就不会被消失。
举个例子,一个音乐播放器服务应该被设置为前台运行,因为用户非常明显知道他在运行。这个状态栏通知可以显示正在播放的歌曲,并且可以允许用户点击进入音乐播放界面。
要使服务运行在前台只要调用startForeground()。这个方法有两个参数:一个通知的整数ID和一个状态栏通知。代码片段:
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);
使用stopForeground()函数可以把服务从前台移除。这个函数带一个布尔值参数,来表示是否从状态栏移除通知。这个函数不会使服务停止。如果把前台服务停止掉,那么通知也会同时被移除。