Service详解_StartedService

来源:互联网 发布:上海程序员培训哪里有 编辑:程序博客网 时间:2024/05/18 02:29

Service简介

Service是Android四大组件之一,它能在后台长时间运行,且不提供用户操作界面。其它组件Start一个Service后,即使用户切换到其它APP了,Service也能继续在后台运行;其它组件Bind到一个Service后,就可以和Service交互或进行进程间通信。例如,可以在一个后台Service中进行crash日志上传、用户信息搜集、播放音乐等。

Service类型

Service按照启动方式分为两种类型:
1、 Started Service:其它组件调用startService()启动Service后, Service会一直运行,即使启动它的组件已经被销毁了。直到Service调用stopSelf()或其它组件调用stopService(),Service才停止。最后,系统将它销毁。
2、 Bound Service:其它组件调用bindService()绑定到Service后,多个Client可以同时绑定到一个Service 。Client通过IBinder与Service通信,调用unbindService()与Service解绑。所有Client都解绑后,系统销毁Service。此Service不需要自己停止。

这两种类型也不是完全分开的,你可以绑定到一个用startService()启动的Service。例如,你用startService()启动一个播放音乐的Service,过一会用户想暂停播放音乐,那么有暂停按钮的Activity调用bindService()绑定到这个Service,用户点击“暂停”按钮后,Activity调用Service的暂停接口暂停播放音乐。这种情况下,需要所有Client都与Service解绑,且调用stopSelf()或stopService(),才能停止Service。

Service生命周期

一个Service从创建到销毁,其生命周期如下:
•Started Service:startService()——onCreate()——onStartCommand()——Service running——onDestroy()
•Bound Service:bindService()——onCreate()——onBind()——Clients are bound to service——onUnbind()——onDestroy()
这里写图片描述
图1:Service的生命周期,左图是Started Service,右图是Bound Service

上图只展示了Service的两种典型生命周期,实际中有些Service既可以start也可以bind,即onStartCommand()和onBind()可能都会运行。

Service常用回调函数

要创建一个Service,必须要继承Service类,并根据Service种类实现下面的回调函数:
1、 onStartCommand()
当其它组件用startService()启动Service时,调用此回调函数。如果Service只支持bind,不支持start,那么不需要实现这个回调函数。这个函数执行后,Service就被start,并且能在后台独立运行。
2、 onBind()
当其它组件用bindService()绑定Service时,调用此回调函数。这个函数返回一个IBinder,用于Client和Service交互。如果Service不支持bind,那么这个函数返回一个null。
3、 onCreate()
用startService()或bindService()首次启动Service时,调用此回调函数。此函数在Service的生命周期内只执行一次,所以你可以在这函数中做些初始化的操作,如创建线程、注册监听器、注册广播接收器等。
4、 onDestroy()
当Service不再被使用且将要销毁时,调用此回调函数。此函数是Service生命周期中执行的最后一个函数,所以你可以在这个函数中做清理资源的操作,如取消线程、注销监听器、注销广播接收器等。

创建一个Started Service

要创建Started Service,有两个类可以用于扩展:
•IntentService:它是Service类的子类,有一个工作线程来处理start请求,且每次只处理一个。如果您的Service不需要同时处理多个请求,那么最好使用这个类。实现onHandleIntent(),在此函数接受请求中的Intent,然后在后台执行任务。
•Service:这是所有Service的基类,如果用这个类创建Service,需要在Service中创建一个工作线程来完成耗时任务。Service运行在主线程,在Service中处理耗时任务,会阻塞主线程导致ANR问题。

扩展IntentService

如果Started Service不需要同时处理多个请求,那么最好继承IntentService来实现Service。
IntentService提供如下功能:
1、 创建一个工作线程,用来处理onStartCommand()传递过来的Intent,并执行对应的任务。耗时任务都在工作线程进行,不会导致ANR问题。
2、 创建一个工作队列,每次传递一个Intent到onHandleIntent(),所以你不需要担心多线程问题。
3、 所有请求都处理完后停止Service,所以Service不需要调用stopSelf()。
4、 提供一个默认实现的onBind(),并返回null。
5、 提供一个默认实现的onStartCommand(),把Intent发送给工作队列,最终传递给onHandleIntent()。
你只需要实现一个构造函数和重写onHandleIntent(),如下所示:

public class IntentServiceA extends IntentService {  /**   * A constructor is required, and must call the super IntentService(String)   * constructor with a name for the worker thread.   */  public IntentServiceA () {      super("IntentServiceA ");  }  /**   * 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.      try {          Thread.sleep(5000);      } catch (InterruptedException e) {          // Restore interrupt status.          Thread.currentThread().interrupt();      }  }}

如果您需要重写其他回调函数,如 onCreate()、onStartCommand()、onDestroy(),请注意要调用他们的super函数,以保证IntentService能正确处理生命周期。例如onStartCommand()必须返回一个默认的实现,以决定Intent传递给onHandleIntent()的方式。

@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();    return super.onStartCommand(intent,flags,startId);}

扩展Service

继承IntentService可以让Service的实现简单化,但是如果你的Service需要处理多线程的任务,那么需要继承Service。

下面的示例代码展示了继承Service来实现上个例子中用IntentService实现的功能。即对于每个start请求,使用一个工作线程来完成任务,并且每次只处理一个请求。

public class StartedService 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.          try {              Thread.sleep(5000);          } catch (InterruptedException e) {              // Restore interrupt status.              Thread.currentThread().interrupt();          }          // 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();  }}

如上所示,使用Service会比IntentService耗费更多的工作量。然而,每次调用onStartCommand()你都能处理,所以你能同时处理多个请求。上面的示例没有实现这个功能,如果你想实现它,可以为每个请求创建一个线程,并且立刻运行,而不是等前面的请求处理完后再处理下一个。

onStartCommand()必须返回一个值,这个值会决定Service被系统Kill后是否需要重启。IntentService已经默认帮你处理了,但是你也可以修改这个值。onStartCommand()的返回值必须是下面几个值之一:
**1、 START_NOT_STICKY:**onStartCommand()返回后,如果系统Kill掉Service,不再重新创建这个Service,直到有下一个Intent传递过来。这可以防止你的Service进行不必要的重启。
**2、 START_STICKY:**onStartCommand()返回后,如果系统Kill掉Service,重新创建这个Service并调用onStartCommand(),但是不传入最后一个Intent。系统会用null intent调用onStartCommand(),除非有下一个Intent来start服务。这适用于没有执行具体任务,一直运行并等待任务的Service,如media players。
**3、 START_REDELIVER_INTENT:**onStartCommand()返回后,如果系统Kill掉Service,重新创建这个Service并调用onStartCommand(),并且传入最后一个Intent。这适用于那些正在执行任务的,需要立刻恢复的Service,如下载文件。
在Manifest中申明Service
要创建一个Service,必须要在Manifest中申明这个Service,如下:

<manifest ... >  ...  <application ... >      <service android:name=".StartedService" />      ...  </application></manifest>

Service的常用属性:
android:name:是Service唯一必须的属性,它标识了定义Service的类。APP发布后,它应该保持不变,请参考 Things That Cannot Change。
android:exported:如果只在APP内部使用Service,则将此属性设置为false,这样可以阻止其它APP启动Service,你就不用担心安全问题。
android:description:关于Service的简洁描述。用户可能会停止设备上运行的未知Service,为了防止Service被用户停止,请用此属性设置Service的简单功能介绍。

注意:应该用显式Intent启动Service,不要给Service设置IntentFilter。如果Service支持隐式Intent启动,则会有安全风险,因为调用者不知道哪个Service将被启动。

Start一个Service

例如,一个Activity用startService()来启动Service。如下所示:

Intent intent = new Intent(this, StartedService.class);startService(intent);

startService()方法会立刻返回。如果Service本来没有在运行,那么会先调onCreate()再调onStartCommand();如果Service已经在运行,那么直接调用onStartCommand()。即多次start同一个Service,只会调用一次onCreate(),会多次调用onStartCommand(),最后只需调用一次stopSelf()或stopService()来停止Service。

如果Service不支持bind,那么传递给startService()的Intent是组件和Service交互的唯一接口。如果组件希望Service返回一个结果,那么传递给startService()的Intent应该包含与Broadcast关联的PendingIntent,Service就可以用广播来返回结果。

Stop一个Service

Started Service需要自己管理生命周期。它必须调用stopSelf()来自己stop,或其它组件调用stopService()来stop它,否则它会一直运行,直到系统低内存时销毁它。一旦调用了stopSelf()或stopService(),系统会立刻销毁它。

如果您的Service能同时处理多个请求,那么你在处理一个请求时不应该stop Service,因为在第一个请求结束时stop Service可能会终止下一个新的请求。要解决这个问题,请使用stopSelf(int),并传入onStartCommand()中startId,以保证stop Service是基于最近一次请求的。如果调用stopSelf(int)之前,Service接收到新的请求,那么两个startId不匹配,所以不stop Service。

注意:为了防止浪费系统资源和手机电量,请确保Service在完成任务后及时stop。如果有必要,组件可以调用stopService()来stop Service。即使Service提供了bind功能,只要调用了Service的onStartCommand(),都需要调用stopSelf()或stopService()来停止Service。

创建一个Bound Service

Bound Service允许组件通过bindService()与它绑定,并与组件建立一个长连接。通常它不允许组件用startService()来启动它。当应用内的组件(如Activity)需要和Service交互,或APP需要通过进程间通信(IPC)与其它APP交互,那么可以通过Bound Service来实现这些功能。

如果要创建Bound Service,需要实现onBind()回调函数,并返回一个定义Client和Service通信接口的IBinder。其它组件调用bindService()获取通信接口后,就能调用Service里的函数。Bound Service只服务于绑定到它的组件,所以当所有组件都解除绑定时,Service将被销毁。你不需要像停止Started Service一样停止一个Bound Service。

要创建一个Bound Service,你必须定义Client和Service通信的接口。这个接口必须继承IBinder,并且由Service的onBind()函数返回。Client获取到这个IBinder后就可以通过接口与Service交互。

多个Client可同时绑定到一个Service。Client完成与Service的交互后,需要调用unBindService()来解绑。当没有Client绑定到Service,系统会销毁Service。

实现Bound Service的方法和步骤较Started Service要更复杂,所以会在下一篇文章《Service详解_BoundService》里详细讲解Bound Service的实现。

启动一个前台Service

前台Service会一直受到用户的关注,并且系统低内存时不会被优先Kill。如果APP需要处理一个用户可感知的任务(即使用户不直接与它交互),那么就需要用前台Service来完成。
前台Service必须在通知栏的“正在运行”栏下显示一个通知,且除非Service被Stop或从前台移除,通知不会消失。这能让用户注意到APP当前正在做一些任务。

例如播放音乐的Service应该设置为运行在前台,因为用户一直关注其运行状态。在状态栏的通知应该显示当前正在播放的歌曲,并且允许用户启动对应的Activity来操作音乐播放器。同样的一个记录用户跑步轨迹的APP也应该使用前台Service来记录用户的位置。

用 startForeground()函数来启动前台服务。这个函数有两个参数:唯一标识Notification的id(不能为0)和需要显示到通知栏的Notification。如下所示:

Intent notificationIntent = new Intent(this, ExampleActivity.class);PendingIntent pendingIntent =        PendingIntent.getActivity(this, 0, notificationIntent, 0);Notification notification =          new Notification.Builder(this)    .setContentTitle(getText(R.string.notification_title))    .setContentText(getText(R.string.notification_message))    .setSmallIcon(R.drawable.icon)    .setContentIntent(pendingIntent)    .setTicker(getText(R.string.ticker_text))    .build();startForeground(ONGOING_NOTIFICATION_ID, notification);

使用 stopForeground()把Service从前台移除,但不会停止Service。此函数有个boolean的参数,标识是否把Service在通知栏展示的通知也一起移除。如果Service在前台运行时停止Service,通知栏的通知也会一起移除。

关于通知的更多资料,请参考Creating Status Bar Notifications。

示例代码

本文档所有示例代码下载路径:http://download.csdn.net/download/jennyliliyang/10155100