Service详解_初识及启动服务实现

Service内容基本会涉及到,我们将围绕以下主要知识点进行分析:

  • Service简单概述
  • Service在清单文件中的声明
  • Service生命周期
  • Service启动服务实现方式

1. Service简单概述

Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。用户无法感知它的存在。Service可由其他组件启动(如Activity),服务一旦被启动,将在后台一直运行,即使服务的组件已经销毁也不受到影响。Service组件和Activity组件略有不同,Activity组件只有一种运行模式,即Activity处于启动状态,但是Service有两种状态:

  • 启动状态
    当应用组件(如Activity)通过调用startService()启动服务时,服务即处于启动状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已经销毁也不受到影响,除非手动调用才能停止,已启动的服务通常是执行单一操作,不会将结果返回给调用方。

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

2. Service在清单文件中的声明

前面说过Service分为启动状态和绑定状态两种,但无论哪种具体的Service启动类型,都是通过继承Service基类自定义而来,也都需要在AndroidManifest.xml中声明,那么在分析这两种状态之前,我们先来了解一下Service在AndroidManifest.xml中的声明语法,其格式如下:

<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:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。
  • android:name:对应Service类名
  • android:permission:是权限声明
  • android:process:是否需要在单独的进程中运行,当设置为- android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以“remote”和”:remote”不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote。
  • android:isolatedProcess :设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。
  • android:enabled:是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。

3. Service生命周期

官方说明图:


service_lifecycle.png

Service生命周期方法:

  • onBind() 当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。无论是启动状态还是绑定状态,此方法必须重写,但在启动状态的情况下直接返回 null。
  • onCreate() 首次创建服务时,系统将调用此方法来执行一次性设置程序(在调onStartCommand() 或onBind() 之前)。如果服务已在运行,则不会调用此方法,该方法只调用一次。
  • onStartCommand(Intent intent, int flags, int startId) 当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果自己实现此方法,则需要在服务工作完成后,通过调用 stopSelf() 或 stopService() 来停止服务。(在绑定状态下,无需实现此方法)
  • onDestroy() 当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。

其中onStartCommand(Intent intent, int flags, int startId)方法比较重要,这个方法有3个传入参数,它们的含义如下:

  • intent :启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service
    flags:表示启动请求时是否有额外数据,可选值有 0,START_FLAG_REDELIVERYSTART_FLAG_RETRY,0代表没有,它们具体含义如下:

START_FLAG_REDELIVERY
这个值代表了onStartCommand方法的返回值为 START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf方法停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),此时Intent时有值的。
START_FLAG_RETRY
该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。

  • startId: 指明当前服务的唯一ID,与stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根据ID停止服务。

实际上onStartCommand的返回值int类型才是最最值得注意的,它有三种可选值:

  1. START_STICKY
      当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
  2. START_NOT_STICKY
      当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  3. START_REDELIVER_INTENT
      当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
    由于每次启动服务(调用startService)时,onStartCommand方法都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand方法中处理的事件,最后根据需求选择不同的Flag返回值,以达到对程序更友好的控制

4. Service启动服务实现方式

4.1 使用步骤

  • 步骤1:新建子类继承Service类

需重写父类的onCreate()、onStartCommand()、onDestroy()和onBind()方法

  • 步骤2:构建用于启动Service的Intent对象
  • 步骤3:调用startService()启动Service、调用stopService()停止服务
  • 步骤4:在AndroidManifest.xml里注册Service

下面我们通过简单案例来实现并分析:

class SimpleService : Service() {

    /**
     * 绑定服务(bindService)时调用
     */
    override fun onBind(intent: Intent?): IBinder? = null

    /**
     * 首次创建服务时调用,系统调用此方法一次性预设程序(在调用 onStartCommand() 或 onBind() 之前)
     * 如果服务已在运行,则不会调用此方法。该方法只被调用一次
     */
    override fun onCreate() {
        super.onCreate()
        System.out.println("OnCreate invoke")
    }

    /**
     * 每次通过startService()方法启动Service时都会被调用
     */
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        System.out.println("onStartCommand invoke")
        return super.onStartCommand(intent, flags, startId)
    }

    /**
     * 服务销毁时的调用
     */
    override fun onDestroy() {
        super.onDestroy()
        System.out.println("onDestroy invoke")
    }
}

从上面的代码我们可以看出SimpleService继承了Service类,并重写了onBind方法,该方法是必须重写的,但是由于此时是启动状态的服务,则该方法无须实现,返回null即可,只有在绑定状态的情况下才需要实现该方法并返回一个IBinder的实现类。接着重写了onCreate、onStartCommand、onDestroy三个主要的生命周期方法,关于方法的介绍,上面👆已经阐述。

我们通过Demo测试一下Service启动状态方法的调用顺序,MainActivity代码如下:

class MainActivity : AppCompatActivity() {

    private var simpleIntent: Intent? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startServiceBtn.setOnClickListener {
            simpleIntent = Intent(this, SimpleService::class.java)
            startService(simpleIntent)
        }

        stopServiceBtn.setOnClickListener {
            simpleIntent?.let { intent ->
                stopService(intent)
            }
        }
    }

}

记得在清单配置文件中声明Service(声明方式跟Activity相似):

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

从代码看出,启动服务使用startService(Intent intent)方法,仅需要传递一个Intent对象即可,在Intent对象中指定需要启动的服务。而使用startService()方法启动的服务,在服务的外部,必须使用stopService()方法停止,在服务的内部可以调用stopSelf()方法停止当前服务。如果使用startService()或者stopSelf()方法请求停止服务,系统会就会尽快销毁这个服务。值得注意的是对于启动服务,一旦启动将与访问它的组件无任何关联,即使访问它的组件被销毁了,这个服务也一直运行下去,直到手动调用停止服务才被销毁,至于onBind方法,只有在绑定服务时才会起作用,在启动状态下,无需关注此方法,ok~,我们运行程序并多次调用startService方法,最后调用stopService方法。Log输出如下:

01-08 09:28:37.344 23543-23543/com.wangyy.service I/System.out: OnCreate invoke
01-08 09:28:37.344 23543-23543/com.wangyy.service I/System.out: onStartCommand invoke
01-08 09:28:48.950 23543-23543/com.wangyy.service I/System.out: onStartCommand invoke
01-08 09:28:49.171 23543-23543/com.wangyy.service I/System.out: onStartCommand invoke
01-08 09:28:49.317 23543-23543/com.wangyy.service I/System.out: onStartCommand invoke
01-08 09:28:49.496 23543-23543/com.wangyy.service I/System.out: onStartCommand invoke
01-08 09:28:51.089 23543-23543/com.wangyy.service I/System.out: onDestroy invoke

从Log可以看出,第一次调用startService方法时,onCreate方法、onStartCommand方法将依次被调用,而多次调用startService时,只有onStartCommand方法被调用,最后我们调用stopService方法停止服务时onDestory方法被回调,这就是启动状态下Service的执行周期。

由于每次启动服务(调用startService)时,onStartCommand方法都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand方法中处理的事件,最后根据需求选择不同的Flag返回值,以达到对程序更友好的控制。

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

推荐阅读更多精彩内容

  • 上篇我们讲解了Android中的5中等级的进程,分别是:前台进程、可见进程、服务进程、后台进程、空进程。系统会按照...
    徐爱卿阅读 3,839评论 6 33
  • 前言:本文所写的是博主的个人见解,如有错误或者不恰当之处,欢迎私信博主,加以改正!原文链接,demo链接 Serv...
    PassersHowe阅读 1,402评论 0 5
  • [文章内容来自Developers] Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。...
    岳小川阅读 857评论 0 7
  • 本文是利用Visual Studio2017对STK11.4进行二次开发,建立一个WinForm程序,把STK的三...
    奔跑伯爵阅读 6,690评论 5 7
  • 四季轮转 几个春秋几个冬 依然无法拒绝 封藏心底 片语不提 记忆胶片放在格子 高高挂起 偶然一望 就...
    不悲伤a不仰望阅读 600评论 6 9