IPC - Binder还要从Service说起

一、 什么是Service

第一次明显感觉到Binder的存在实在写Service的时候,虽然那些系统服务都是通过Binder实现的,但是一般来说对开发者并无感知。

像后台音乐播放,后台下载都离不开Service,一般来说Service具有较长时间的运行特性。但是它仍然是Android中的四大组件之一,是Context的子类,他是运行在UI线程中的。

Service可以分为(本文提到的都是本地服务):

  • 本地服务:指的是服务和启动服务的activity在同一个进程中
  • 远程服务: 指的是服务和启动服务的activity不在同一个进程中。

二、Service的使用

一般来说我们把开启Service的一端叫做客户端,把Service叫做服务端

使用一个Service的步骤

继承Service类,实现其抽象方法

package me.febsky.aidltest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

从上面代码可以看到,要想实现Service,有个方法必须实现,这个onBind方法的返回值是一个IBinder类型。

AndroidManifest.xml中声明上一步创建的Service

 <service
      android:name=".MyService"
      android:enabled="true"
      android:exported="true" />

注意这时候,Service和Activity运行在同一个进程中

启动Service
Service的启动有两种方式,一种是通过Context的startService(Intent)方法,另一种是通过Context的bindService()的方式打开,这两种不同的启动方式很重要,分别对应后面的不同的生命周期。

三、 Service的生命周期

Service的生命周期图.png

上面这张图是官网给出的,Service的生命周期主要看懂这张图就懂了。

1. 以startService的方式开启Service,也即是上图的左边生命周期

可以写一个Activity,在这个Activity中有个button名字叫做START,button的点击事件如下:

    public void startMyService(View view) {
        startService(new Intent(MainActivity.this, MyService.class));
    }

为了方便观察生命周期,我们把MyService类的一些生命周期函数实现一些,并打印一下log:

public class MyService extends Service {
    public MyService() {
    }
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("Q_M", "onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("Q_M", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("Q_M", "onDestroy");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

当第一次点击START按钮的时候,打印结果调用顺序如下:
onCreate--> onStartCommand
当再次点击START按钮的时候,打印结果调用顺序如下:
onStartCommand

这种service理论上可以无限地运行下去(除非关机,或者系统内存不够,后者用户手动杀死进程),必须Service内部调用stopSelf()方法或者其他组件调用stopService()方法来停止它。

但是startService的开启方式有个小小的缺陷,这种方式下,只能其他组件像MyService发消息,MyService无法直接将结果反馈给调用者(当然可以借助广播发送反馈)。有个非常常见的应用场景,比如我要下载一个文件,这个下载过程最好放到Service中(为啥不放到Activity中,因为有时候用户不想一直盯着这个下载进度看,想关掉当前页面,但是这个时候下载没有完成,这个下载这时候还要后台进行的啊),但是用户还想看到下载进度怎么办,那Service没下载一点,都要向展示下载进度的Activity反馈当前下载的进度。这个时候就引出了Service的另一种开启方式,bindService

2. bindService(intent, connection, flag)对应上图右面的生命周期

这个bindService 只看方法签名就比较麻烦,比上面startService的方式多了两个参数。
一个Service可以被多个Activity绑定,当所有的activity(客户端)调用unbindService()之后(并且这个MyService没有通过startService的方式开启过),这个Service将自动被系统销毁。要想bind的方式开启Service,必须实现以下几步:

  • 客户端Service实现ServiceConnection接口
  • 服务端实现IBinder接口,并在onBind中返回
  • 客户端调用bindService,并接收服务端返回的IBinder子类对象

这个地方引出了Binder和AIDL的概念,主要用在上面第二部在Service中的onBind方法中返回一个IBinder的子类。这里先假设Service和他的绑定对应的Activity在同一个进程中,要不然就设计到进程间通信,需要复写Binder的一些方法,比如onTransact等。这些都是重复并且麻烦的工作,Android将其封装成AIDL,然后编译的时候自动生成。【aidl下篇再说】

客户端Activity的实现

package me.febsky.aidltest;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
    }

    //bind button的点击事件
    public void bind(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    //unbind button的点击事件
    public void unBind(View view) {
        unbindService(mServiceConnection);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("Q_M", "onServiceConnected invoked");

            //这里能这么写是因为在同一个进程中
            MyService.MyBinder myBinder = (MyService.MyBinder) service;
            myBinder.invokeTest();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(getLocalClassName(), "service disconnected");
        }
    };
}

布局文件就是两个按钮,就不再粘代码了。一个叫bind,一个叫unbind。

服务端Service的实现

package me.febsky.aidltest;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("Q_M", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("Q_M", "onCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("Q_M", "onDestroy");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d("Q_M", "onBind");
        return new MyBinder();
    }

    class MyBinder extends Binder {

        public void invokeTest() {
            Log.d("Q_M", "MyBinder内的方法被调用");
        }

    }
}

点击bind按钮,可以根据日志,观察调用顺序:

08-05 13:44:47.763 1213-1213/me.febsky.aidltest D/Q_M: onCreate
08-05 13:44:47.765 1213-1213/me.febsky.aidltest D/Q_M: onBind
08-05 13:44:47.771 1213-1213/me.febsky.aidltest D/Q_M: onServiceConnected invoked
08-05 13:44:47.771 1213-1213/me.febsky.aidltest D/Q_M: MyBinder内的方法被调用

然后,点击unbind按钮:

08-05 13:45:49.164 1213-1213/me.febsky.aidltest D/Q_M: onDestroy

3. 混合开启Service

Service混合开启生命周期.png

一般来说startServicebindService的开启方式并不是单独存在的,一般会混合使用。比如,一个后台音乐service可能因调用 startService()方法而被开启了,稍后,可能用户想要控制播放器或者得到一些当前歌曲的信息,可以通过 bindService()将一个activity和service绑定。这种情况下,stopService()stopSelf()实际上并不能停止这个service,除非所有的客户都解除绑定。但是onCreate方法只会执行一次。

四、 前台Service和IntentService

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

推荐阅读更多精彩内容

  • 原文地址:Android Service完全解析,关于服务你所需知道的一切(上) 相信大多数朋友对Service这...
    AiPuff阅读 4,075评论 11 98
  • 1.什么是Activity?问的不太多,说点有深度的 四大组件之一,一般的,一个用户交互界面对应一个activit...
    JoonyLee阅读 5,670评论 2 51
  • 最近刚入职,工作中发现自己的能力是在太差了.遂决定重头学习,Activity的文章已经写过了,所以就先从Servi...
    hui朝晖阅读 1,052评论 1 7
  • 过去的他妈的都过去了 老子不是拎不清的人 垃圾就是垃圾 跟谁都一样 老子不要垃圾 别对我有亏欠 我已经删了关于你的...
    阳台上的猫Tid阅读 148评论 0 0
  • 焦虑和恐惧,紧张,全职三年,这种情绪感受的最明显,特别茆同志回来,看他忙碌的身影,越发焦虑,似乎我需要做些什...
    爱自满溢阅读 175评论 0 0