Binder 学习笔记

目录

前言

什么是Binder?

为什么跨进程通信要使用binder?

Binder通信模型

从AIDL的角度来理解Binder

AIDL java代码分析

demo:手写最简单的aidl

问题


 

文章参考:

写给 Android 应用工程师的 Binder 原理剖析

Binder学习指南

前言

本篇文章主要围绕下面的几个点进行Binder学习的记录。

什么是Binder?

一句话概括:Binder是Android中跨进程通信的一种方式。

为什么跨进程通信要使用binder?

参考:写给 Android 应用工程师的 Binder 原理剖析

文章主要从性能、稳定性、安全性进行对比总结binder的优势所在。

性能方面:

  • socket作为一款通用的接口,其传输效率低,开销大。主要用在跨网络的进程间通信和本机上进程间的低速通信。
  • 消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
  • 共享内存虽然无需拷贝,但控制复杂,难以使用
  • Binder 只需要一次数据拷贝,性能上仅次于共享内存。

稳定性:

  • Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。
  • 共享内存虽然无需拷贝,但是在并发读写使它变得极为不可靠。

安全性

传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。

Binder通信模型

摘自 Binder学习指南

对于跨进程通信的双方,我们姑且叫做Server进程(简称Server),Client进程(简称Client);由于进程隔离的存在,它们之间没办法通过简单的方式进行通信,那么Binder机制是如何进行的呢?

回想一下日常生活中我们通信的过程:假设A和B要进行通信,通信的媒介是打电话(A是Client,B是Server);A要给B打电话,必须知道B的号码,这个号码怎么获取呢?通信录.

这个通信录就是一张表;内容大致是:

1
2
B -> 12345676
C -> 12334354

先查阅通信录,拿到B的号码;才能进行通信;否则,怎么知道应该拨什么号码?回想一下古老的电话机,如果A要给B打电话,必须先连接通话中心,说明给我接通B的电话;这时候通话中心帮他呼叫B;连接建立,就完成了通信。

另外,光有电话和通信录是不可能完成通信的,没有基站支持;信息根本无法传达。

我们看到,一次电话通信的过程除了通信的双方还有两个隐藏角色:通信录和基站。Binder通信机制也是一样:两个运行在用户空间的进程要完成通信,必须借助内核的帮助,这个运行在内核里面的程序叫做Binder驱动,它的功能类似于基站;通信录呢,就是一个叫做ServiceManager的东西(简称SM)

OK,Binder的通信模型就是这么简单,如下图:

 

Binder通信原理

摘自 Binder学习指南

 

上文给出了Binder的通信模型,指出了通信过程的四个角色: Client, Server, SM, driver; 但是我们仍然不清楚Client到底是如何与Server完成通信的

两个运行在用户空间的进程A和进程B如何完成通信呢?内核可以访问A和B的所有数据;所以,最简单的方式是通过内核做中转;假设进程A要给进程B发送数据,那么就先把A的数据copy到内核空间,然后把内核空间对应的数据copy到B就完成了;用户空间要操作内核空间,需要通过系统调用;刚好,这里就有两个系统调用:copy_from_usercopy_to_user

但是,Binder机制并不是这么干的。讲这么一段,是说明进程间通信并不是什么神秘的东西。那么,Binder机制是如何实现跨进程通信的呢?

Binder驱动为我们做了一切。

假设Client进程想要调用Server进程的object对象的一个方法add;对于这个跨进程通信过程,我们来看看Binder机制是如何做的。 (通信是一个广泛的概念,只要一个进程能调用另外一个进程里面某对象的方法,那么具体要完成什么通信内容就很容易了。)

Alt text

Alt text

首先,Server进程要向SM注册;告诉自己是谁,自己有什么能力;在这个场景就是Server告诉SM,它叫zhangsan,它有一个object对象,可以执行add 操作;于是SM建立了一张表:zhangsan这个名字对应进程Server;

然后Client向SM查询:我需要联系一个名字叫做zhangsan的进程里面的object对象;这时候关键来了:进程之间通信的数据都会经过运行在内核空间里面的驱动,驱动在数据流过的时候做了一点手脚,它并不会给Client进程返回一个真正的object对象,而是返回一个看起来跟object一模一样的代理对象objectProxy,这个objectProxy也有一个add方法,但是这个add方法没有Server进程里面object对象的add方法那个能力;objectProxyadd只是一个傀儡,它唯一做的事情就是把参数包装然后交给驱动。(这里我们简化了SM的流程,见下文)

但是Client进程并不知道驱动返回给它的对象动过手脚,毕竟伪装的太像了,如假包换。Client开开心心地拿着objectProxy对象然后调用add方法;我们说过,这个add什么也不做,直接把参数做一些包装然后直接转发给Binder驱动。

驱动收到这个消息,发现是这个objectProxy;一查表就明白了:我之前用objectProxy替换了object发送给Client了,它真正应该要访问的是object对象的add方法;于是Binder驱动通知Server进程,调用你的object对象的add方法,然后把结果发给我,Sever进程收到这个消息,照做之后将结果返回驱动,驱动然后把结果返回给Client进程;于是整个过程就完成了。

由于驱动返回的objectProxy与Server进程里面原始的object是如此相似,给人感觉好像是直接把Server进程里面的对象object传递到了Client进程;因此,我们可以说Binder对象是可以进行跨进程传递的对象

但事实上我们知道,Binder跨进程传输并不是真的把一个对象传输到了另外一个进程;传输过程好像是Binder跨进程穿越的时候,它在一个进程留下了一个真身,在另外一个进程幻化出一个影子(这个影子可以很多个);Client进程的操作其实是对于影子的操作,影子利用Binder驱动最终让真身完成操作。

理解这一点非常重要;务必仔细体会。另外,Android系统实现这种机制使用的是代理模式, 对于Binder的访问,如果是在同一个进程(不需要跨进程),那么直接返回原始的Binder实体;如果在不同进程,那么就给他一个代理对象(影子);我们在系统源码以及AIDL的生成代码里面可以看到很多这种实现。

另外我们为了简化整个流程,隐藏了SM这一部分驱动进行的操作;实际上,由于SM与Server通常不在一个进程,Server进程向SM注册的过程也是跨进程通信,驱动也会对这个过程进行暗箱操作:SM中存在的Server端的对象实际上也是代理对象,后面Client向SM查询的时候,驱动会给Client返回另外一个代理对象。Sever进程的本地对象仅有一个,其他进程所拥有的全部都是它的代理。

一句话总结就是:Client进程只不过是持有了Server端的代理;代理对象协助驱动完成了跨进程通信。

从AIDL的角度来理解Binder

binder学习指南中举了定义了一个简单的aidl接口ICompute

// ICompute.aidl
package com.example.test.app;
interface ICompute {
     int add(int a, int b);
}

经过编译生成的java代码类结构如下:

Binder/IBinder: 它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递。也可以将binder理解成一种协议,类似http在 客户端 发起http请求,服务端接收到请求报文,做对应的处理将结果返回给客户端。在跨进程通信的过程中binder就是网络通信中的http

IInterface: IInterface代表的就是远程server对象具有什么能力,具体来说,就是aidl里面的接口。同时指导如何获取跨进程传输的binder。

这样来看的话,Stub继承Binder实现了IInterface接口,既可以进行跨进程传输,同时可以直接和远端进行交互。

Proxy:实现了IInterface接口通过它可以获取到跨进程传输的binder

AIDL java代码分析

aidl生成的java代码如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.example.txl.tool;
// Declare any non-default types here with import statements

public interface ICompute extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.example.txl.tool.ICompute {
        private static final java.lang.String DESCRIPTOR = "com.example.txl.tool.ICompute";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface( this, DESCRIPTOR );
        }

        /**
         * Cast an IBinder object into an com.example.txl.tool.ICompute interface,
         * generating a proxy if needed.
         */
        public static com.example.txl.tool.ICompute asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
            if (((iin != null) && (iin instanceof com.example.txl.tool.ICompute))) {
                return ((com.example.txl.tool.ICompute) iin);
            }
            return new com.example.txl.tool.ICompute.Stub.Proxy( obj );
        }

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

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString( descriptor );
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface( descriptor );
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add( _arg0, _arg1 );
                    reply.writeNoException();
                    reply.writeInt( _result );
                    return true;
                }
                default: {
                    return super.onTransact( code, data, reply, flags );
                }
            }
        }

        private static class Proxy implements com.example.txl.tool.ICompute {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

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

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int add(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken( DESCRIPTOR );
                    _data.writeInt( a );
                    _data.writeInt( b );
                    mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int add(int a, int b) throws android.os.RemoteException;
}

这里的java代码符合前面对Binder和IInterface的描述。

Server端实现的时候继承Stub类,它能够进行跨进程传输供Client端调用。

Client端调用Stub的静态 方法asInterface获取一个具有服务端外观(和服务端有同样的方法)的ICompute对象。

其实asInterface方法和Stub方法没有什么太大的关系。它的代码如下:

public static com.example.txl.tool.ICompute asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
            if (((iin != null) && (iin instanceof com.example.txl.tool.ICompute))) {
                return ((com.example.txl.tool.ICompute) iin);
            }
            return new com.example.txl.tool.ICompute.Stub.Proxy( obj );
        }

逻辑是:先从本地去查看是否有符合需求的binder对象,如果有直接返回本地对象。如果没有生成一个Proxy对象返回。

对于客户端而言这个Proxy类与其说是一个代理对象倒不如说是它在指导Binder如何进行数据处理。我们看看它的add方法

@Override
public int add(int a, int b) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken( DESCRIPTOR );
        _data.writeInt( a );
        _data.writeInt( b );
        //除了这句代码,其他的全部在进行数据处理
        mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

可以看到当调用add方法的时候除了  mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );其他的都在进行数据处理。而transact就像是将指定数据同步发送到服务端然后等待响应。这也是前面我为什么将Binder类比成http。

客户端 transact方法接收4个参数,他们分别是

  1. 要调用方法的编码
  2. 要发送给服务端参数的容器
  3. 要返回数据的容器
  4. 标记falg(暂时不知道这个参数的作用)

服务端onTransact同样的接收4个参数,他们分别与客户端的4个参数对应。

这样一次aidl跨进程通信就分析完了。做一个简单的总结:

服务端:实现继承Binder并实现IIterface,重写onTransact方法处理客户端的回调。

客户端:同过绑定服务时得到的Ibinder对象,通过transact进行对应的数据传输。

demo:手写最简单的aidl

服务端实现:

package com.txl.calculationserver;

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

public class CalculationService extends Service {
    private static final String TAG = "CalculationService";
    Binder binder = new CalculationBinder();
    public CalculationService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

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

    private static class CalculationBinder extends Binder implements IInterface {
        private static final java.lang.String DESCRIPTOR = "com.txl.calculationserver.CalculationService.CalculationBinder";
        static final int TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION ;

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

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

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString( descriptor );
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface( descriptor );
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add( _arg0, _arg1 );
                    reply.writeNoException();
                    reply.writeInt( _result );
                    return true;
                }
                default: {
                    return super.onTransact( code, data, reply, flags );
                }
            }
        }

        public int add(int a, int b) throws RemoteException {
            Log.d( TAG,"call add method by client" );
            return a+b;
        }
    }
}

客户端实现:

public class MainActivity extends AppCompatActivity {
    private final String TAG = "MainActivity";

    private ProxyCalculationServer proxyCalculationServer;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            proxyCalculationServer = ProxyCalculationServer.asInterface( iBinder );
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );
        findViewById( R.id.tv_call_remote_add ).setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(proxyCalculationServer != null){
                    try {
                        int result = proxyCalculationServer.add( 3,6 );
                        Log.d( TAG,"result = "+result );
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }else {
                    Log.e( TAG,"proxyCalculationServer is null" );
                }
            }
        } );
        Intent intent = new Intent(  );
        intent.setAction( "com.txl.calculationserver" );
        intent.setPackage( "com.txl.calculationserver" );
        bindService( intent,connection,BIND_AUTO_CREATE );
    }

    static class ProxyCalculationServer implements IInterface {
        private static final java.lang.String DESCRIPTOR = "com.txl.calculationserver.CalculationService.CalculationBinder";
        static final int TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION ;
        private android.os.IBinder mRemote;
        public ProxyCalculationServer(IBinder obj) {
            mRemote = obj;
        }
//        @Override
//        public android.os.IBinder asBinder() {
//            return mRemote;
//        }

        public static ProxyCalculationServer asInterface(IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
            if (((iin != null) && (iin instanceof ProxyCalculationServer))) {
                return ((ProxyCalculationServer) iin);
            }
            return new ProxyCalculationServer( obj );
        }

        public int add(int a, int b) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.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 {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

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

这个两个文件分别实现在不同的项目中。对于简单的xml没有贴上代码。

最后服务端和客户端的运行结果如下

通过上面的案例可以看到,对于服务端而言不是一定要获取一个Proxy对象。只要你懂得binder传输过程中的数据处理,甚至完全可以直接自己调用transact。

总结:

aidl实现跨进程通信不是必须要先编写 .aidl 文件完全可以按照前面总结aidl需要完成的东西来进行编写。但是aidl文件帮助我们更加简单的实现款进程通信的过程,也不容易出错。建议大家在实现aidl的时候不要手写,手写实现过程的目的是为了更好的了解这一过程。

问题

  1.  服务端什么时候被注册到ServerManager?
  2. IInterface接口的asBinder方法是干什么的?为什么返回null也能够正常调用?
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值