Android-Service

Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件,由其他应用组件启动,即时切换到其他应用,服务仍将在后台继续运行。
组件可以绑定到服务,与之进行交互,甚至是执行进程之间的通信(IPC——Inter Process Communication)

服务基本上有两种形式

启动服务:startService()

  • 调用该方法时,服务即处于“启动”状态。服务启动后,即可在后台无限期运行,即使启动服务的组件已被销毁。
  • 该方式启动的服务通常是执行单一操作,而且不会将结果返回给调用方。
  • 使用此种方式启动服务,则在服务工作完成后,需要通过调用 stopSelf()stopService() 来停止服务。

绑定服务:bindService()

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

服务也可以同时以两种方式运行,既可以是启动服务,也允许绑定,只要实现了对应的 onStartCommand()onBind() 方法

服务在其托管进程的主线程中运行,它既不会创建自己的线程,也不在单独的进程中运行(除非另行指定)。即直接在服务执行任何CPU密集型操作或者耗时操作可能会引发ANR。所以,应该在服务内创建新线程来完成耗时的操作,让应用的主线程专注于运行用户与Activity之间的交互。

启动服务 startService()

启动服务的生命周期独立于启动它的组件,并且可以在后台无限制地运行。启动服务应通过Service调用 stopSelf() 或者其他组件调用 stopService() 来停止它。

onStartCommand() 方法必须返回整型数,用于描述系统应该如何在服务终止的情况下继续运行服务。onStartCommand() 方法的返回值必须是一下常量之一:

  • START_NOT_STICKY:
    如果系统在 onStartCommand() 返回后终止服务,则除非有挂起Intent要传递,否自系统不会重建服务。这是最安全的选项,可以避免在不必要时运行服务,以及更容易重启服务完成未完成的工作。
  • START_STICKY:
    如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个Intent。相反,除非有挂起Intent要启动服务(在这种情况下,将传递这些Intent),否则系统会通过空Intent调用 OnStartCommand()。这使用于不执行命令,但无限期运行并等待作业的业务。
  • START_REDELIVER_INTENT:
    如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个Intent调用 onStartCommand()。任何挂起Intent均依次传递。这使用于主动执行应该立即恢复作业的服务(如下载文件)。

启动服务
通过将Intent(指定要启动的服务)传递给 startService(),从Activity或者其他组件启动服务。Android系统调用服务的 onStartCommand() 方法,并向其传递Intent。(切勿直接调用 onStartCommand())

组件可以结合使用显示Intent与 startService(),启动服务。

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

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

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

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

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

IntentService

由于大多数启动服务都不必同时处理多个请求,因此使用IntentService类实现服务也许是最好的选择。(官方文档)
IntentService执行以下操作:

  • 创建默认的工作线程,用于在应用的主线外执行传递给 onStartCommand() 的所有Intent;
  • 创建工作队列,用于将Intent逐一传递给 onHandleIntent() 实现,这样就永远不必担心多线程问题;
  • 在处理完所有启动请求后停止服务,因此不必在调用 stopSelf();
  • 提供 OnBind() 的默认实现(返回 null);
  • 提供 onStartCommand() 的默认实现,可将Intent一次发送到工作队列和 onHandleIntent() 实现;

绑定服务 bindService()

创建绑定服务
绑定服务允许应用组件通过调用 bindService() 与其绑定,以便创建长期连接(通常不允许组件通过调用 startService() 来启动它)
如需与Activity和其他应用组件中的服务进行交互,或者需要通过进程间通信(IPC)向其他应用公开某些应用功能,则应该创建绑定服务。

创建绑定服务必须实现 onBind() 回调方法以返回IBinder,用于定义与服务通信的接口。然后,其他应用组件可以调用 bindService() 来检索该接口,并开始对服务调用方法。服务只用于与其绑定的应用组件,因此如果没有组件绑定到服务,则系统会销毁服务(不必按通过 onStartCommand() 启动服务那样来停止绑定服务)

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

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

向用户发送通知
绑定服务一旦运行起来,服务即可使用Toast通知或者状态栏通知来通知用户所要发生的事件。

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

要请求让服务运行于前台,调用 startForeground()。此方法采用两个参数:唯一标识通知的整型数 和 状态栏的Notification
要请求从前台移除服务,需调用 stopForeground()。此方法采用一个布尔值,表示是否也移除状态栏通知。此方法不会停止服务,但是,如果在服务正在前台运行时将其停止,通知也会被移除。

管理服务的生命周期

服务的生命周期可以遵循两条不同的路径:
启动服务
该服务在其他组件调用 startService() 时创建,然后无限期运行,且必须通过调用 stopSelf() 来停止运行。此外,其他组件也可以通过调用 stopService() 来停止服务。服务停止后,系统会将其销毁。

绑定服务
该服务在另一个组件(客户端)调用 bindService() 时创建。然后,客户端通过IBinder接口与服务进行通信。客户端可以通过调用 unbinService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统将会销毁该服务。(服务不必自行停止运行)

启动服务和绑定服务的生命周期并非完全独立,也就是说,可以绑定到已经使用 startService() 启动的服务。这种情况下,除非所有客户端均取消绑定,否则 stopService() 或者 stopSelf() 不会实际停止服务。

附上服务生命周期图


service生命周期图

后面再更新一些Demo

2019年6月27日16:13:16更新
快速撸了一个测试服务出来,赋予它启动和绑定两种开启服务的方式,具体内容如下:

//测试服务
public class TestBindService extends Service {

    private static final String TAG = "TestBindService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: 创建TestBindService");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: 启动TestBindService");
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: 绑定TestBindService");
        return new TestBinder();
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.e(TAG, "onRebind: 再次绑定TestBindService");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG, "onUnbind: 解绑TestBindService");
        return super.onUnbind(intent);
        //return true;
    }

    @Override
    public boolean stopService(Intent name) {
        Log.e(TAG, "stopService: 关闭TestBindService");
        return super.stopService(name);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: 销毁TestBindService");
    }

    public String getServiceInformation() {
        Calendar calendar = Calendar.getInstance();

        return getString(R.string.handle_service_time,
                calendar.get(Calendar.YEAR),
                calendar.get(Calendar.MONTH) + 1,
                calendar.get(Calendar.DAY_OF_MONTH),
                calendar.get(Calendar.HOUR),
                calendar.get(Calendar.SECOND));
    }

    public class TestBinder extends Binder {
        public TestBindService getService() {
            return TestBindService.this;
        }
    }
}

再撸一个测试的Activity出来,测试TestBindService,Activity主要提供以下功能:

Activity中提供的功能

测试服务:

  • 启动服务

在当前Activity点击start Service按钮执行startService(),由于之前没有启动或者绑定过服务,所以此时服务是不存在的,startService()执行以后会先创建服务onCreate(),随后调用onStartCommand()执行内部逻辑

随后,无论你再怎么start Service(只要应用进程没有挂掉)都只会调用onStartCommand(),因为服务已经创建了,如下图所示,点击start Service按钮启动服务后再疯狂点击start Service按钮,或者跳转其他页面再启动该服务的时候,不会再创建服务,而是直接调用onStartCommand()发送任务

start service 只会创建一次

启动服务服务后,除非服务自己调用stopSelf()或者其他组件调用stopSerivce(),只要应用没有挂掉,并且没有重写onStartCommand()返回值,一般情况下服务是不会自动销毁并退出的。如下图所示,就是组件调用stopSerivce()停止服务后再次启动服务的流程。onDestory()方法调用表示服务已经销毁了,下次再启动或者绑定的时候都需要重新创建服务,正确使用服务很关键,因为无用的服务长期驻留后台运行时一件很浪费和危险的事情

start service销毁以后重新创建

  • 绑定服务

在当前Activity点击Bind Service按钮触发服务的绑定,可以发现,如果服务没有创建,则会先创建服务,随后调用服务的onBind()方法,执行服务的绑定,然后回调配置的连接ServiceConnection相关的连接成功或者连接失败的方法

此时我们可以在ServiceConnection连接成功后拿到连接的Binder,因为此处的Binder是我们在Service中自定义的,所以返回什么,想要持有什么我们都可以操作。此处Binder持有了我们的TestBindService,所以我们可以拿到Service并调用里面的方法,这样就实现了调用组件和Service的通信了,想干什么就干什么

此处还涉及到调用本地服务的相关内容,如想了解详情自行查阅相关文档

image.png

绑定服务后如果在此点击Bind Service按钮进行连接,不会再回调任何方法了,因为服务已经绑定并建立了连接
随后再解除绑定(没有任何其他启动服务的操作),服务会先执行解除绑定的onUnbind()方法,再销毁服务(如果有多个组件同时绑定服务的时候,只有最后一个组件解除绑定,服务才会销毁,下面有说明)

image.png

多个页面同时绑定服务,按照上面的逻辑,首先创建服务,执行onBind(),并且建立起连接

第一个组件绑定服务

继续启动第二个页面来绑定服务
上一个页面已经创建并绑定服务了,所以第二个页面直接连接服务就可以了,注意没有调用onBind()方法了

第二个组件绑定服务

这个时候在第二个页面解除绑定调用unbindService()方法,没有日志打印,但是当前的页面(组件)已经和Service关闭了连接,因为上一个页面还与Service通信着,所以调用unBindService()方法与服务解除绑定,没有调用onUnbind()方法,服务也不会销毁,致使当前组件与服务的通信关闭了

回到第一个绑定的页面,解除服务绑定,因为此时只有当前页面和服务保持着绑定和连接,所以当前页面解绑时,会调用onUnbind()并销毁服务

最后一个组件解除绑定

混合启动和绑定服务

先启动服务,再绑定服务,解除绑定注意:没有调用onDestory()来销毁服务
因为服务已经启动过了,即便解除了绑定,服务依然不会销毁,除非在解绑以后再调用stopSelf()或者stopService()

混合启动第一个示例:启动-绑定-解绑(没有调用stop相关方法)

先启动服务,再绑定服务,再停止服务注意:没有调用onUnbind()来解绑
没有执行服务的销毁,因为当前服务还处于绑定的连接状态,除非先解除绑定,否则调用stop相关方法停止服务不会生效

混合启动第二个示例:启动-绑定-停止(没有调用onUnbind()

关于上面两个启动和绑定混合使用的例子,其实还有一些隐情,通过下面这两个例子来具体说明

先绑定服务,再启动服务,解除绑定,停止服务,这是对上面混合启动第一个示例的补充说明,不同点在于,这里最后调用了停止服务的方法。可以看到在调用停止方法之前,虽然解除了服务的绑定,没有任何组件还与它保持着绑定的关联,但是由于之前启动过服务,所以服务还会以启动流程的生命周期运行,除非主动执行停止服务的操作 。所以下图中的销毁服务是由停止服务stopService()触发的

image.png

先绑定服务,在启动服务,停止服务,解除绑定 这是对上面混合启动第二个示例的补充说明,不同点在于这里最后调用了服务的解除绑定方法。由混合启动第二个示例可以知道,在服务启动、在绑定以后,继续调用stop相关停止服务的方法,是不会销毁和关闭服务的,但是如果调用了stop相关的停止方法,实际上以及对服务启动的关闭生效了,也就是说服务不会因为startService()启动的原因持续在后台运行了,一旦当前绑定解除,则服务立即就会销毁,所以下图中销毁服务是由解除绑定触发的,因为stop停止方法已经在解绑之前调用过了

image.png

上面两个看似结果一样,其实执行流程还是不一样的,但是都能达到一样的效果:只要服务启动启动过,不管你有没有绑定,解不解除绑定,只要不调用stopService或者stopSelf()那服务都不会退出销毁,而是一直在后台运行等待接收任务

从销毁的组件启动和绑定服务

从Destroy的Activity启动服务,管你销毁还是finish?,只要你是启动服务,就与调用者的状态没有任何关联了,启动服务会按照启动的生命周期流程运行

从销毁的组件启动服务

从Destroy的Activity绑定服务,服务创建绑定后立即销毁了,因为绑定服务和调用组件的生命周期息息相关。
至于为什么会泄漏,向大家都清楚了,图片中也有描述,所以不要搞那些有的没的骚操作,一不小心就会种下祸根

从销毁的组件绑定服务

onRebind()

使用绑定服务的时候,还有一个方法onRebind(),难道是在重复绑定服务的时候调用?你会发现无论你怎么重新绑定服务都是没有走这个方法,因为这个走这个方法是有先行条件的:

  • 首先服务已经启动
  • onUnbind()必须返回true
    onRebind的触发

其实关于服务的这篇文章2年之前就写了,期间工作中也使用过服务(用得很少),最近读Activity启动原理和Service启动原理源码相关的时候,感觉感觉对service的基本使用好像都有些模模糊糊的,就自己撸一个demo出来做实验把自己想验证的问题都验证一下,结合源码相关的理解就会发现认识会更清楚一些

最后附上demo地址,不定期更新一些奇奇怪怪的东西:
https://github.com/Discredited/StudyProject/blob/master/app/src/main/java/com/june/studyproject/service/ServiceActivity.java

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

推荐阅读更多精彩内容