【Android API指南】App组件(6) - Services

来源:互联网 发布:淘客助手和淘宝联盟 编辑:程序博客网 时间:2024/04/28 06:51
 Service是一个执行长时间后台运行操作,并且不提供界面的程序组件。其他组件可以启动一个service,这个service会运行在后台,即使用户切换到了其他程序,service也不会停止。一个组件可以和service绑定来进行交互,甚至是进行进程间的通信(IPC)。例如,一个service可以处理网络连接,播放音乐,执行文件I/O,或者和一个content provider交互,这些都在后台运行。

一个service基本可以采取两种方式:

Started
当一个组件调用startService()来启动一个service时,service就是"Started"的,一旦被启动,service就会一直运行在后台,通常,一个启动的service执行一个单元的操作,并且没有返回值。例如,从网络下载一个文件。当操作完成,service就自动关闭了。

Bound
当一个组件使用bindService()来绑定一个service时,service就是"bound"的。一个绑定的service提供一个客户端-服务器的接口来让组件和它进行交互,发送请求,取得结果,进程间的交互。多个组件可以同时绑定一个service,当所有的组件都解除绑定时,service被销毁。

当然,一个service可以在被启动的同时也被绑定。只要你简单的实现一些回调就可以实现:onStartCommand()让组件启动它,onBind()让组件绑定它。

和activity一样,任何组件都可以使用service。当然你也可以在清单文件中定义service为私有的。

警告:一个service运行在它的宿主进程的主线程中 - service不创建它自己的线程,不运行在单独的进程中(除非你指定)。意思就是说,如果你的service执行任何高CPU负载工作或者阻塞操作(例如播放MP3或者联网),你就应该创建一个新的线程来让service工作。使用单独的线程,你将减少程序无响应的风险,程序主线程也可以专注于用户交互。

基础

你需要继承Service类来创建一个service,在你的实现中,你需要重写一些重要的回调函数:

onStartCommand()
当其他组件使用startService()来启动一个service时调用。一旦这个方法执行,service就被启动并一直运行在后台,当service完成工作时,要调用stopSelf()或者stopService()来停止service。如果service仅仅提供绑定,那么这个方法不需要实现。

onBind()
当其他组件使用bindService()绑定一个service时执行。这个方法的实现需要提供一个客户端和服务器的接口,通过返回IBinder来实现通信。如果你不允许绑定的话,这个方法需要返回null。

onCreate()
第一次创建时调用,只会执行一次,在上面的两个方法前执行。

onDestroy()
被销毁或者长时间不使用时调用这个方法。这个方法中主要是释放资源,例如关闭线程,注册的监听,接受者等等,是service最后调用的方法。

如果一个service是通过startService()启动的,那么这个service会一直运行,直到它调用stopSelf()或者其他组件调用stopService()来停止它。

如果是通过bindService()创建的service,一旦service被所有的客户端解除绑定时,系统就会销毁它。

系统会在急需内存资源时强制停止一个service,如果一个service绑定在一个正取得用户焦点的activity上,那么基本不会被销毁,如果一个service被声明为前台运行,那么它也不会被销毁。一个service随着启动时间的增加,系统会降低它的在后台任务的位置,从而有很大几率被销毁,一旦你的service被销毁,你必须很好的设计重启操作。当系统资源充足时,它会被马上重启。

下面的章节介绍每种启动service的方式和他们是怎么被其他组件使用的。

在清单文件中声明一个service
你必须在清单文件中声明所有的service:
<manifest ... >  ...  <application ... >      <service android:name=".ExampleService" />      ...  </application></manifest>
android:name是唯一必须声明的属性。一旦发布了程序,这个名称就不应该更换。

和activity一样,service也可以定义intent过滤器来让其他组件隐式的调用service。

如果你想让你的service是私有的,你需要声明android:exported属性为false。

创建一个Started Service

其他组件使用startService()传递一个intent来启动一个service,而service中的onStartCommand()方法接收intent并启动service。当service启动后,它有自己独立的生命周期。

例如,假设一个activity要保存数据到网络数据库,activity可以通过intent传递数据启动一个service,service接收intent,连接网络,更新数据库,当更新完成后,service自动停止和被销毁。

下面有两个你可以继承的service类:

Service
所有service的基类。当你继承这个类时,比较重要的一点是你应该创建一个新的线程来执行service的工作,因为默认service运行在程序主线程中,会降低程序的性能。

IntentService
service类的子类,使用一个工作线程来处理所有的启动请求,一次一个。如果你不需要同时处理多个请求,那么这个类是很好的选择。你只需要实现onHandleIntent()方法来接收intent,然后执行后台工作。

扩展IntentService类
因为大多数started service不需要同时处理多个请求,所以实现IntentService可能是做好的选择了。

它做下面这些事:
  • 创建一个独立于主线程的工作线程,执行传递给onStartCommand()的所有intent。
  • 创建一个工作队列,一次只传递一个intent给你的onHandleIntent(),所以你不用担心多线程。
  • 处理完所有的请求后停止service,你不需要调用stopSelf()。
  • 提供默认的返回null的onBind()的实现。
  • 提供默认的onStartCommand()的实现,它发生intent到工作队列,然后工作队列发送到onHandleIntent()中。
你只需在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.      long endTime = System.currentTimeMillis() + 5*1000;      while (System.currentTimeMillis() < endTime) {          synchronized (this) {              try {                  wait(endTime - System.currentTimeMillis());              } catch (Exception e) {              }          }      }  }}
如果你想重写onCreate,onStartCommand,或者onDestroy方法,你需要调用父类的实现,以便IntentService能很好的管理工作线程的生命周期。例如:
@Overridepublic 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允许绑定,那么你需要实现这个方法,默认返回null)。

扩展Service类
如果你想service能实现多线程的工作,那么你需要继承Service类。

为了做比较,下面的代码实现了上面的IntentService一样的工作,对于每个启动请求,它使用一个工作线程来处理,一次只处理一个。
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方法需要返回一个整型,这个整型值描述了当系统杀掉这个service后,系统会这么继续这个service。返回值是下面这些常量:

START_NOT_STICKY
如果系统在onStartCommand方法返回后杀掉service,那么不好重新创建这个service,除非有新的intent被传递过来。这是最安全的做法,避免service被重复创建,让程序可以重启未完成的工作。

START_STICKY
如果系统在onStartCommand方法返回后杀掉service,那么重新创建service,并调用onStartCommand方法,但是不重新传递最近的intent。而是使用null的intent来调用onStartCommand方法。除非有没有处理的intent来启动service,这种情况适合媒体播放,它不执行命令,但是运行在后台等待一个工作的来临。

START_REDELIVER_INTENT
如果系统在onStartCommand方法返回后杀掉service,使用最后传递的intent调用onStartCommand()来重新创建service。这种情况适合哪种需要立即回复工作的service,比如下载文件。

启动一个service
下面的例子使用显式的intent启动一个service:
Intent intent = new Intent(this, HelloService.class);startService(intent);
startService()方法会马上返回,然后系统调用service的onStartCommand()方法。如果service不是已经在运行,那么就会调用onCreate()方法。

如果service不提供绑定,那么startService()是传递intent给service是唯一的一个通信方式。不过,如果你想service返回一个结果,启动service的客户端可以创建一个PendingIntent来启动service,那么service就可以使用广播来传递一个结果。

多个启动请求会导致onStartCommand被多次调用,不过停止请求只需要一个。

停止一个service
一个started的service必须自己管理生命周期,需要自己调用stopSelf或者其他组件调用stopService来停止。调用后,系统会尽快的销毁这个service。

在多请求的情况下,我们可以使用stopSelf(int)来确保停止的是对应的ID的请求。

警告:在程序中停止service并释放资源非常重要。当你启动绑定service时,如果有onStartCommand的调用,你也需要自己停止service。

创建一个Bound Service

一个Bound Service允许程序组件调用bindService()来绑定,从而创建一个长时间的连接。

Bound service让你可以在组件间进行通信,而且通过进程通信,你的程序的部分功能会被展现给其他程序。

为了创建一个bound service,你必须实现onBind()方法,返回一个IBinder,这个IBinder定义了通信接口。其他程序组件通过调用bindService()来检索这个接口,然后在service上执行函数。当没有组件绑定到这个bound service时,会被系统销毁。

发送通知给用户

service可以通过Toast Notification或者Status Bar Notification来通知用户。

toast notification是出现在当前窗口的一个提示信息,会在一小段时间后消失,当status bar notification提供一个图标和信息在status bar时,用户可以点击图标来执行某个动作。

通常一个status bar notification是好的选择,当后台工作完成后,用户可以直接点击执行下面的工作。

在前台运行一个Service

前台service必须在status bar中提供一个通知,放在“运行中”的标题下面,除非service停止或者转入后台,不然这个通知不会消失。

例如,一个音乐播放器需要被设置为运行在前台,因为用户需要操作它。status bar可以显示当前播放歌曲,运行用户点击启动一个activity来与播放器交互。

调用startForeground()来让service运行在前台,这个方法有两个参数:整形的notification的唯一标示符和一个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, notification);
调用stopForeground()从前台移除service。需要一个布尔型参数来指定是否同时移除status bar的通知。这个方法不会停止service,不过,如果你停止运行在前台的service,那么通知也会消失。

提示:上面的方法在Android2.0才被引进,如果你想在更低版本中使用,你需要使用setForeground()方法。

管理service的生命周期

service的生命周期比activity简单,不过也需要注意,因为一个service可能在你不知道的情况下运行在后台。

它的生命周期包含下面两个重要的路径:
  • started service
    其他组件调用startService()时被创建。然后这个service会一直运行,直到自己调用stopSelf(),或者其他组件调用stopService。当service已经停止,系统消耗它。

  • bound service
    其他组件(客户端)调用bindService()时创建,客户端通过IBinder接口与service通信,调用unbindService()关闭连接。多个客户端可以绑定相同的service,当所有的客户端解除绑定后,系统自动消耗service。
这两个路径不是独立的,你可以绑定一个使用startService()启动的service。例如,一个背景音乐service可以被startService()启动,过了一会,你可能想执行一些控制或者取得一些当前歌曲信息,那么一个activity就可以绑定这个service,在这种情况下,stopService和stopSelf不能停止这个service,直到所有的客户端解除绑定。

实现生命周期回调
和activity一样,你可以实现回调函数来改变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生命周期的两个内嵌循环:
  • 整个生命时间是从onCreate()调用到onDestroy()返回。在onCreate中初始化,在onDestroy中释放资源。例如,一个音乐播放器service可以在onCreate()中创建线程来播放音乐,在onDestroy中停止线程。
  • 活动生命时间是从onStartCommand()或者onBind()被调用开始。如果service是被started的,那么活动时间和整个生命时间的停止时一样的。如果是service是bound的,活动时间在onUnbind返回时结束。
提示:虽然started service通过调用stopSelf()或者stopService()来停止,但是没有一个特别的回调函数,所以,除非service被绑定到了客户端,不然当service停止后系统就会销毁它,onDestroy()是唯一回调函数。

不管service是怎么启动的,都允许客户端绑定它。