Binder学习概要

很早就想写一篇Binder的文章了,但是迟迟没写出来,因为Binder机制牵涉到的知识点太多了,有Java层的Binder,也有底层的binder驱动。通常我们在Java层面做应用开发牵涉到Binder的,有AIDL、Messenger等。如何写AIDL、Messenger相关的代码并不难,难点在于理解Binder的原理。如果想要系统地学习Binder,推荐看《Android开发艺术探索》的相关章节以及老罗《Android进程间通信(IPC)机制Binder简要介绍和学习计划》系列,分别对应的是Java层的Binder机制和底层的Binder机制。还有一些其它的优秀的文章,文后会有推荐。

本文不打算深入地介绍Binder 机制的原理,毕竟这不是一两篇文章能介绍的清楚的,而且现在网上也有很多优秀的文章。本文想介绍的是我个人通过对Binder地学习,对Binder机制和Android系统的一些理解。

限于水平,难免有误,还请指正。

Binder基本介绍

Binder是Android系统运行的一个重要基石,做Android开发的应该没有没听说过Binder的吧,那么Binder到底是个什么东西呢?

Binder,最简单来说就是一个Java类,全路径名是 android.os.Binder ,一般我们看到包名是 android.os 的,就应该想到它是系统运行相关的类。这个Binder类实现了IBinder接口,代表这个类的对象具有跨进程通讯的能力。从IPC角度上讲,Binder是Android中一种跨进程通讯方式。Binder还可以理解为一个虚拟的物理设备,这是因为它是有驱动的,但是又不像一般的硬件那样有物理实体,它的设备驱动是/dev/binder,这个驱动是Android特有的,Linux中没有。从Framework层讲,是ServiceManager与各种manager,比如ActivityManagerService、WindowManagerService和各种ManagerService进行通讯的桥梁;从应用层来说,Binder是客户端和服务端进行通讯的媒介。

我觉得不管从什么角度来看Binder,Binder的作用就是用来进行跨进程通讯 (IPC) 的。这里,我们需要简单介绍下IPC,以便于更好的理解Binder的作用。

IPC方式

任何一个系统都需要有相应的IPC机制,Linux上面可以通过管道、System V IPC,即消息队列/共享内存/信号量,或者Socket的方式来进行跨进程通讯,Android是基于Linux的,也就是说Android也可以使用这些IPC方式,那么Android系统为什么还要再引入Binder这种IPC方式呢?这个问题,我觉得可以从三个方面来回答:使用方便、传输性能、安全。

  • 使用方便: Binder是基于C/S架构的,利用了面向对象的思想,对于开发者使用来说,很方便。从这个角度来说,共享内存这种方式就不适合,因为它使用太复杂。
  • 传输性能:Android毕竟是移动设备,移动设备就要考虑内存占用和耗电量的问题,Binder只需要拷贝内存1次,而管道、消息队列、Socket都需要对数据拷贝2次。
  • 安全:传统IPC没有任何安全措施,完全依赖上层协议来确保。传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。
IPC 数据拷贝次数
共享内存 0
Binder 1
Socket/管道/消息队列 2

基于以上这些原因,Android引入了Binder这种IPC方式,基于C/S架构,传输过程只需要1次拷贝,为发送方添加UID/PID身份验证,既支持实名Binder也支持匿名Binder,安全性高。

Binder中的4种角色

  • Client:客户端,使用服务的一端
  • Server:服务端,提供服务的一端
  • ServerManager:ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。其实ServiceManager端也是一个Server端,也有自己的Binder实体,对于ServerManager端来说,其它端都是client端。它是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力。
  • Binder驱动:驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
图片摘自老罗的Binder简要介绍和学习计划.png

Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中。 Client和Server之间的进程间通信通过Binder驱动程序间接实现。Binder驱动程序和Service Manager在Android平台中已经实现,对于我们应用开发者来说,只需要实现自己的Client和Server就行,常见的就是使用AIDL的方式来实现IPC。

应用开发中的Binder使用

Binder的那些概念介绍是挺枯燥乏味的,在Android开发中我们无时不刻不在使用Binder,那么我们就结合平时的开发来了解一些使用的Binder机制的地方。

1 Activity启动

Activity的启动需要用到ActivityManagerService,但是我们的App进程和ActivityManagerService所在的进程不是同一个进程,所以就需要用到进程间通讯了。在App进程中我们拿到的是ActivityManagerService的一个分身,也就是ActivityManagerProxy,这个ActivityManagerProxy与ActivityManagerService都实现了IActivityManager接口,因此它们具有相同的功能,但是ActivityManagerProxy只是做了一个中转,创建两个Parcel对象,一个用于携带请求的参数,一个用于拿到请求结果,然后调用transact方法,通过Binder驱动,ActivityManagerService的onTransact方法会被调用,然后根据相应的code,调用相应的方法,并把处理结果返回。

在这个过程中,我们的App进程就是Client,ActivityManagerService所在的进程是Service。

但是Activity的启动过程还没有完,ActivityManagerService还会调用我们App所在进程的ApplicationThread来最终完成Activity的启动,其实ActivityManagerService拿到的也是ApplicationThread的一个分身ApplicationThreadProxy,通过这个分身,ApplicationThread相应的方法会被调用。

在这个过程中,我们的App端是Server,ActivityManagerService所在的进程是Client。

还有一个问题我们要注意,ActivityThread有一个内部类H(一个Hander),ApplicationThread方法内部都会通过这个Handler来发送消息,最终调用到ActivityThread的方法。为什么要这么做呢?

在分析源码的过程中,很长一段时间,这个问题都困扰着我,直到有一天对Binder的理解加深了,我才明白:Binder服务端的方法都是运行在Binder线程池的一个线程中的,所以要通过Hander,把方法的调用切换到主线程中来

2 Intent携带数据

我们都知道Intent可以传递的数据包含:基本类型、String、实现了Serializable接口或者Parcelable接口的类以及对应的数组或者集合类。其实Intent中的数据都是通过Bundle来携带的,那么我们就要有个疑问了,为什么限定只能是这些类型的数据,而不是任意的数据类型呢?

归根结底,限制这些类型的是Parcel这个类。如果我们查看源码的话就会看到,Bundle其实也是用到了Parcel这个类。

Parcel ,“包裹的意思”,它的作用就是为了在IPC过程中存放数据。我们要知道一点,进程间传递数据,实际上就是二进制数据,所以对于非基本类型,必然存在着序列化和反序列过程,这也是为什么要求Intent传递的非基本类型数据必须实现Serializable或者Parcelable接口的原因。

至于Parcel在IPC过程中使用到的地方,我们可以看一段代码,这个是我仿造着AIDL生成的文件,自己手写的一个Binder服务端。看一下Proxy类的add方法,实际上就是先创建两个Parcel对象,一个通过调用 writexxx 方法用于存放请求数据,一个是通过调用 readxxx 方法获取结果。Proxy真正干的就是这些,真正计算的还是服务端Stub的实现类。当Proxy调用 mRemote.transact(TRANSACTION_add, _data, _reply, 0); 方法后,Stub的onTransact方法会被调用,进而调用真正的add方法。

在这里,我们就可以看到Parcel的一系列 writexxx、readxxx方法的作用。

public interface ICalculateInterface extends IInterface {

    public abstract class Stub extends Binder implements ICalculateInterface {
        private static final String DESCRIPTOR = "com.sososeen09.knowledge.ipc.handipc.ICalculateInterface";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        @Override
        public IBinder asBinder() {
            return this;
        }

        public static ICalculateInterface asInterface(IBinder obj) {
            if (obj == null) {
                return null;
            }

            IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);

            if (iInterface != null && iInterface instanceof ICalculateInterface) {
                return (ICalculateInterface) iInterface;
            } else {
                return new Proxy(obj);
            }
        }

        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0, _arg1, _result;
                    _arg0 = data.readInt();
                    _arg1 = data.readInt();
                    _result = this.add(_arg0, _arg1);

                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements ICalculateInterface {

            private IBinder mRemote;

            public Proxy(IBinder remote) {
                mRemote = remote;
            }

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int add(int a, int b) throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                int result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);

                    mRemote.transact(TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();

                    result = _reply.readInt();
                } finally {
                    _data.recycle();
                    _reply.recycle();
                }
                return result;
            }

            @Override
            public IBinder asBinder() {
                return mRemote;
            }
        }
    }

    static final int TRANSACTION_add = (IBinder.FIRST_CALL_TRANSACTION + 0);
    int add(int a, int b) throws RemoteException;
}

关于Binder的介绍和Java层的使用,先介绍到这里,这些内容会持续更新。

Binder使用的一些注意事项

  • Binder方法是在Binder线程池中被调用的,所以不需要再次new一个线程了,Client调用Server端方法,当前线程会被调起,太耗时的话记得用一个线程来调用。
  • Intent携带的数据大小是限制了,不要超过1M,否则就会报一个TransactionTooLargeException的异常。这是因为Binder数据的缓存大小就是1M。有的时候,即使一次携带的数据不到1M,还是可能会报异常,因为存在并发的情况。

参考

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

推荐阅读更多精彩内容