Android—Service解析

Android—Service

一、什么是Service

Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC) 。
每个Service类都必须在其包的 AndroidManifest.xml 中具有相应的服务声明。 可以使用 Context.startService ()和 Context.bindService ()启动服务。

二、Service分类

按是否是前台服务分类

  • 前台服务
    startForeground(int id, Notification notification)
    该方法的作用是把当前服务设置为前台服务,其中id参数代表唯一标识通知的整型数,需要注意的是提供给 startForeground() 的整型 ID 不得为 0,而notification是一个状态栏的通知。
  • 后台服务
    后台服务就是未显示在通知栏的服务

按是否是远程服务分类

  • 远端服务
    在C/S结构中位于服务端的服务,远端服务与客户端运行在不同的进程
  • 本地服务
    即服务和客户端运行在同一进程中

三、生命周期

  • oncreate
    Service首次创建时会回调该方法
  • onstartcommon
    每次通过startService启动service时会回调
  • onbind
    每个组件首次通过bindservice绑定service时会回调
  • ondestory
    service销毁时会回调

以上就是Service常用的几个生命周期函数,因为启动service的方式有两种,在这两种方式中生命周期会有不同。
startService方式流程:oncreate ——>onstartcommon——>ondestory

  • oncreate仅在service创建时才会调用之后再次调用startService启动并不会再次调用。
  • onStartCommend每次调用startService启动都会调用,该函数有两个参数intent、flags,一个int返回值
    intent:启动时,启动组件传递过来的Intent
    flags:表示启动请求时是否有额外数据,可选值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY。
    START_FLAG_REDELIVERY
    这个值代表了onStartCommand方法的返回值为
    START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf方法停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),此时Intent是有值的。
    START_FLAG_RETRY
    该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。
    返回int值:
    START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT,它们具体含义如下:
    START_STICKY
      当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务
    START_NOT_STICKY
      当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
    START_REDELIVER_INTENT
      当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务
  • ondestory
    我们应该始终记得在Service的onDestroy()方法里去清理掉那些不再使用的资源,防止在Service被销毁后还会有一些不再使用的对象仍占用着内存

bindService方式流程:oncreate ——>onbind——>ondestory

  • oncreate
    同上
  • onbind
    当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。无论是启动状态还是绑定状态,此方法必须重写,但在启动状态的情况下直接返回 null。

提供一个 IBinder接口的实现类,该类用以提供客户端用来与服务进行交互的编程接口,该接口可以通过三种方法定义接口:

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

  2. 使用 Messenger
    Messenger可以翻译为信使,通过它可以在不同的进程中共传递Message对象(Handler中的Messager,因此 Handler 是 Messenger 的基础),在Message中可以存放我们需要传递的数据,然后在进程间传递。如果需要让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口,客户端就可利用 Message 对象向服务发送命令。同时客户端也可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,也就是说Messenger是以串行的方式处理客户端发来的消息,这样我们就不必对服务进行线程安全设计了。

  3. 使用 AIDL
    由于Messenger是以串行的方式处理客户端发来的消息,如果当前有大量消息同时发送到Service(服务端),Service仍然只能一个个处理,这也就是Messenger跨进程通信的缺点了,因此如果有大量并发请求,Messenger就会显得力不从心了,这时AIDL(Android 接口定义语言)就派上用场了, 但实际上Messenger 的跨进程方式其底层实现 就是AIDL,只不过android系统帮我们封装成透明的Messenger罢了 。因此,如果我们想让服务同时处理多个请求,则应该使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。使用AIDL必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展。

  • ondestory
    同上

四、使用

service使用步骤

  • 在AndroidManifest文件中注册
    注册时唯一不可缺省的属性是name,它唯一标识了一个Service。
    Android:exported属性设为false,表示不允许其他应用程序启动本应用的组件。android:pression属性可以指定启动该Service所需要的权限

  • 继承Service类重写相关方法

启动方式

  • startService
    一旦启动,Service将一直运行在后台(run in the background indefinitely)即便启动Service的组件已被destroy。通常,一个被start的Service会在后台执行单独的操作,也并不给启动它的组件返回结果。比如说,一个start的Service执行在后台下载或上传一个文件的操作,完成之后,Service应自己停止。

  • bindService
    通过绑定方式启动的Service是一个client-server结构,该Service可以与绑定它的组件进行交互。一个bound service仅在有组件与其绑定时才会运行,多个组件可与一个service绑定,service不再与任何组件绑定时,该service会被destroy。多次调用bind方法只有第一次才会触发onbind方法

startService和bindService区别
1、调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方、

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

  • startForegroundService(8.0新增)
    创建前台服务的方式通常是先创建一个后台服务,然后将该服务推到前台。Android O及Android P,系统不允许后台应用创建后台服务,Android O 引入了一种全新的方法,即ContextCompat.startForegroundService() ,以在前台启动新服务。
    一般使用流程:

    1. 调用ContextCompat.startForegroundService() 可以创建一个前台服务,相当于创建一个后台服务并将它推到前台;
    2. 创建一个用户可见的 Notification;
    3. 必须在5秒内调用该服务的startForeground(int id, Notification notification)方法,否则将停止服务并抛出android.app.RemoteServiceException:Context.startForegroundService() did not then call Service.startForeground()异常。

停止方式

  • stopself
  • stopService
  • unbindService

五、系统提供的Service实现类

IntentService

它本质是一种特殊的Service,继承自Service并且本身就是一个抽象类
它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止
它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务
它内部通过HandlerThread和Handler实现异步操作
创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作

通过for循环多次去启动IntentService,然后去下载图片,注意即使我们多次启动IntentService,但IntentService的实例只有一个,这跟传统的Service是一样的,最终IntentService会去调用onHandleIntent执行异步任务。这里可能我们还会担心for循环去启动任务,而实例又只有一个,那么任务会不会被覆盖掉呢?其实是不会的,因为IntentService真正执行异步任务的是HandlerThread+Handler,每次启动都会把下载图片的任务添加到依附的消息队列中,最后由HandlerThread+Handler去执行

最后附上源码解析链接:IntentService源码分析

JobService

JobService是Android L添加的组件,适用于需要特定条件下才执行后台任务的场景。 由系统统一管理和调度,在特定场景下使用JobService更加灵活和省心。
下面贴篇链接 有兴趣可以看下。
使用介绍
JobService和Service - 简书
JobService的使用介绍_allisonchen的专栏-CSDN博客
Android 9.0 JobScheduler(一) JobScheduler的使用_FightFightFight的博客-CSDN博客
android: job service_u011279649的专栏-CSDN博客

JobIntentService

源码分析已完成

LifecycleService

待补充

六、相关问题

service保活

分两种情况:

  • 因内存资源不足而杀死Service
    可将onStartCommand() 方法的返回值设为 START_STICKY或START_REDELIVER_INTENT ,该值表示服务在内存资源紧张时被杀死后,在内存资源足够时再恢复。也可将Service设置为前台服务,这样就有比较高的优先级,在内存资源紧张时也不会被杀掉
  • 通过 settings -> Apps -> Running -> Stop 方式杀死Service
    这种情况是用户手动干预的,不过幸运的是这个过程会执行Service的生命周期,也就是onDestory方法会被调用,这时便可以在 onDestory() 中发送广播重新启动。这样杀死服务后会立即启动。这种方案是行得通的,但为程序更健全,我们可开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A

5.0之后不可隐式启动service问题

分析源码可知启动service的intent的component和package都为空并且版本大于LOLLIPOP(5.0)的时候,直接抛出异常
解决办法:
1、设置Action和packageName

 final Intent serviceIntent=new Intent(); 
 serviceIntent.setAction("com.android.For 
   egroundService");
  serviceIntent.setPackage(getPackageNa 
  me());//设置应用的包名
    startService(serviceIntent);

2、显式启动

AccessibilityService

AccessibilityService设计初衷在于帮助残障用户使用android设备和应用,在后台运行,可以监听用户界面的一些状态转换,例如页面切换、焦点改变、通知、Toast等,并在触发AccessibilityEvents时由系统接收回调。后来被开发者另辟蹊径,用于一些插件开发,比如微信红包助手,还有一些需要监听第三方应用的插件。
有兴趣的可以自行查阅相关内容。
AccessibilityService使用入门
官方文档

bindService获取代理是同步还是异步

Android面试题:bindService获取代理是同步还是异步 - 简书

service与Thread区别

本质来讲Service和Thread是两个完全不同的东西,service用来在后台执行任务但service一直运行在主线程所以它并不能做耗时操作。此时就需要Thread把耗时操作放到Thread中这样就不会影响主线程

七、Service源码解析

startService流程源码分析

startService流程源码分析

bindService流程源码分析

bindService流程源码分析

小结:service是Android四大组件之一,组件是用来在后台执行任务,注意此处的后台并不单纯指应用切换到后台,正确的理解是没有用户界面。除此之外service还涉及一个后台概念,就是8.0之后禁止在后台启动service,此处后台的正确理解是应用退出前台界面且这种状态持续时间达到系统设置的阈值(目前系统阈值是退出前台界面一分钟后即算进入后台状态,此时无法启动service)。service与thread的本质上是不同的,线程是系统分配资源的最小单元,service是Android系统提供的一个组件它运行在主线程,也就是说它是线程中运行的一个组件,因为在主线程所以不能执行耗时操作,如果必须要执行耗时操作可以在内部启动一个子线程去执行,也可以直接使用intentservice这是Android提供的执行异步请求的service。

service要注意的知识点有:生命周期、分类、start/bind源码分析、8.0之后后台启动service问题、前台service、service保活、系统提供的service子类

posted @ 2020-10-09 16:39  Robin132929  阅读(502)  评论(0编辑  收藏  举报