android系列之service基础篇

概述

1. 长时间后台运行而没有用户界面
2. 主线程运行

启动方式及生命周期

startService 方式启动

  1. 生命周期独立于启动它的组件,即使启动它的组件已经销毁了也不受任何影响,由于启动的服务长期运行在后台,这会大量的消耗电量,因此,我们应该在任务执行完之后调用 stopSelf 来停止服务,或者通过其他应用组件调用 stopServcie 来停止服务。
  2. 生命周期:onCreate --> onStartCommand --> onDestory。
  3. 首次启动服务的时候,系统会调用 onCreate 方法,多次启动不会在调用 onCreate 方法,只会调用 onStartCommand,onStartCommand 被调用后,服务就启动起来了,将会在后台无限期的运行,知道通过 stopService 或者 stopSelf 方法来停止服务。当服务被销毁时,将会回调 onDestory 方法。
  4. 多次调用 startService(Intent) 会回调 onStartCommand 方法,而多次调用 stopService 只有第一次会回调 onDestory 方法。
  5. startService 在多个context里多次调用,只有第一次会调用service的onCreate(),所有的context调用startService都会重复调用service的onStartCommand(Intent intent, int flags, int startId),并且每次startId数字逐次递增,并且的调用的service是同一个对象实例(即第一次创建的那个)。不同的activity之间利用startService启动的service是同一个对象实例。

bindService 方式启动

  1. 它与绑定组件的生命周期是有关的
  2. 多个组件可以绑定到同一个服务上,如果只有一个组件绑定服务,当绑定的组件被销毁时,服务也就会停止了。如果是多个组件绑定到一个服务上,当绑定到该服务的所有组件都被销毁时,服务才会停止。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。
  3. 生命周期:onCreate --> onBind --> onUnbind --> onDestory
  4. onBind():当其他组件想通过 bindService 与服务绑定时,系统将会回调这个方法,在实现中,必须返回一个 IBinder 接口,供客户端和服务进行通信,必须实现此方法,这个方法是 Service 的一个抽象方法,但是如果不允许绑定,返回 null 就好了。
  5. 第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。onServiceConnected会执行多次。
  6. 并且我们注意到onServiceConnected方法的第二个参数也是IBinder类型的,不难猜测onBind()方法返回的对象被传递到了这里。也就是说我们可以在onServiceConnected方法里拿到了MyService服务的内部类MyBinder的对象,通过这个内部类对象,只要强转一下,我们可以调用这个内部类的非私有成员对象和方法。 onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要IBinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。看一下android对于onBind方法的返回类型IBinder的介绍,字面上理解是IBinder是android提供的进程间和跨进程调用机制的接口。而且返回的对象不要直接实现这个接口,应该继承Binder这个类。
  7. onUnbind:当所有与服务绑定的组件都解除绑定时,就会调用此方法。
    - 多次 bindService 会回调onRebind方法,多次 unBindService 则会 Crash。
  8. 混合启动
    - 如果一个服务被启动又被绑定,onCreate 方法只会执行一次,startService 调用多少次,onStartCommand 就会执行多少次,调用 stopService 并不会回调 onDestory,unBindService 可以。

分类

Service几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

  1. 前台service
  2. 后台service

疑问

1. onStartCommand 返回值含义

  1. START_NOT_STICKY:如果系统在 onStartCommand 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  2. START_STICKT:如果系统在 onStartCommand 返回后终止服务,则会重建服务并调用 onStartCommand,但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会通过空 Intent 调用 onStartCommand。这适用于不执行命令、但无限期运行并等待作业的媒体播放器等。
  3. START_REDELIVER_INTENT:如果系统在 onStartCommand 返回后终止服务,则会重建服务,并通过传递给服务等最后一个 Intent 调用 onStartCommand。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业的服务,例如下载文件。

2. 既使用startService又使用bindService的情况

  1. 如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart方法便会调用多少次。Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。

  2. 那么,什么情况下既使用startService,又使用bindService呢?

    • 如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。

    • 另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。

3. startForegroundService

  1. 最开始使用这个方法时,不是 Crash 就是 ANR。首先使用前台服务,必须申请 FOREGROUND_SERVICE 权限,这是普通权限,未申请则会引发 SecurityException。
    1. FOREGROUND_SERVICE 权限申请
    2. onStartCommand 必须调用 startForeground 构造通知栏。 使用前台服务,必须提供一个通知栏,不然五秒就会 ANR
    3. 非绑定服务

4. 如何保证 Service 不被杀死

  1. 在 Service 的 onStartCommand 中返回 START_STICKY,该标志使得 Service 被杀死后尝试再次启动 Service
  2. 提高 Service 优先级,比如设置成前台服务
  3. 在 Activity 的 onDestory 发送广播,在广播接收器的 onReceiver 重启 Service

5.Service与IntentService的区别

因为最大部分的service不需要同时处理多个请求(处理多个请求是一个比较危险的多线程的场景),这样在在这种情况下呢,最好使用IntentService类如果你实现你的服务。

IntentService

IntentService与Service应该有两处不同,一是,IntentService是序列化执行Intent的,也就是,如果向IntentService发送多个Intent,IntentService是不会阻塞的;二是,IntentService执行完Intent后,是会自动关闭的。而Service没有这两个特点。

IntentService是结合了HandlerThread+Looper+Msg来增加Service的异步能力

  1. 直接创建一个默认的工作线程HandlerThread,该线程执行所有的intent传递给onStartCommand(),然后由ServiceHandler转发消息handleMessage,执行onHandleIntent
  2. 直接创建一个工作队列,将一个Intent传递给你onHandleIntent()的实现,所以我们就永远不必担心多线程。
  3. 当请求完成后自己会调用stopSelf(),所以你就不用调用该方法了。
  4. 提供的默认实现onBind()返回null,所以也不需要重写这个方法。
  5. 提供了一个默认实现onStartCommand(),将意图工作队列,然后发送到你onHandleIntent()实现。
  6. 我们需要做的就是实现onHandlerIntent()方法,还有一点就是经常被遗忘的,构造函数是必需的,而且必须调用超IntentService(字符串) ,因为工作线程的构造函数必须使用一个名称

6. Service与thread的区别

6.1 thread

线程是程序执行的最小单元,可以执行异步操作。

6.2 Service

android官方对service的定义:一个服务(Service)是一个应用的组件,其可以在后台执行长时间的操作,而且不提供UI。其他应用组件可以开启一个服务,该服务将会在后台一直运行,纵使用户切换到了其他应用。另外,一个组件可以捆绑到服务上,来与其交互,甚至执行IPC。例如,一个服务可以执行网络链接、播放音乐、执行I/O或者和一个内容提供者(Content Provider)交互,都是在后台运行。

那么,我们对service存在的迷惑之处应该来源于 其可以在后台执行长时间的操作. 这不就是可以在后台执行些耗时操作嘛。注意官方在下面又说:一个服务(service)运行在主线程中,服务并不创建自己的线程,也不在隔离进程中运行(除非你指定)。这意味着,如果你的服务要执行CPU费时操作或阻塞操作,你需要在服务中创建新的线程来执行该操作。使用其他线程,可以避免ANR错误,保证应用的主线程可以与用户交互。

或许还有疑惑,既然service不能执行耗时后台任务,为什么还要存在service呢?

因此service虽然是在后台执行,但是还是在主线程中。这里的“后台执行”真正含义在于:与ui无关的操作,处于屏幕事件分发与响应之背后。而在处理耗时的会阻塞的cpu或者io操作的时候,还是要在子线程中处理的哦。

其实大家不要把后台和子线程联系在一起就行了,这是两个完全不同的概念。Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。你可能又会问,前面不是刚刚验证过Service是运行在主线程里的么?在这里一直执行着心跳连接,难道就不会阻塞主线程的运行吗?当然会,但是我们可以在Service中再创建一个子线程,然后在这里去处理耗时逻辑就没问题了。

额,既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?其实这跟android的系统机制有关,我们先拿Thread来说。Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。

当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同。

Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上,service和调用者之间的通讯都是同步的(不论是远程service还是本地service),它跟线程一点关系都没有!

你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,这些是 Thread 做不到的。

7. 本地服务与远程服务

本地服务依附在主进程上,在一定程度上节约了资源。本地服务因为是在同一进程,因此不需要IPC,也不需要AIDL。相应bindService会方便很多。缺点是主进程被kill后,服务变会终止。

远程服务是独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被kill的是偶,该服务依然在运行。缺点是该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。

Activity和Service(通过start方式启动)运行在两个不同的进程当中时,如果再次bindService的话,程序崩溃。 此时需要通过AIDL来进行跨进程通信IPC

8. 如何使用AIDL

  1. 新建一个AIDL文件,该文件需要定义好Activity与Service通信的接口
  2. 在自定义Service中,实现定义好的AIDL.Stub接口,然后通过onBind方法返回aidl接口实例对象
  3. 至此已经实现了Activity与Service跨进程通信了,而与远程服务通信的更关注的是为了让一个应用程序去访问另一个应用程序中的Service,以实现共享Service的功能。
  4. android对于跨进程通信的传递数据格式支持有限,基本上只支持java的基本类型,字符串,list和map等。如果要传递自定义类的话,需要这个类去实现Parcelable接口,并且这个类也要定义一个同名的AIDL文件。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值