android开发学习

安卓服务概览

2020-03-11  本文已影响0人  VanHoan

服务概览

Service是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。

此外,组件可以通过绑定到服务与之进行交互,甚至是执行进程间通信IPC。例如,服务可在后台处理网络事务、播放音乐,执行文件I/O或与ContentProvider进行交互。

以下是三种不同的服务类型:

虽然分开概括讨论启动和绑定服务,但服务是可同时以这两种方式运行的,换句话说,它即可以是启动服务(无限期运行),亦支持绑定。唯一的问题在于是否实现一组回调方法:onStartCommand()(让组件启动服务)和onBind()(实现服务绑定)

无论服务是牌启动状态还是绑定状态(或者同时处理这两种状态),任何应用组件均可像使用Activity那样,通过调用Intent来使用服务(即此服务来自另一应用)。<u>不过可以通过AndroidManifest.xml将服务声明为私有的,并阻止其他应用访问</u>


<manifest ... >

  ...

  <application ... >

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

      ...

  </application>

</manifest>

在服务和线程之间进行选择

服务是一种即使用户未与应用交互也可以后台运行的组件,因此,只有在需要服务时才应创建服务。

<font color=BrulyWood>如果必须在主线程之外执行操作,但只在用户与应用有交互时执行相应的操作,那么应用创建线程。</font>

<u>例如,如果只是想在Activity运行的同时播放一些音乐,则可以onCreate()中创建线程,在onStart()中启动线程,然后在onStop()中停止线程。或者还可以考虑使用AsyncTask或者HandlerThread,而非传统的Thread类。如需了解有关线程的详细信息,请参阅进程和线程文档</u>

如果确实要使用服务,默认情况下,它仍会在应用的主线程中运行,因此,如果服务执行的是密集或者阻塞性操作,则仍应在服务内创建新线程。

基础知识

如要创建服务,您必须创建Service 的子类(或使用它的一个现有子类)。在实现中,您必须重写一些回调方法,从而处理服务生命周期的某些关键方面,并提供一种机制将组件绑定到服务(如适用)。以下是您应重写的最重要的回调方法:

只有在内存过低且必须回收系统资源以供拥有用户焦点的 Activity 使用时,Android 系统才会停止服务。如果将服务绑定到拥有用户焦点的 Activity,则它其不太可能会终止;如果将服务声明为在前台运行,则其几乎永远不会终止。如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升—如果服务是启动服务,则您必须将其设计为能够妥善处理系统执行的重启。如果系统终止服务,则其会在资源可用时立即重启服务,但这还取决于您从 onStartCommand() 返回的值。如需了解有关系统会在何时销毁服务的详细信息,请参阅进程和线程文档。

下文将介绍如何创建 startService() 和 bindService() 服务方法,以及如何通过其他应用组件使用这些方法。

使用清单文件声明服务

如同对 Activity 及其他组件的操作一样,您必须在应用的清单文件中声明所有服务。

如要声明服务,请添加 <service> 元素作为 <application> 元素的子元素。下面是示例:


<manifest ... >

  ...

  <application ... >

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

      ...

  </application>

</manifest>

您还可在 <service> 元素中加入其他属性,以定义一些特性,如启动服务及其运行时所在进程需要的权限。android:name 属性是唯一必需的属性,用于指定服务的类名。发布应用后,请保此类名不变,以避免因依赖显式 Intent 来启动或绑定服务而破坏代码的风险(请阅读博文 Things That Cannot Change [不能更改的内容])。

注意:为确保应用的安全性,在启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务会响应 Intent,而用户也无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),则系统会抛出异常。

您可以通过添加 android:exported 属性并将其设置为 false,确保服务仅适用于您的应用。这可以有效阻止其他应用启动您的服务,即便在使用显式 Intent 时也如此。

<u>注意:用户可以查看其设备上正在运行的服务。如果他们发现自己无法识别或信任的服务,则可以停止该服务。为避免用户意外停止您的服务,您需要在应用清单的 <service> 元素中添加 android:description。请在描述中用一个短句解释服务的作用及其提供的好处。</u>

创建启动服务

启动服务由另一个组件通过调用 startService() 启动,这会导致调用服务的 onStartCommand() 方法。

服务启动后,其生命周期即独立于启动它的组件。即使系统已销毁启动服务的组件,该服务仍可在后台无限期地运行。因此,服务应在其工作完成时通过调用 stopSelf() 来自行停止运行,或者由另一个组件通过调用 stopService() 来将其停止。

应用组件(如 Activity)可通过调用 startService() 方法并传递 Intent 对象(指定服务并包含待使用服务的所有数据)来启动服务。服务会在 onStartCommand() 方法接收此 Intent。

<u>例如,假设某 Activity 需要将一些数据保存到在线数据库中。该 Activity 可以启动一个协同服务,并通过向 startService() 传递一个 Intent,为该服务提供要保存的数据。服务会通过 onStartCommand() 接收 Intent,连接到互联网并执行数据库事务。事务完成后,服务将自行停止并销毁。</u>

注意:默认情况下,服务与服务声明所在的应用运行于同一进程,并且运行于该应用的主线程中。如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。为避免影响应用性能,请在服务内启动新线程。

通常,您可以扩展两个类来创建启动服务:

扩展IntentService类

由于大多数启动服务无需同时处理多个请求(实际上,这种多线程情况可能很危险 - ???),因此最佳选择是利用 IntentService 类实现服务。

IntentService 类会执行以下操作:


private final class ServiceHandler extends Handler {

        public ServiceHandler(Looper looper) {

            super(looper);

        }

        @Override

        public void handleMessage(Message msg) {

            onHandleIntent((Intent)msg.obj);

            // 执行完成后,关闭当前服务

            stopSelf(msg.arg1);

        }

    }

@Override

public void onStart(@Nullable Intent intent, int startId) {

    Message msg = mServiceHandler.obtainMessage();

    msg.arg1 = startId;

    msg.obj = intent;

    mServiceHandler.sendMessage(msg);

}

/**

* You should not override this method for your IntentService. Instead,

* override {@link #onHandleIntent}, which the system calls when the IntentService

* receives a start request.

* @see android.app.Service#onStartCommand

*/

@Override

public int onStartCommand(@Nullable Intent intent, int flags, int startId) {

    onStart(intent, startId);

    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;

}

/**

* Unless you provide binding for your service, you don't need to implement this

* method, because the default implementation returns null.

* @see android.app.Service#onBind

*/

@Override

@Nullable

public IBinder onBind(Intent intent) {

    return null;

}

@WorkerThread

protected abstract void onHandleIntent(@Nullable Intent intent);

如要完成客户端提供的工作,请实现 onHandleIntent()。不过,您还需为服务提供小型构造函数。

如果您还决定重写其他回调方法(如 onCreate()、onStartCommand() 或 onDestroy()),请确保调用超类实现,以便 IntentService 能够妥善处理工作线程的生命周期。

除 onHandleIntent() 之外,您无需从中调用超类的唯一方法就是 onBind()。只有在服务允许绑定时,您才需实现该方法。

扩展服务类

借助 IntentService,您可以非常轻松地实现启动服务。但是,若要求服务执行多线程(而非通过工作队列处理启动请求),则可通过扩展 Service 类来处理每个 Intent。

为进行比较,以下示例代码展示了 Service 类的实现,该类执行的工作与上述使用 IntentService 的示例完全相同。换言之,对于每个启动请求,其均使用工作线程来执行作业,且每次仅处理一个请求。


public class HelloService extends Service {

  private Looper serviceLooper;

  private ServiceHandler serviceHandler;

  // 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 doesn't 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

    serviceLooper = thread.getLooper();

    serviceHandler = new ServiceHandler(serviceLooper);

  }

  @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 = serviceHandler.obtainMessage();

      msg.arg1 = startId;

      serviceHandler.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,此示例需要执行更多工作。看代码,创建工作线程的代码和IntentService中是一致的。

但是,由于 onStartCommand() 的每个调用均有您自己处理,因此您可以同时执行多个请求。此示例并未这样做,但若您希望如此,则可为每个请求创建新线程,然后立即运行这些线程(而非等待上一个请求完成,可以调整为很多服务端使用的线程模型中的Acceptor和Workers线程处理多个请求)。

请注意,<font color=BrulyWood>onStartCommand()方法必须返回一个整数</font>。此整数用于描述系统应如何在系统终止服务的情况下继续运行服务。IntentService 的默认实现会为您处理此情况,但您可以对其进行修改。从 onStartCommand() 返回的值必须是以下常量之一:

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

If the system kills the service after onStartCommand() returns, do not recreate the service unless there are pending intents to deliver. This is the safest option to avoid running your service when not necessary and when your application can simply restart any unfinished jobs.

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

<font color=BrulyWood>简单一点说:当返回START_STICKY时,系统会重新创建服务再次调用onStartCommand(),如果有挂起的Intent需要执行,onstartCommand()就传递该Intent,否则传递参数为null。</font>

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

If the system kills the service after onStartCommand() returns, recreate the service and call onStartCommand() with the last intent that was delivered to the service. Any pending intents are delivered in turn. This is suitable for services that are actively performing a job that should be immediately resumed, such as downloading a file.

<font color=BrulyWood>实质上这两句话是矛盾的,看源码AMS会把所有挂起状态的Intent(存在于pendingStarts列表中)按照顺序依次通过onStartCommand()发送给重建后的service,而不仅仅是最后一个</font>

image

启动服务

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

<font color=BrulyWood>注意:如果您的应用面向 API 26 或更高版本,除非应用本身在前台运行,否则系统不会对使用或创建后台服务施加限制。如果应用需要创建前台服务,则其应调用 startForegroundService()。此方法会创建后台服务,但它会向系统发出信号,表明服务会将自行提升至前台。创建服务后,该服务必须在五秒内调用自己的 startForeground() 方法。</font>

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

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


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

startService(intent);

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

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

停止服务

启动服务必须管理自己的生命周期。因为,除非必须回收内存资源,否则系统不会停止或者销毁服务,并且服务在onStartCommand()返回后仍会继续执行。服务必须通过调用自身的stopSelf()自行停止运行,或者由其他组件调用stopService()停止。

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

如果服务同时处理多个对onStartCommand()的请求,则不应该在处理完一个请求后停止服务,因为可能收到新的请求(在第一个请求结束时停止会终止第二个请求)。为了避免此问题,可以使用stopSelf(int)确保服务停止请求始终基于最近的启动请求。

<font color=BrulyWood>换句话说,在调用stopSelf(int)时,需要传递与停止请求ID相对应的启动请求ID(就是onStartCommand(int)中的参数startId)。此外,如果服务在在调用stopSelf(int)之前收到新的启动请求,由于ID不匹配,服务也不会停止。</font>

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

创建绑定服务

绑定服务允许应用组件通过调用 bindService() 与其绑定,从而创建长期连接。此服务通常不允许组件通过调用 startService() 来启动它。

如需 Activity 与其他应用组件中的服务进行交互,或需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。

如要创建绑定服务,您需通过实现 onBind() 回调方法返回 IBinder,从而定义与服务进行通信的接口。然后,其他应用组件可通过调用 bindService() 来检索该接口,并开始调用与服务相关的方法。服务只用于与其绑定的应用组件,因此若没有组件与该服务绑定,则系统会销毁该服务。您不必像通过 onStartCommand() 启动的服务那样,以相同方式停止绑定服务。

要创建绑定服务,必须定义客户端如何与服务端进行通信的接口。服务与客户端之间的这个接口必须是IBinder的实现,并且服务必须从onBind()方法中返回该接口。收到IBinder后,客户端便可开始通过该接口与服务进行交互。

多个客户端可以同时绑定到同一个服务。完成与服务的交互后,客户端会通过调用unbindService()来取消绑定。如果检测到没有绑定到服务的客户端,系统会销毁该服务。

实现绑定服务有多种方法,并且此实现比启动服务更为复杂。具体信息请参阅另一份文档绑定服务,了解关于绑定服务的详细介绍。

向用户发送通知

处于运行状态时,服务可以使用 Toast 通知或状态栏通知来通知用户所发生的事件。

Toast 通知是指仅在当前窗口界面显示片刻的消息。状态栏通知在状态栏中提供内含消息的图标,用户可通过选择该图标来执行操作(例如启动 Activity)。

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

如需了解详细信息,请参阅 Toast 通知或状态栏通知开发者指南。

在前台运行服务

前台服务是用户可感知的一种服务,即使在内存不足时,系统也不会考虑将其终止。<font color=BrulyWood>前台服务必须为状态栏提供通知,将其放在运行中的标题下方。这意味着除非将服务停止或从前台移除,否则不能清除该通知。</font>

例如,应将通过服务播放音乐的音乐播放器设置为在前台运行,因为用户会明确意识到其操作。状态栏中的通知可能表示正在播放的歌曲,并且其允许用户通过启动 Activity 与音乐播放器进行交互。同样,如果应用允许用户追踪其运行,则需通过前台服务来追踪用户的位置。

如要请求让服务在前台运行,请调用 startForeground()。此方法采用两个参数:唯一标识通知的整型数和用于状态栏的 Notification。此通知必须拥有 PRIORITY_LOW 或更高的优先级。下面是示例:


Intent notificationIntent = new Intent(this, ExampleActivity.class);

PendingIntent pendingIntent =

        PendingIntent.getActivity(this, 0, notificationIntent, 0);

Notification notification =

          new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)

    .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()。此方法采用布尔值,指示是否需同时移除状态栏通知。此方法不会停止服务。但是,如果您在服务仍运行于前台时将其停止,则通知也会随之移除。

管理服务的生命周期

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

服务生命周期(从创建到销毁)可遵循以下任一路径:

这两种使用服务的方式并非完全独立。您可以绑定到已使用 startService() 启动的服务。例如,您可以使用 Intent(标识要播放的音乐)来调用 startService(),从而启动后台音乐服务。随后,当用户需稍加控制播放器或获取有关当前所播放歌曲的信息时,Activity 可通过调用 bindService() 绑定到服务。此类情况下,在所有客户端取消绑定之前,stopService() 或 stopSelf() 实际不会停止服务。

实现生命周期回调

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


public class ExampleService extends Service {

    int startMode;      // indicates how to behave if the service is killed

    IBinder binder;      // interface for clients that bind

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

image

左图显示使用 startService() 创建的服务的生命周期,右图显示使用 bindService() 创建的服务的生命周期。

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

对于启动服务,活动生命周期与整个生命周期会同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,活动生命周期会在 onUnbind() 返回时结束。

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

`

上一篇下一篇

猜你喜欢

热点阅读