4.2.0 Android四大组件之- Service概述

服务基本上分为两种形式

  1. 启动

当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。

  1. 绑定

当应用组件通过调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int)) 绑定服务时,服务即处于“绑定”状态。
绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作;仅当与另一个应用组件绑定时,绑定服务才会运行。

应重写的最重要的回调方法包括:

  1. [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))

启动服务startService() -> 重写 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int)) 一旦执行此方法,服务即会启动并可在后台无限期运行。 如果已实现此方法,则在服务工作完成后,需要通过调用 stopSelf()
stopService()来停止服务。(如果您只想提供绑定,则无需实现此方法。)

  1. onBind()

绑定服务 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int)),系统将调用此方法onBind()。在此方法的实现中,必须通过返回 IBinder提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果并不希望允许绑定,则应返回 null。

  1. onCreate()

首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))
onBind()
之前)。如果服务已在运行,则不会调用此方法。

  1. onDestroy()

当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。

如果组件通过调用 startService()启动服务(这会导致对 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))的调用),则服务将一直运行,直到服务使用 stopSelf()自行停止运行,或由其他组件通过调用 stopService()停止它为止。

如果组件是通过调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))来创建服务(且调用 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))),则服务只会在该组件与其绑定时运行。一旦该服务与所有客户端之间的绑定全部取消,系统便会销毁它。

为了确保应用的安全性,**请始终使用显式 Intent 启动或绑定 Service
**,且不要为服务声明 Intent 过滤器。

创建启动服务


从传统上讲,可以扩展Service , IntentService两个类

1. Service

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

该基类包含更多代码,但如需同时处理多个启动请求,则更适合使用该基类;
使用 IntentService显著简化了启动服务的实现。但是,若要求服务执行多线程(而不是通过工作队列处理启动请求),则可扩展 Service类来处理每个 Intent。
为了便于比较,以下提供了 Service 类实现的代码示例,该类执行的工作与使用 IntentService的示例完全相同。
也就是说,对于每个启动请求,它均使用工作线程执行作业,且每次仅处理一个请求。

以下是使用 Service类的代码示例:

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

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            long endTime = System.currentTimeMillis() + 5 * 1000;
            while (System.currentTimeMillis() < endTime) {
                synchronized (this) {
                    try {
                        wait(endTime - System.currentTimeMillis());
                    } catch (Exception e) {
                    }
                }
            }
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        HandlerThread thread = new HandlerThread("ServiceStartArguments",
                Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        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();

        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        mServiceHandler.sendMessage(msg);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

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


###2. [IntentService](http://developer.android.com/reference/android/app/IntentService.html)
这是 [Service](http://developer.android.com/reference/android/app/Service.html)的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现 [onHandleIntent()](http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent))方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。
扩展[IntentService](http://developer.android.com/reference/android/app/IntentService.html)创建启动服务,执行以下操作: 
> - 创建默认的工作线程,用于在应用的主线程外执行传递给 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))的所有 Intent。
 - 创建工作队列,用于将一个 Intent 逐一传递给 [onHandleIntent()](http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent))实现,这样您就永远不必担心多线程问题。
 - 在处理完所有启动请求后停止服务,因此您永远不必调用 [stopSelf()](http://developer.android.com/reference/android/app/Service.html#stopSelf())。
 - 提供 [onBind()](http://developer.android.com/reference/android/app/IntentService.html#onBind(android.content.Intent))的默认实现(返回 null)。
 - 提供 [onStartCommand()](http://developer.android.com/reference/android/app/IntentService.html#onStartCommand(android.content.Intent, int, int))的默认实现,可将 Intent 依次发送到工作队列和 [onHandleIntent()](http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent))实现。
 --- 
综上所述 :
只需实现 [onHandleIntent()](http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent))来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。)

以下是 [IntentService](http://developer.android.com/reference/android/app/IntentService.html)的实现示例:
```java
public class HelloIntentService extends IntentService {
      public HelloIntentService() {
          super("HelloIntentService");
      }
      @Override
      protected void onHandleIntent(Intent intent) {
          long endTime = System.currentTimeMillis() + 5 * 1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
      }
}

-- 您只需要一个构造函数和一个 onHandleIntent()实现即可。
如果您决定还重写其他回调方法(如 onCreate()、[onStartCommand()](http://developer.android.com/reference/android/app/IntentService.html#onStartCommand(android.content.Intent, int, int))或 onDestroy()),请确保调用超类实现,以便 IntentService能够妥善处理工作线程的生命周期。
例如,[onStartCommand()](http://developer.android.com/reference/android/app/IntentService.html#onStartCommand(android.content.Intent, int, int))必须返回默认实现(即,如何将 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);
}

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

启动服务

可以通过将 [Intent](http://developer.android.com/reference/android/content/Intent.html)(指定要启动的服务)传递给 startService(),从 Activity 或其他应用组件启动服务。Android 系统调用服务的 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))
方法,并向其传递 Intent。(切勿直接调用[onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))。)
例如,Activity 可以结合使用显式 Intent 与 startService()
,启动上文中的示例服务 (HelloSevice):

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

startService()方法将立即返回,且 Android 系统调用服务的 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))方法。如果服务尚未运行,则系统会先调用 onCreate(),然后再调用 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))。

如果服务亦未提供绑定,则使用 startService()传递的 Intent 是应用组件与服务之间唯一的通信模式。但是,如果您希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent(使用[getBroadcast()](http://developer.android.com/reference/android/app/PendingIntent.html#getBroadcast(android.content.Context, int, android.content.Intent, int))),并通过启动服务的 Intent传递给服务。然后,服务就可以使用广播传递结果。

多个服务启动请求会导致多次对服务的 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))进行相应的调用。但是,要停止服务,只需一个服务停止请求(使用 stopSelf()stopService())即可。

停止服务

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

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

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

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

创建绑定服务


绑定服务允许应用组件通过调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))与其绑定,以便创建长期连接(通常不允许组件通过调用startService()来启动它)。

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

要创建绑定服务,必须实现 onBind()回调方法以返回 IBinder,用于定义与服务通信的接口。然后,其他应用组件可以调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))来检索该接口,并开始对服务调用方法。服务只用于与其绑定的应用组件,因此如果没有组件绑定到服务,则系统会销毁服务(您不必按通过 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))
启动的服务那样来停止绑定服务)。

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

多个客户端可以同时绑定到服务。客户端完成与服务的交互后,会调用 unbindService()
取消绑定。一旦没有客户端绑定到该服务,系统就会销毁它。

有多种方法实现绑定服务,其实现比启动服务更为复杂,因此绑定服务将在有关绑定服务的单独文档中专门讨论。

向用户发送通知


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

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

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

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

在前台运行服务


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

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

要请求让服务运行于前台,请调用 [startForeground()](http://developer.android.com/reference/android/app/Service.html#startForeground(int, android.app.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_ID, notification);

注意:提供给 [startForeground()](http://developer.android.com/reference/android/app/Service.html#startForeground(int, android.app.Notification))的整型 ID 不得为 0

要从前台删除服务,请调用 stopForeground()。此方法取一个布尔值,指示是否也删除状态栏通知。 此方法绝对不会停止服务。
但是,如果您在服务正在前台运行时将其停止,则通知也会被删除。
如需了解有关通知的详细信息,请参阅创建状态栏通知

管理服务生命周期


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

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

这两条路径并非完全独立。也就是说,您可以绑定到已经使用 startService()启动的服务。例如,可以通过使用 Intent(标识要播放的音乐)调用 startService()来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 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 生命周期回调方法不同,您不需要调用这些回调方法的超类实现。

Service生命周期

服务生命周期左图显示了使用 startService()所创建的服务的生命周期,右图显示了使用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))所创建的服务的生命周期。

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

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

上图说明了服务的典型回调方法。尽管该图分开介绍通过 startService()创建的服务和通过[bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))创建的服务,但是请记住,不管启动方式如何,任何服务均有可能允许客户端与其绑定。因此,最初用 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))(通过客户端调用 startService())启动的服务仍可接收对 onBind()的调用(当客户端调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))时)。

如需了解有关创建提供绑定的服务的详细信息,请参阅绑定服务文档,该文档的管理绑定服务的生命周期部分提供了有关 onRebind()回调方法的更多信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • [文章内容来自Developers] Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。...
    岳小川阅读 842评论 0 7
  • 前言:本文所写的是博主的个人见解,如有错误或者不恰当之处,欢迎私信博主,加以改正!原文链接,demo链接 Serv...
    PassersHowe阅读 1,390评论 0 5
  • 一段时间不用,知识点就会忘,所以整理下一些基本的知识点,加深下印象,便于以后复习。这篇说一下Android中四大组...
    朋永阅读 445评论 0 2
  • 此章节参考Android官网内容而编写,如有遗漏,后面会慢慢补上。 一、Service是什么。 Service是一...
    __帝华阅读 120评论 0 0
  • 战狼确实火了,票房疯长,影评如潮。今天我们也趁着这股热量,来蹭下流量,感受下盛夏时节的这团火! 让我们一同回忆电影...
    照山阅读 2,936评论 0 5