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
Binder1
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学习概要》 图片摘自老罗的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,还是可能会报异常,因为存在并发的情况。

参考

    原文作者:sososeen09
    原文地址: https://www.jianshu.com/p/a50d3f2733d6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞