Android基础终极篇--Service

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012585964/article/details/52485085

声明,此篇文档是根据API Guides稍加修改,方便在手机查看巩固基础而来

Service 是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。
服务基本上分为两种形式:

启动服务

当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。
一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 
已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后,服务会自行停止运行。

绑定服务

当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。
绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 
仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

无论应用是处于启动状态还是绑定状态,抑或处于启动并且绑定状态,任何应用组件均可像使用活动那样通过调用 Intent 来使用服务(即使此服务来自另一应用)。 不过,您可以通过清单文件将服务声明为私有服务,并阻止其他应用访问。 使用清单文件声明服务部分将对此做更详尽的阐述。

注意:服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。 
这意味着,如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应在服务内创建新线程来完成这项工作。
通过使用单独的线程,可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。

在清单文件中声明服务

如同 Activity(以及其他组件)一样,也必须在应用的清单文件中声明所有服务。

添加 < service >元素作为< application > 元素的子元素。例如:

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

< service >元素有以下属性:

<service android:enabled=["true" | "false"]
         android:exported=["true" | "false"]
         android:icon="drawable resource"
         android:isolatedProcess=["true" | "false"]
         android:label="string resource"
         android:name="string"
         android:permission="string"
         android:process="string" >
    . . .
</service>

android:enabled

服务是否能被系统实例化 —“true”可以,“false”不允许。 默认值是“true”。
< application > 元素拥有自己的 enabled 属性,适用于应用程序所有的内部组件,包括服务。 
服务要能被启用, < application> 和 < service> 的此属性都必须设置为“true”(均为默认值即可)。

android:exported

其它应用程序的组件能否调用服务或与服务交互 — “true”可以,“false”不可以。 
如果设为“false”,则只有本应用程序或用户 ID 相同程序的组件才能启动或绑定该服务。
默认值取决于服务是否包含 Intent 过滤器。 
如果不含任何过滤器,该服务仅供应用程序内部使用(因为其他应用程序通常不知道确切的类名称)。这时的默认值是“false”。 
如果存在一个以上的过滤器,则表示服务愿意被外部使用,因此默认值是“true”。

本属性并不是限制服务对其他应用程序的公开程度的唯一途径。 还可以利用权限机制对可与服务进行交互的外部对象进行限制(请参阅permission属性)。

为了确保应用的安全性,请始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器。 
启动哪个服务存在一定的不确定性,而如果对这种不确定性的考量非常有必要,则可为服务提供 Intent 过滤器并从 Intent 中排除相应的组件名称,
但随后必须使用 setPackage() 方法设置 Intent 的软件包,这样可以充分消除目标服务的不确定性。
或者添加 android:exported 属性并将其设置为 "false",确保服务仅适用于您的应用。这可以有效阻止其他应用启动您的服务,即便在使用显式 Intent 时也如此。

android:icon

代表服务的图标。 
本属性必须设为对 drawable 资源的引用,该资源包含了图片的定义。 如果未设置本属性值,则会用全局性的应用程序图标来代替。
服务的图标不管是在此处还是在< application> 元素中设置的,同时也是本服务中所有 Intent 过滤器的默认图标

android:isolatedProcess

如果设为 true ,则本服务将会运行于一个特殊的进程中。 该进程与系统其他部分隔离,且没有自己的权限。 与其通讯的唯一手段就是通过 Service API (绑定和启动)。

android:label

供用户查看的服务名称。 如果未设置本属性,则用全局性的应用程序文本标签代替。
服务的文本标签 — 不管是在在此处还是在 < application> 元素中设置的, 同时也是本服务中所有 Intent 过滤器的默认文本标签。

android:name

实现服务的 Service 子类的名称。 这应该是一个完全限定格式的类名(比如“com.example.project.RoomService”)。 
不过作为简称,如果首字符为句点(比如“.RoomService”),则会在前面自动加上 < manifest> 元素定义的包名称。
应用程序一经发布,就 不应再更改名称 (除非设置了 android:exported="false")。
没有默认值。本名称必须指定。

android:permission

要启动或绑定服务的对象所必须拥有的权限名称。 
如果 startService()、 bindService()或 stopService() 的调用者未获得本属性设定的权限,这些方法将会失效,Intent 对象也不会分发给本服务。
如果本属性未被设置,则会把 < application> 元素的 permission 属性所定义的权限应用于本服务。 如果两个地方的属性都没有设置,则本服务将不受权限机制的保护。

android:process

运行服务的进程名称。 通常,应用程序的所有组件都运行在创建时的默认进程中。
该进程的名称与程序包名相同。 < application> 元素的 process 属性可以为每个组件设置不同的默认进程。 
但每个组件也可以用各自的 process 属性覆盖默认设置,使得程序可以跨越多个进程运行。
如果本属性设置的名称以冒号(':')开头,则必要时会新建一个属于该程序私有的进程,服务将在该新进程中运行。 
如果进程名称以小写字母开头,则服务将运行于一个以此名字命名的全局进程中,并赋予应有的访问权限。 这就允许分属于不同应用程序的多个组件共享同一个进程,以减少资源的占用。

启动服务

从传统上讲,您可以扩展两个类来创建启动服务:

Service

这是适用于所有服务的基类。
扩展此类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。

IntentService

这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。
只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。

扩展 IntentService 类

由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。

IntentService 执行以下操作:

创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
创建工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。
提供 onBind() 的默认实现(返回 null)。
提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。

综上所述,只需实现 onHandleIntent() 来完成客户端提供的工作即可。(另外,还需要为服务提供一个构造函数。)
以下是 IntentService 的实现示例:

public class HelloIntentService extends IntentService {
 //您只需要一个构造函数和一个 onHandleIntent() 实现即可。
  /** 
   * 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 能够妥善处理工作线程的生命周期。
//除 onHandleIntent() 之外,您无需从中调用超类的唯一方法就是 onBind()(仅当服务允许绑定时,才需要实现该方法)。

//例如,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);
} 

扩展 Service 类

使用 IntentService 显著简化了启动服务的实现,但是,若要求服务执行多线程(而不是通过工作队列处理启动请求),则可扩展 Service 类来处理每个 Intent。

为了便于比较,以下提供了 Service 类实现的代码示例,该类执行的工作与上述使用 IntentService 的示例完全相同。也就是说,对于每个启动请求,它均使用工作线程执行作业,且每次仅处理一个请求。:

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  //  服务实现一个 Handler
  private final class ServiceHandler extends Handler { 
      public ServiceHandler(Looper looper) {
          super(looper);
      } 
      @Override 
      public void handleMessage(Message msg) {
          // 执行任务, 比如下载文件. 
          // 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() { 
    // 因为服务运行在主线程中,又不希望主线程阻塞,所以新建一个线程运行服务 We also make it 
    // 我们还让他优先在后台运行,这样CPU密集型任务不会阻塞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);

      //如果任务中断,从这里重启任务 
      return START_STICKY;
  } 

  @Override 
  public IBinder onBind(Intent intent) {
      // 因为不提供绑定服务,所以返回null 
      return null; 
  } 

  @Override 
  public void onDestroy() { 
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  } 
} 

与使用 IntentService 相比,这需要执行更多工作。

但是,因为是由您自己处理对 onStartCommand() 的每个调用,因此可以同时执行多个请求。此示例并未这样做,但如果您希望如此,则可为每个请求创建一个新线程,然后立即运行这些线程(而不是等待上一个请求完成)。

请注意,onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService 的默认实现将为您处理这种情况,不过您可以对其进行修改)。从 onStartCommand() 返回的值必须是以下常量之一:

START_NOT_STICKY:

如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。
这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。

START_STICKY:

如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但绝对不会重新传递最后一个 Intent。
相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。
这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。

START_REDELIVER_INTENT:

如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。
任何挂起 Intent 均依次传递。
这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

启动服务

您可以通过将 Intent(指定要启动的服务)传递给 startService(),从 Activity 或其他应用组件启动服务。Android 系统调用服务的 onStartCommand() 方法,并向其传递 Intent。

例如,Activity 可以结合使用显式 Intent 与 startService(),启动上文中的示例服务 (HelloSevice):

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

startService() 方法将立即返回,且 Android 系统调用服务的 onStartCommand() 方法。如果服务尚未运行,则系统会先调用 onCreate(),然后再调用 onStartCommand()。

如果服务亦未提供绑定,则使用 startService() 传递的 Intent 是应用组件与服务之间唯一的通信模式。
但是,如果您希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent (使用 getBroadcast()),并通过启动服务的 Intent 传递给服务。
然后,服务就可以使用广播传递结果。

多个服务启动请求会导致多次对服务的 onStartCommand() 进行相应的调用。但是,要停止服务,只需一个服务停止请求(使用 stopSelf() 或 stopService())即可。

停止服务

启动服务必须管理自己的生命周期。也就是说,除非系统必须回收内存资源,否则系统不会停止或销毁服务,而且服务在 onStartCommand() 返回后会继续运行。因此,服务必须通过调用 stopSelf() 自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。

一旦请求使用 stopSelf() 或 stopService() 停止服务,系统就会尽快销毁服务。

但是,如果服务同时处理多个 onStartCommand() 请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已经收到了新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为了避免这一问题,您可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。也就说,在调用 stopSelf(int) 时,传递与停止请求的 ID 对应的启动请求的 ID(传递给 onStartCommand() 的 startId) 。然后,如果在您能够调用 stopSelf(int) 之前服务收到了新的启动请求, ID 就不匹配,服务也就不会停止。

注意:为了避免浪费系统资源和消耗电池电量,应用必须在工作完成之后停止其服务。 如有必要,其他组件可以通过调用 stopService() 来停止服务。即使为服务启用了绑定,一旦服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务。

绑定服务

创建提供绑定的服务时,您必须提供 IBinder,用以提供客户端用来与服务进行交互的编程接口。 您可以通过三种方法定义接口:

使用Binder

如果服务是供您的自有应用专用,并且在与客户端相同的进程中运行(常见情况),则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。
客户端收到 Binder 后,可利用它直接访问 Binder 实现中乃至 Service 中可用的公共方法。
如果服务只是您的自有应用的后台工作线程,则优先采用这种方法。 不以这种方式创建接口的唯一原因是,您的服务被其他应用或不同的进程占用。

使用 Messenger

如需让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口。
服务可以这种方式定义对应于不同类型 Message 对象的 Handler。
此 Handler 是 Messenger 的基础,后者随后可与客户端分享一个 IBinder,从而让客户端能利用 Message 对象向服务发送命令。
此外,客户端还可定义自有 Messenger,以便服务回传消息。
这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。

使用 AIDL

AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 IPC。
之前采用 Messenger 的方法实际上是以 AIDL 作为其底层结构。如上所述,Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。
不过,如果您想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。
如需直接使用 AIDL,您必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,您随后可在服务内对其进行扩展。

注:大多数应用“都不会”使用 AIDL 来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL 并不适合大多数应用,本文也不会阐述如何将其用于您的服务。如果您确定自己需要直接使用 AIDL,请参阅 AIDL 文档。

使用Binder 创建绑定服务

如果您的服务仅供本地应用使用,不需要跨进程工作,则可以实现Binder 类,让客户端通过该类直接访问服务中的公共方法。

注:此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。 例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效。

以下是具体的设置方法:

1>在您的服务中,创建一个可满足下列任一要求的 Binder 实例:
    包含客户端可调用的公共方法
    返回当前 Service 实例,其中包含客户端可调用的公共方法
    或返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法

2>从 onBind() 回调方法返回此 Binder 实例。

3>在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务。

注:之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其 API。服务和客户端还必须在同一进程内,因为此方法不执行任何跨进程编组。

例如,以下这个服务可让客户端通过 Binder 实现访问服务中的方法:

public class LocalService extends Service {
    // 2> Binder,让客户端通过该对象直接访问服务中的公共方法
    private final IBinder mBinder = new LocalBinder();
    // Random number generator 
    private final Random mGenerator = new Random();

    /** 1> 实现Binder 类,返回当前 Service 实例
     * Class used for the client Binder.  Because we know this service always 
     * runs in the same process as its clients, we don't need to deal with IPC. 
     */ 
    public class LocalBinder extends Binder {
        LocalService getService() { 
            // Return this instance of LocalService so clients can call public methods 
            return LocalService.this;
        } 
    } 

    // 3> 从 onBind() 回调方法返回此 Binder 实例。
    @Override 
    public IBinder onBind(Intent intent) {
        return mBinder;
    } 

    /**4> 客户端可调用的公共方法 */ 
    public int getRandomNumber() { 
      return mGenerator.nextInt(100);
    } 
} 

LocalBinder 为客户端提供 getService() 方法,以检索 LocalService 的当前实例。这样,客户端便可调用服务中的公共方法。 例如,客户端可调用服务中的 getRandomNumber()。

点击按钮时,以下这个 Activity 会绑定到 LocalService 并调用 getRandomNumber():

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    } 

    @Override 
    protected void onStart() { 
        super.onStart(); 
        // 7> 绑定LocalService 
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    } 

    @Override 
    protected void onStop() { 
        super.onStop(); 
        // 8> 解绑服务 
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        } 
    } 

    /** Called when a button is clicked (the button in the layout file attaches to 
      * this method with the android:onClick attribute) */ 
    public void onButtonClick(View v) {
        if (mBound) {
            // 6> 使用提供的方法调用绑定服务 
            // However, if this call were something that might hang, then this request should 
            // occur in a separate thread to avoid slowing down the activity performance. 
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        } 
    } 
    // 5> 在客户端中,从 onServiceConnected() 回调方法接收 Binder。
    /** Defines callbacks for service binding, passed to bindService() */ 
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override 
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance 
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        } 

        @Override 
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        } 
    }; 
} 

上例说明了客户端如何使用 ServiceConnection 的实现和 onServiceConnected() 回调绑定到服务。

注:上例并未显式取消与服务的绑定,但所有客户端都应在适当的时间(例如当 Activity 暂停时)取消绑定。

如需查看更多示例代码,请参阅 ApiDemos 中的 LocalService.java 类和 LocalServiceActivities.java 类。

使用 Messenger

如需让服务与远程进程通信,则可使用 Messenger 为您的服务提供接口。利用此方法,您无需使用 AIDL 便可执行进程间通信 (IPC)。

以下是 Messenger 的使用方法摘要:

1>服务实现一个 Handler,由其接收来自客户端的每个调用的回调

2>用于创建 Messenger 对象(对 Handler 的引用)

3>Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端

4>客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务

5>服务在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message

这样,客户端并没有调用服务的“方法”。而客户端传递的“消息”(Message 对象)是服务在其 Handler 中接收的。

以下是一个使用 Messenger 接口的简单服务示例:

public class MessengerService extends Service {
    /** Command to the service to display a message */ 
    static final int MSG_SAY_HELLO = 1;

    /**
     * 1>服务实现一个 Handler,由其接收来自客户端的每个调用的回调 
     */ 
    class IncomingHandler extends Handler { 
        @Override 
        public void handleMessage(Message msg) {
        //6>服务在 handleMessage() 方法中接收每个 Message
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break; 
                default: 
                    super.handleMessage(msg);
            } 
        } 
    } 

    /** 
     * 2>用于创建 Messenger 对象(对 Handler 的引用) 
     */ 
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**3>Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端 
     * When binding to the service, we return an interface to our messenger 
     * for sending messages to the service. 
     */ 
    @Override 
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    } 
} 

请注意,服务就是在 Handler 的 handleMessage() 方法中接收传入的 Message,并根据 what 成员决定下一步操作。

客户端只需根据服务返回的 IBinder 创建一个 Messenger,然后利用 send() 发送一条消息。例如,以下就是一个绑定到服务并向服务传递 MSG_SAY_HELLO 消息的简单 Activity:

public class ActivityMessenger extends Activity {
    /** Messenger for communicating with the service. */ 
    Messenger mService = null;

    /** Flag indicating whether we have called bind on the service. */ 
    boolean mBound;

    /** 4>客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化
     * Class for interacting with the main interface of the service. 
     */ 
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // This is called when the connection with the service has been 
            // established, giving us the object we can use to 
            // interact with the service.  We are communicating with the 
            // service using a Messenger, so here we get a client-side 
            // representation of that from the raw IBinder object. 
            mService = new Messenger(service);
            mBound = true;
        } 

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been 
            // unexpectedly disconnected -- that is, its process crashed. 
            mService = null;
            mBound = false;
        } 
    }; 

    public void sayHello(View v) {
        if (!mBound) return;
        // Create and send a message to the service, using a supported 'what' value 
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
        // 5> 将 Message 对象发送给服务 
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        } 
    } 

    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    } 

    @Override 
    protected void onStart() { 
        super.onStart(); 
        // 7> 绑定服务 
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    } 

    @Override 
    protected void onStop() { 
        super.onStop(); 
        // 8> 解绑服务 
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        } 
    } 
} 

请注意,此示例并未说明服务如何对客户端作出响应。如果您想让服务作出响应,则还需要在客户端中创建一个 Messenger。然后,当客户端收到 onServiceConnected() 回调时,会向服务发送一条 Message,并在其 send() 方法的 replyTo 参数中包含客户端的 Messenger。

如需查看如何提供双向消息传递的示例,请参阅 MessengerService.java(服务)和 MessengerServiceActivities.java(客户端)示例。

使用AIDL

AIDL(Android Interface Definition Language)接口定义语言。它定义一个编程接口,这个接口是客户端与服务端都约定一致的用来跨进程通信。

注意:只有当允许让来自不同应用的客户端跨进程通信(IPC)访问你的服务并且在你的服务中有多个线程需要处理,才有必要使用AIDL。
如果没有必要处理并发跨进程(IPC)访问不同的应用,应该通过实现Binder接口,或者如果你想执行跨进程通信,但又没有必要处理多线程, 使用Messager 来实现接口。

AIDL接口的调用采用的是直接的函数调用方式,但你无法预知哪个进程(或线程)将调用该接口。同进程的线程调用和其他进程调用该接口之间是有所区别的:

在同进程中调用AIDL接口,AIDL接口代码的执行将在调用该AIDL接口的线程中完成,
如果在主UI线程中调用AIDL接口,那么AIDL接口代码的执行将会在这个主UI线程中完成。
如果是其他线程,AIDL接口代码的执行将在service中完成。
因此,如果仅仅是本进程中的线程访问该服务,你完全可以控制哪些线程将访问这个服务(但是如果是这样,那就完全没必要使用AIDL了,采取Binder接口的方式更为合适)。

远程进程(其他线程)调用AIDL接口时,将会在AIDL所属的进程的线程池中分派一个线程来执行该AIDL代码,
所以编写AIDL时,你必须准备好可能有未知线程访问、同一时间可能有多个调用发生(多个线程的访问),
所以ADIL接口的实现必须是线程安全的。

可以用关键字oneway来标明远程调用的行为属性,如果使用了该关键字,那么远程调用将仅仅是调用所需的数据传输过来并立即返回,而不会等待结果的返回,也即是说不会阻塞远程线程的运行。
AIDL接口将最终将获得一个从Binder线程池中产生的调用(和普通的远程调用类似)。
如果关键字oneway在本地调用中被使用,将不会对函数调用有任何影响。

简单使用AIDL

AIDL接口使用后缀名位.aidl的文件来定义,.aidl文件使用java语法编写,并且将该.aidl文件保存在 src/目录下(无论是服务端还是客户端都得保存同样的一份拷贝,也就是说只要是需要使用到该AIDL接口的应用程序都得在其src目录下拥有一份.aidl文件的拷贝)。

要创建一个通过AIDL绑定的服务,采用下面的步骤。

1>创建.aidl文件。这个文件定义了带有方法签名的接口

2>实现该接口。Android SDK工具会根据你的.aidl文件以Java编程语言生成一个接口。
该接口有一个叫做Stub的内部抽象类, 它继承自Binder并且实现了你在AIDL接口中定义的方法。你必须继承Stub类然后实现它的方法。

3>暴露接口给客户端。实现一个 Service并且覆盖 onBind() 方法,返回你针对Stub类的实现.

1、创建.aidl文件

每一个.aidl文件只能定义一个接口,只需要申明该接口的方法(不需要实现)。

AIDL默认支持下面几种数据类型:

1>Java编程语言中的基础数据类型(int,long,char,boolean等)

2>String

3>CharSequence

4>List。在List 中的所有元素必须是上面支持的类型或者是其他由AIDL生成的接口,或者你申明的实现了Parcelable接口的类型。
List可能被选用为泛型类,比如 List< String>.实际在接受服务一侧生成的类为永远是 ArrayList。尽管生成的方法使用的是 List接口。

5>Map。Map中的虽有元素必须是上面类型或者是其他由AIDL生成的接口,或者你申明的实现了Parcelable接口的类型。
泛型例如 Map<String,Integer>不支持。实际在接受服务一侧生成的类为永远是HashMap。尽管生成的方法使用的是 Map接口。

必须要为每一个上面未列出类型添加import申明,尽管他们是作为接口定义在同一个包里面。

当定义服务接口:

方法可以带有0个或者多个参数,带有返回值或者没有返回值
所有的非基础数据类型参数需要一个额外的用于标明参数去向的标记。可以是in、out或者inout(参见下面的例子)。基础数据类型默认是in.而不能选择其他方式。
需要限制确实需要的方向,因为组装参数的开销很大。
所有包含在.aidl文件中的代码注释也就会包生成的 IBinder 接口中,除了导包语句之前的声明的注释。
在AIDL中仅支持申明方法,不能申明静态域。

下面是一个.aidl文件示例:

// IRemoteService.aidl
package com.example.android;

// 声明一个没有import的aidl

/** Example service interface */
interface IRemoteService {
    /** 获取service的进程ID, to do evil things with it. */
    int getPid();

    /** 参数可以使用的基本类型,和AIDL的返回值基本类型
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

只需要将.aidl文件保存到项目的src/目录下面,当构建项目的时候SDK工具会在项目的gen/目录下生成IBinder接口文件,生成的文件名与.aidl文件名相匹配,但是是以.java扩展名结尾(例如IRemoteService.aidl会生成IRemoteService.java)

如果使用的是Android Studio,增量构建几乎会立即生成Binder类。如果你没有使用Android Studio,Gradle 工具会在你下次构建应用时生成的binder类。一旦你写完了.aidl文件,你应该用gradle assembleDebug或者gradle assembleRelease构建项目,这样才能让你的代码链接到生成的类上。

2、实现该接口

当构建应用的时候,Android SDK工具生成一个根据.aidl文件生成.java接口,生成的接口包含一个叫做的Stub内部类,这个类是父接口(比如YourInterface.Stub)的抽象实现,并且申明了所有来自.aidl文件的方法。

注意:Stub也定义了一些帮助方法,比较常用的是asInterface(),这个方法传入一个 IBinder (这个参数通常传入到客户端的的onServiceConnected()回调方法中)并且返回一个Stub的实例。
为了实现由.aidl生成的接口,继承生成的Binder接口,例如YourInterface.Stub,然后实现继承自.aidl文件中的方法。

我们可以新建一个类RemoteService, 继承服务类Service,然后在里面对接口进行实现。下面是一个调用名为IRemoteService 接口的实现。(在上面的例子 IRemoteService.aidl中定义),使用匿名实例:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

现在mBinder是Stub 类的实例,它定义了该服务的跨进程通信接口,在下一步,这个实例会暴露给客户端这样客户端便能与服务端交互。

下面是在实现AIDL接口是应当注意的规则:

传入的调用不一定保证会在主线程中执行,因此你需要考虑多线程从开始到构建服务保证线程安全

默认情况下,远程过程调用(Remote Procedure Call,缩写为 RPC)是同步的,如果知道服务会花费超过数秒来完成一个请求,你不应当在activity 的主线中调用该方法,
因为这可能会导致应用挂起(Android应用可能会显示应用无法响应的对话框),通常你应该在客户端的一个单独的线程中调用该方法。

不会有异常回传给调用者

3、暴露接口给客户端

一旦你实现了服务接口,你需要将它暴露给客户端这样客户端就能绑定服务。为了暴露接口给服务,继承服务类Service 并实现 onBind()以返回一个实现了生成的Stub(前面所讨论).

下面是一个暴露IRemoteService实例接口给服务端的示例:

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }
//3、暴露接口给客户端
    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }
//2、实现该接口
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

4、客户端绑定该服务

现在,当客户端例如Activity调用bindService()绑定到该服务,客户端的 onServiceConnected() 回调方法接受一个service的 onBind()方法返回的mBinder实例。

客户端必须访问接口类,如果客户端和服务端处在不同的应用中,客户端必须具有一个应用必须具有一份.aidl的拷贝并将其存放于src/目录下面。(Android SDK)会生成android.os.Binder接口。提供客户端访问AIDL的方法。

当客户端接收到 onServiceConnected() 回调方法中 IBinder .它必须调用YourServiceInterface.Stub.asInterface(service)将其转换成YourServiceInterface 类型。示例如下:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};

了解更多的示例代码参考 ApiDemos中的 RemoteService.java 类.

通过IPC传递对象

如果想要通过IPC接口将一个类对象从一个进程传递给另外一个进程,这个可以实现,但是必须得保证这个类在IPC两端的有效性,并且该类必须支持Parcelable接口。支持Parcelable接口非常重要,因为这允许Android系统将Object拆解为原始数据类型,这样才能达到跨进程封送(序列化发送)。

要想创建一个支持 Parcelable协议的类,需要如下几个步骤:

1,创建一个类并实现 Parcelable接口

2,实现 writeToParcel方法,这个方法将当前对象的状态写入到 Parcel中。

3,添加一个名为CREATOR的静态对象到类里面,这个对象实现了 Parcelable.Creator接口。

4,最后,创建一个.aidl文件,申明你的parcelable类(如下面的 Rect.aidl文件所示)。

如果你正在使用一个自定义构建进程,不需要添加.aidl文件到你的构建。跟C语言的头文件很相似,.aidl文件不会编译。
AIDL使用这些所生成的代码方法和成员来组装和拆解你的对象。

例如。下面是一个Rect.aidl文件来创建一个可组装和拆解的Rect类.

//4,最后,创建一个.aidl文件,申明你的parcelable类
package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

下面是Rect类如何实现 Parcelable 协议的

import android.os.Parcel;
import android.os.Parcelable;
//1,创建一个类并实现 Parcelable接口.
public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;
//3,添加一个名为CREATOR的静态对象到类里面,这个对象实现了 Parcelable.Creator接口。
    public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }
//2,实现 writeToParcel方法,这个方法将当前对象的状态写入到 Parcel中。
    public void writeToParcel(Parcel out) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }
}

对Rect类的组装非常简单,查看 Parcel 上的其他方法可以知道你能向Parcel中写入其他类型的值。

警告:不要忘记接受来自其他进程数据的安全性。在该例子中,Rect读取来自 Parcel的四个数字。但是这取决于你确保这些值是在一个可接受的范围类,无论客户端尝试做什么。参考 安全和权限了解更多有关如何保证你的应用免于恶意软件破坏的信息。

调用AIDL接口的方法

下面是一个类调用由远程接口AIDL定义的方法的步骤:

1,将.aidl文件放入项目的src/aidl目录下面。

2,声明IBinder接口实例(该示例基于AIDL生成)

3,实现 ServiceConnection.

4,调用 Context.bindService(),传入你 ServiceConnection 的实现。

5,在 onServiceConnected()的实现中,你会收到一个IBinder的实例(也叫service),
调用YourInterfaceName.Stub.asInterface((IBinder)service)将 onServiceConnected()回调方法中的IBinder参数转换成YourInterface 类型。

6,调用你在接口中定义的方法。你始终要要捕获 DeadObjectException 异常,这个异常会在链接断开时抛出。这个异常只会有远程方法抛出.

7,如果想断开链接,用你接口的实例调用 Context.unbindService()

关于调用跨进程服务的一点说明:

对象跨进程采取引用计数方式
可以发送匿名对象作为方法的参数

下面是摘自ApiDemos项目中远程服务范例部分代码展示如何调用AIDL创建的服务。

public static class Binding extends Activity {
    /** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary mSecondaryService = null;

    Button mKillButton;
    TextView mCallbackText;

    private boolean mIsBound;

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button clicks.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(mUnbindListener);
        mKillButton = (Button)findViewById(R.id.kill);
        mKillButton.setOnClickListener(mKillListener);
        mKillButton.setEnabled(false);

        mCallbackText = (TextView)findViewById(R.id.callback);
        mCallbackText.setText("Not attached.");
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            mKillButton.setEnabled(true);
            mCallbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mKillButton.setEnabled(false);
            mCallbackText.setText("Disconnected.");

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection mSecondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            mSecondaryService = ISecondary.Stub.asInterface(service);
            mKillButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            mSecondaryService = null;
            mKillButton.setEnabled(false);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names.  This allows other applications to be
            // installed that replace the remote service by implementing
            // the same interface.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, mSecondaryConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
            mCallbackText.setText("Binding.");
        }
    };

    private OnClickListener mUnbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (mIsBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // has crashed.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(mSecondaryConnection);
                mKillButton.setEnabled(false);
                mIsBound = false;
                mCallbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener mKillListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently our service has a call that will return
            // to us that information.
            if (mSecondaryService != null) {
                try {
                    int pid = mSecondaryService.getPid();
                    // Note that, though this API allows us to request to
                    // kill any process based on its PID, the kernel will
                    // still impose standard restrictions on which PIDs you
                    // are actually able to kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here; packages
                    // sharing a common UID will also be able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    mCallbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // Just for purposes of the sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }

    };
}

绑定服务

应用组件(客户端)可通过调用 bindService() 绑定到服务。Android 系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder。

绑定是异步的。bindService() 会立即返回,“绝对不会”使 IBinder 返回客户端。要接收 IBinder,客户端必须创建一个 ServiceConnection 实例,并将其传递给 bindService()。ServiceConnection 包括一个回调方法,系统通过调用它来传递 IBinder。

注:只有 Activity、服务和内容提供程序可以绑定到服务—您无法从广播接收器绑定到服务。

因此,要想从您的客户端绑定到服务,您必须:

实现 ServiceConnection。
您的实现必须重写两个回调方法:
    onServiceConnected() 系统会调用该方法以传递服务的 onBind() 方法返回的 IBinder。
    onServiceDisconnected() Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统“绝对不会”调用该方法。
调用 bindService() 以传递 ServiceConnection 实现。

当系统调用您的 onServiceConnected() 回调方法时,您可以使用接口定义的方法开始调用服务。
要断开与服务的连接,请调用 unbindService()。
当您的客户端被销毁时,它将取消与服务的绑定,但您应该始终在完成与服务的交互时或您的 Activity 暂停时取消绑定,以便服务能够在未被占用时关闭。
例如,以下代码段通过扩展 Binder 类将客户端与上面创建的服务相连,因此它只需将返回的 IBinder 转换为 LocalService 类并请求 LocalService 实例:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established 
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit 
        // service that is running in our own process, we can 
        // cast its IBinder to a concrete class and directly access it. 
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true; 
    } 

    // Called when the connection with the service disconnects unexpectedly 
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false; 
    } 
}; 

客户端可通过将此 ServiceConnection 传递至 bindService() 绑定到服务。例如:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

bindService() 的第一个参数是一个 Intent,用于显式命名要绑定的服务(但 Intent 可能是隐式的)
第二个参数是 ServiceConnection 对象
第三个参数是一个指示绑定选项的标志。它通常应该是 BIND_AUTO_CREATE,以便创建尚未激活的服务。 其他可能的值为 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示无)。

以下是一些有关绑定到服务的重要说明:

您应该始终捕获 DeadObjectException 异常,它们是在连接中断时引发的。这是远程方法引发的唯一异常
对象是跨进程计数的引用
您通常应该在客户端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 时刻期间配对绑定和取消绑定。 例如:
如果您只需要在 Activity 可见时与服务交互,则应在 onStart() 期间绑定,在 onStop() 期间取消绑定。
如果您希望 Activity 在后台停止运行状态下仍可接收响应,则可在 onCreate() 期间绑定,在 onDestroy() 期间取消绑定。
请注意,这意味着您的 Activity 在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当您提高该进程的权重时,系统终止该进程的可能性会增加

注:通常情况下,切勿在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,您应该使发生在这些转换期间的处理保持在最低水平。此外,如果您的应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务。 (Activity文档中介绍了这种有关 Activity 如何协调其生命周期的 Activity 转换。)

如需查看更多显示如何绑定到服务的示例代码,请参阅 ApiDemos 中的 RemoteService.java 类。

第三部分

向用户发送通知

一旦运行起来,服务即可使用 Toast 通知或状态栏通知来通知用户所发生的事件。

Toast 通知是指出现在当前窗口的表面、片刻随即消失不见的消息,而状态栏通知则在状态栏提供内含消息的图标,用户可以选择该图标来采取操作(例如启动 Activity)。

通常,当某些后台工作已经完成(例如文件下载完成)且用户现在可以对其进行操作时,状态栏通知是最佳方法。 当用户从展开视图中选定通知时,通知即可启动 Activity(例如查看已下载的文件)。

在前台运行服务

前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,状态栏位于“正在进行”标题下方,这意味着除非服务停止或从前台删除,否则不能清除通知。

例如,应该将从服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。

要请求让服务运行于前台,需要调用 startForeground()。此方法有两个参数:唯一标识通知的整型数和状态栏的 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()。此方法取一个布尔值,指示是否也删除状态栏通知。 此方法绝对不会停止服务。 但是,如果您在服务正在前台运行时将其停止,则通知也会被删除。

管理服务生命周期

服务的生命周期比 Activity 的生命周期要简单得多。但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户没有意识到的情况下运行于后台。

服务生命周期(从创建到销毁)可以遵循两条不同的路径:

启动服务

该服务在其他组件调用 startService() 时创建,然后无限期运行,且必须通过调用 stopSelf() 来自行停止运行。
此外,其他组件也可以通过调用 stopService() 来停止服务。服务停止后,系统会将其销毁。

绑定服务

该服务在另一个组件(客户端)调用 bindService() 时创建。然后,客户端通过 IBinder 接口与服务进行通信。客户端可以通过调用 unbindService() 关闭连接。
多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。 (服务不必自行停止运行。)

这两条路径并非完全独立。也就是说,可以绑定到已经使用 startService() 启动的服务。例如,可以通过使用 Intent(标识要播放的音乐)调用 startService() 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 bindService() 绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 stopService() 或 stopSelf() 不会真正停止服务。

实现生命周期回调

与 Activity 类似,服务也拥有生命周期回调方法,可以实现这些方法来监控服务状态的变化并适时执行工作。 以下框架服务展示了每种生命周期方法:

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 生命周期回调方法不同,不需要调用这些回调方法的超类实现。

这里写图片描述
图 1. 服务生命周期左图显示了使用 startService() 所创建的服务的生命周期,右图显示了使用 bindService() 所创建的服务的生命周期。

通过实现这些方法,您可以监控服务生命周期的两个嵌套循环:

服务的整个生命周期从调用 onCreate() 开始起,到 onDestroy() 返回时结束。

与 Activity 类似,服务也在 onCreate() 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。
例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。
无论服务是通过 startService() 还是 bindService() 创建,都会为所有服务调用 onCreate() 和 onDestroy() 方法。

服务的有效生命周期从调用 onStartCommand() 或 onBind() 方法开始。

每种方法均有 Intent 对象,该对象分别传递到 startService() 或 bindService()。
对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。
对于绑定服务,有效生命周期在 onUnbind() 返回时结束。

注:尽管启动服务是通过调用 stopSelf() 或 stopService() 来停止,但是该服务并无相应的回调(没有 onStop() 回调)。因此,除非服务绑定到客户端,否则在服务停止时,系统会将其销毁—onDestroy() 是接收到的唯一回调。

图 1 说明了服务的典型回调方法。尽管该图分开介绍通过 startService() 创建的服务和通过 bindService() 创建的服务,但是请记住,不管启动方式如何,任何服务均有可能允许客户端与其绑定。因此,最初使用 onStartCommand()(通过客户端调用 startService())启动的服务仍可接收对 onBind() 的调用(当客户端调用 bindService() 时)。

管理绑定服务的生命周期

当服务与所有客户端之间的绑定全部取消时,Android 系统便会销毁服务(除非还使用 onStartCommand() 启动了该服务)。因此,如果您的服务是纯粹的绑定服务,则无需对其生命周期进行管理—Android 系统会根据它是否绑定到任何客户端代您管理。

不过,如果您选择实现 onStartCommand() 回调方法,则您必须显式停止服务,因为系统现在已将服务视为已启动。在此情况下,服务将一直运行到其通过 stopSelf() 自行停止,或其他组件调用 stopService() 为止,无论其是否绑定到任何客户端。

此外,如果您的服务已启动并接受绑定,则当系统调用您的 onUnbind() 方法时,如果您想在客户端下一次绑定到服务时接收 onRebind() 调用(而不是接收 onBind() 调用),则可选择返回 true。onRebind() 返回空值,但客户端仍在其 onServiceConnected() 回调中接收 IBinder。下文图 2 说明了这种生命周期的逻辑。

这里写图片描述
图 2. 允许绑定的已启动服务的生命周期。

猜你喜欢

转载自blog.csdn.net/u012585964/article/details/52485085