Android IPC机制——Binder详解

来源:互联网 发布:mac 大文件上传 编辑:程序博客网 时间:2024/06/10 01:50

IPC是指Android中的进程间通信,即在不同进程之间传递消息数据,Android中可实现进程通信的方法有很多,比如Intent、ContentProvider、Messenger、Binder、Socket或是利用文件,这些方式各有千秋,都有最适合使用的场景,这次要介绍的是一种安全高效的面向对象式的IPC实现——Binder。

当使用bindService()绑定一个服务时,service会在其onBind()方法中返回一个Binder对象,然后在client的ServiceConnection中获取这个Binder,即可跨进程使用service的方法,接下来我们就来看一看Binder的实现原理。

在Android中,实现Binder很简单,不需要我们去写,只需要写一个aidl文件,在其中写一个接口,声明需要的方法,其他的工作通过编译之后系统会为我们完成,最后生成java文件。Android为我们提供了这这种简单的Binder使用方式,虽然简化了开发,但也一定程度的限制了我们对其工作原理的深入理解,下面就以系统生成的Binder类来讲解一下Binder构造。

本文会分四个个部分来分析Binder:

1.Binder的组成结构
2.Binder的使用方法
3.Binder对象的传递流程
4.Binder对client请求的处理过程


Aidl生成Binder类

首先,创建一个aidl文件,如下:

package com.ipctest.aidl;interface IUser {   boolean login(String userName,String userPwd);   void logout(String userName);}

然后编译工程,在AndroidStudio的目录结构下,生成的.java文件在build–generated–source–aidl–debug目录下,我得到的文件经过格式整理如下:

/* * This file is auto-generated.  DO NOT MODIFY. * Original file:  */package com.ipctest.aidl;// Declare any non-default types here with import statementspublic interface IUser extends android.os.IInterface{    /** Local-side IPC implementation stub class. */    public static abstract class Stub extends android.os.Binder implements com.ipctest.aidl.IUser{        private static final java.lang.String DESCRIPTOR = "com.ipctest.aidl.IUser";        /** Construct the stub at attach it to the interface. */        public Stub(){            this.attachInterface(this, DESCRIPTOR);        }        /**         * Cast an IBinder object into an com.ipctest.aidl.IUser interface,         * generating a proxy if needed.         */        public static com.ipctest.aidl.IUser asInterface(android.os.IBinder obj)        {            if ((obj==null)) {                return null;            }            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);            if (((iin!=null)&&(iin instanceof com.ipctest.aidl.IUser))) {                return ((com.ipctest.aidl.IUser)iin);            }            return new com.ipctest.aidl.IUser.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{            switch (code){                case INTERFACE_TRANSACTION:                {                    reply.writeString(DESCRIPTOR);                    return true;                }                case TRANSACTION_login:                {                    data.enforceInterface(DESCRIPTOR);                    java.lang.String _arg0;                    _arg0 = data.readString();                    java.lang.String _arg1;                    _arg1 = data.readString();                    boolean _result = this.login(_arg0, _arg1);                    reply.writeNoException();                    reply.writeInt(((_result)?(1):(0)));                    return true;                }                case TRANSACTION_logout:                {                    data.enforceInterface(DESCRIPTOR);                    java.lang.String _arg0;                    _arg0 = data.readString();                    this.logout(_arg0);                    reply.writeNoException();                    return true;                }            }            return super.onTransact(code, data, reply, flags);        }        private static class Proxy implements com.ipctest.aidl.IUser{            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 boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException            {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                boolean _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    _data.writeString(userName);                    _data.writeString(userPwd);                    mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);                    _reply.readException();                    _result = (0!=_reply.readInt());                }finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }            @Override             public void logout(java.lang.String userName) throws android.os.RemoteException{                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    _data.writeString(userName);                    mRemote.transact(Stub.TRANSACTION_logout, _data, _reply, 0);                    _reply.readException();                }finally {                    _reply.recycle();                    _data.recycle();                }            }        }    static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);    static final int TRANSACTION_logout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);    }    public boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException;    public void logout(java.lang.String userName) throws android.os.RemoteException;}

Binder类结构

上面就是是系统生成的IUser.java,可能看起来会有些头疼,但结构其实非常简单,上面作为源码参考,下面我将其方法内容省略,将结构分离出来,这个接口继承于IInterface,在其中中实现了一个内部类Stub,Stub又有一个内部类Proxy,代码如下:

package com.ipctest.aidl;//AIDL文件中定义的IUser接口public interface IUser extends android.os.IInterface{    //IUser中的内部类,继承于Binder类并实现了IUser接口,在service中传递的就是这个类    public static abstract class Stub extends android.os.Binder implements com.ipctest.aidl.IUser{        //接口的唯一标识,一般由包名+类名组成        private static final java.lang.String DESCRIPTOR = "com.ipctest.aidl.IUser";        public Stub(){            //在构造方法中将自身接口标识存储起来            this.attachInterface(this, DESCRIPTOR);        }        public static com.ipctest.aidl.IUser asInterface(android.os.IBinder obj)        {            //获取binder对象            //在这里会调用obj.queryLocalInterface(DESCRIPTOR)来获取binder,在service返回Binder时会判断client请求进行处理            //如果请求来自当前线程,queryLocalInterface()会返直接返回构造方法中attachInterface()的binder对象,也就是binder本身            //如果来自其他进程,queryLocalInterface方法直接返回null            //这时就需要创建一个Proxy对象(Stub的内部代理类)供client使用        }        @Override         public android.os.IBinder asBinder()        {            //获取当前Binder            return this;        }        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{            //在跨进程请求时被调用,请求先被Proxy对象处理,Proxy将方法参数序列化之后            //和方法编号以及方法参数和用于存储返回值的序列化对象(如果有返回值)一同交给此方法            //然后在这里将参数还原,并调用相应的方法进行处理,最后将返回值序列化后返回给Proxy。        }        //Stub的内部类,当请求来自同一进程时,不会使用,当请求来自另一个进程时,会将client得到的binder包装成它的实例        private static class Proxy implements com.ipctest.aidl.IUser{            private android.os.IBinder mRemote;            //持有一个IBinder,通常就是stub本身            Proxy(android.os.IBinder remote){                mRemote = remote;            }            //获取当前的Proxy对象            @Override             public android.os.IBinder asBinder(){                return mRemote;            }            public java.lang.String getInterfaceDescriptor(){                return DESCRIPTOR;            }            @Override             public boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException{                //login方法请求封装            }            @Override             public void logout(java.lang.String userName) throws android.os.RemoteException{                //logout方法请求封装            }        }        //IUser借口中两个方法的code    static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);    static final int TRANSACTION_logout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);    }    //待实现的方法    public boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException;    public void logout(java.lang.String userName) throws android.os.RemoteException;}

Binder使用方法

上面就是IUser的结构,下面通过一个例子先演示一下Binder的实现方式:

//首先创建Service,还是使用前面的aidl生成的Binderpublic class MyService extends Service{    private final String TAG="BinderTest";    //创建Binder对象,实现两个方法    private Binder mBinder= new IUser.Stub() {        @Override        public boolean login(String userName, String userPwd) throws RemoteException {            Log.d(TAG,userName+"   登录成功!");            return true;        }        @Override        public void logout(String userName) throws RemoteException {            Log.d(TAG,userName+"   退出成功!");        }    };    //在onBind中返回Binder对象    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }}
//然后在Activity中启动服务public class MainActivity extends AppCompatActivity {    private final String TAG = "MyServiceTest";    private EditText mUserNameEdt, mUserPwdEdt;    private Button mLoginBtn, mLogoutBtn;    IUser mUserBinder;    //创建ServicerConnection    ServiceConnection mServiceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            Log.d(TAG, "onServiceConnected");            mUserBinder = IUser.Stub.asInterface(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Log.d(TAG, "oncreate");        setContentView(R.layout.activity_main);        initLayout();        //绑定服务        Intent intent = new Intent(MainActivity.this, MyService.class);        bindService(intent,mServiceConnection,BIND_AUTO_CREATE);    }    private void initLayout() {        mUserNameEdt = (EditText) this.findViewById(R.id.user_name_edt);        mUserPwdEdt = (EditText) this.findViewById(R.id.user_pwd_edt);        mLoginBtn = (Button) this.findViewById(R.id.login_btn);        mLogoutBtn = (Button) this.findViewById(R.id.logout_btn);        //点击登录按钮访问Service的Binder的login方法        mLoginBtn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String userName = mUserNameEdt.getText().toString();                String userPwd = mUserPwdEdt.getText().toString();                if (mUserBinder != null) {                    try {                        mUserBinder.login(userName, userPwd);                    } catch (RemoteException e) {                        e.printStackTrace();                    }                }            }        });        //点击退出按钮访问Service的Binder的logout方法        mLogoutBtn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String userName = mUserNameEdt.getText().toString();                if (mUserBinder != null) {                    try {                        mUserBinder.logout(userName);                    } catch (RemoteException e) {                        e.printStackTrace();                    }                }            }        });    }

点击按钮结果:

这里写图片描述
可以发现Binder中的方法被执行了,这是同一进程的运行结果,如果将service运行在独立线程中又会如何呢?

    //在Manifest中为MyService设置一个进程    <service            android:name=".MyService"            android:process=":remote" />

运行结果:
这里写图片描述

这里写图片描述

可以看到,在将MyService的进程中login()和logout()被执行了,也就是说点击按钮后成功的跨进程调用了MyService的方法


Binder对象的传递过程

接下来详细解析Binder从Service到client的传递过程

首先看上边的Demo,在MyService中实现了对象

private Binder mBinder= new IUser.Stub() {        @Override        public boolean login(String userName, String userPwd) throws RemoteException {            Log.d(TAG,userName+"   登录成功!");            return true;        }        @Override        public void logout(String userName) throws RemoteException {            Log.d(TAG,userName+"   退出成功!");        }    };@Override    public IBinder onBind(Intent intent) {        return mBinder;    }

IUser.Stub,在文章开头的源码中可以找到,它是一个抽象类,继承与Binder类,这个对象在service与MyService通信使用的Binder,同时它实现了我们定义的Aidl的IUser接口,也就是说这个Binder拥有了我们的自定义方法(在Stub中只是将IUser接口的方法继承了下来,但并没有实现,直到在我们创建这个实例时手动实现了方法),然后通过Binder的onTransact()的code参数将client的请求类型与本地的方法绑定,在将此Binder对象返回给client,单从应用层来看,如此便将方法暴露给了client。(后面注意Stub类是我们自定义的Binder类,后面说的binder对象便是Stub对象)

再看client代码:

ServiceConnection mServiceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            Log.d(TAG, "onServiceConnected");            mUserBinder = IUser.Stub.asInterface(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    }; bindService(intent,mServiceConnection,BIND_AUTO_CREATE);

在client中,创建了一个ServiceConection对象,并在bindService()启动服务时进行绑定,当服务启动ServiceConnection连接成功时:
1、service的onBind()方法被执行,返回我们我们创建的Binder对象(mBinder)
2、clientServiceConection对象的onServiceConnected(ComponentName name, IBinder service)方法被执行,参数service用来接收service返回的Binder,然后在下面这一句代码将得到的Binder转为可识别的对象(asInterface如果是同进程直接返回收到的binder,如果是跨进程会返回一个Binder的内部代理类Proxy的实例),这样client就得到了在Service中创建的Binder,通过aidl的IUser引用即可使用Binder的方法。

mUserBinder = IUser.Stub.asInterface(service);

那么IUser.Stub.asInterface(service),这个方法是到底是如何处理收到的binder对象的呢?来看源码:

public static com.ipctest.aidl.IUser asInterface(android.os.IBinder obj){           /*obj为service的binder对象,先做非空判断*/        if ((obj==null)) {            return null;        }        /*这里验证binder对象中DESCRIPTOR是否合法,是直接返回binder,否返null(详解见下面的源码)*/        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);        /*如果不为null,将binder对象转换为我们定义的IUser类型,返回给client的ServiceConnection*/        if (((iin!=null)&&(iin instanceof com.ipctest.aidl.IUser))) {            return ((com.ipctest.aidl.IUser)iin);        }        /*如果为null代表为跨进程请求,创建一个Proxy代理对象(Stub的内部类,后面详解)*/        return new com.ipctest.aidl.IUser.Stub.Proxy(obj);    }

上面可以看到asInterface将binder对象进行了处理,如果请求为当前进程,那么同进程可共享内存,即可直接使用service返回的binder对象,但如果请求是跨进程,则将binder对象包装为一个代理对象,返回给client,到这里Binder的传递流程就通了,但如何区分是当前进程还是跨进程呢?关键就在Stub的queryLocalInterface的方法,我们继续深入,这个方法是Stub的父类Binder中的方法,我们看源码:

//在Stub的asInterface中调用了这个方法,DESCRIPTOR为Stub的接口唯一标识,默认为包名路径obj.queryLocalInterface(DESCRIPTOR);//queryLocalInterface源码public IInterface queryLocalInterface(String descriptor) {       //在这里先将传入的接口标识和Binder的字段mDescriptor对比是否一样,是返回mOwner字段,否返回null       //那mDescriptor和mOwner两个字段又代表了什么呢,我们找到其获取值得地方,见下一个方法       if (mDescriptor.equals(descriptor)) {           return mOwner;       }       return null;   }//在看这两个字段的赋值之前,先看看他们的类型,这是这两个字段的声明//可以看到mOwner是一个IInterface 接口引用,也就是说他可以接受任何类型的对象实例private IInterface mOwner;//mDescriptor为一个字符串,然后看赋值private String mDescriptor; //在这里我们发现,attachInterface方法中对mOwner和mDescriptor字段进行了赋值//既如此,那么我们找到attachInterface方法的调用者即可知道这两个字段的内容,看下一个方法public void attachInterface(IInterface owner, String descriptor) {       mOwner = owner;       mDescriptor = descriptor;   }//仔细看了Stub结构的读者应该可以发现,attachInterface方法在Stub的构造方法中就被调用了public Stub(){            //这里传入的参数为this为我们传递的stub对象本身            //DESCRIPTOR为Stub的接口标识,在Stub源码可以看到            this.attachInterface(this, DESCRIPTOR);        }

也就是说在binder对象被创建时,使用attachInterface(this, DESCRIPTOR)将其自身和接口标识存入mOwner和mDescriptor字段
在client接收到这个对象后,调用queryLocalInterface(DESCRIPTOR)方法,将Stub类的DESCRIPTOR字段与mDescriptor比较,如果相同表示client请求来自同一进程,返回mOwner字段,否则表示是跨进程请求,返回null,那么就有一个问题:

Service返回的是同一个Binder对象,且这个对象在构造时就已经为mDescriptor字段赋值,那么为什么在同一进程的client在进行mDescriptor.equals(descriptor)比较的时候是为true成立的,而client在另一个进程时这个条件就为false了呢?

这个就涉及到更底层的知识了,从系统的角度来看,client得到的binder对象引用并不是由service直接交付的,而是通过Binder驱动: 当我们的client需要serivice中binder对象的引用而又不在同一进程时,service首先会将本地内存中binder对象的名字通过处于内核的Binder驱动交给ServiceManagerServiceManager将binder的引用存储起来,在client中通过binder的名字来访问ServiceManager中存储的对binder对象的引用,然后Binder驱动会为client也创建一个Binder对象,不同的是这个对象并不是一个Binder实体,而是对service中binder的方法调用请求的封装(调用通过从ServiceManager中得到的binder引用)

那么到这里就可以知道,之所以mDescriptor.equals(descriptor)在跨进程的时候会不成立,是因为在Binder驱动为client创建binder对象时,这个对象只是一个对service中的binder实体各种业务请求的封装,而不是一个真正的binder实体

想要深入理解这个部分,可以看看:http://blog.csdn.net/universus/article/details/6211589

现在来整理一下:
1、首先在binder对象被创建时,在构造方法中调用attachInterface(this, DESCRIPTOR)将其自身和接口标识存入mOwner和mDescriptor字段
2、Service 的onBinder()返回binder对象,Bidner驱动创建mRemote交给client,client得到binder对象
3、client进行请求方式判断(同进程或跨进程),是同一个进程直接返回binder对象,否则返回代理对象
4、client使用service业务
client拿到binder对象的过程就到这


Binder 请求处理过程

先说service和client在同一进程的情况:同进程内存是可以共享的,所以前面解释过当请求来自同一进程时,client得到的binder就是我们创建的mBinder对象,所以我们调用其方法就是常规的方法调用,

而service和client不在同一进程时,就产生了跨进程的问题,我们知道,不同进程的内存是不可共享的,一个新的进程甚至会导致Application和各个静态变量的重复创建,所以我们就无法直接对binder的方法进行调用,这时就需要通过Binder驱动去访问seriver中的binder。

前面说了,client中使用的binder对象是Binder驱动为client创建的一个“对service中binder的方法调用请求的封装”,那这个调用请求是如何实现的呢?前面讲解了当client请求来自跨进程时,会创建一个Stub中的Proxy类的实例,我们在来看看这个Proxy类的源码,在前面的源码中可以看到Proxy类同样实现了IUser接口,先看看构造方法

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

在Stub的asInterface()方法中有这句代码,就是前面说的当请求为跨进程时创建Proxy的对象

return new sikang_demo.ipctest.IUser.Stub.Proxy(obj);

可以看到这里将obj作为构造参数,记录在了Proxy对象中,也就是说它持有了service的service引用,然后再看源码

@Override             public boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException            {                /*用于存储方法参数和返回值*/                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                boolean _result;                try {                    /*写入接口标识、和binder中login()方法需要的参数userName,和userPwd*/                    _data.writeInterfaceToken(DESCRIPTOR);                    _data.writeString(userName);                    _data.writeString(userPwd);                    /*调用mRemote的transact方法申请serive业务*/                    mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);                    /*得到返回值*/                    _reply.readException();                    _result = (0!=_reply.readInt());                }finally {                    _reply.recycle();                    _data.recycle();                }                /*将结果反馈给客户端*/                return _result;            }            @Override             public void logout(java.lang.String userName) throws android.os.RemoteException{                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    _data.writeString(userName);                    mRemote.transact(Stub.TRANSACTION_logout, _data, _reply, 0);                    _reply.readException();                }finally {                    _reply.recycle();                    _data.recycle();                }            }

这里实现了IUser接口的两个方法,在跨进程的客户端请求binder方法业务时,直接与客户端接触的就是这里的方法,我们看看方法的内容,方法的处理是一样的,这里根据login方法来讲解

首先在方法开始创建了两个Parcel对象,我们知道Parcel是Java中序列化的一种实现,在跨进程通信时,传输的数据必须可序列化,这里这两个Parcel对象 _data_reply分别用于保存方法参数和接收返回值,可以看到在_data中写入了一个binder的接口标识,和login方法需要的两个参数,然后调用了

mRemote.transact(Stub.TRANSACTION_logout, _data, _reply, 0);

mRemote为Proxy被创建时传入的binder引用,先来看看这个方法几个参数的含义:

public final boolean transact(int code, Parcel data, Parcel reply, int flags)

code:请求方法的编号,当这个请求被service的binder收到时,就是通过这个参数来确定客户端请求的是哪个方法。
data:请求方法需要的参数
reply:用于接收方法的返回值
flags:一般用不到这个参数

data和reply很好理解,但这个code是什么呢?先看看Stub类的最下面有这么两句代码

static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_logout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

这就是我们在IUser接口中定义的两个方法的code,由此可知,binder被创建时,会为它从接口继承来的每个方法都创建一个唯一的code,用来为方法编号,当client需要请求方法时,只需要向上面一样,传入一个方法code,及这个方法的参数和返回值保存者,就可以实现对指定方法的调用。
现在知道了client是这么请求的,那service又是如何响应的呢?看Stub中的onTransact类,

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{            switch (code){                case INTERFACE_TRANSACTION:                {                    reply.writeString(DESCRIPTOR);                    return true;                }                case TRANSACTION_login:                {                    /**取出方法参数*/                    data.enforceInterface(DESCRIPTOR);                    java.lang.String _arg0;                    _arg0 = data.readString();                    java.lang.String _arg1;                    _arg1 = data.readString();                    /**调用本地方法,传入参数*/                    boolean _result = this.login(_arg0, _arg1);                    reply.writeNoException();                    /**将返回值写入reply对象*/                    reply.writeInt(((_result)?(1):(0)));                    return true;                }                case TRANSACTION_logout:                {                    data.enforceInterface(DESCRIPTOR);                    java.lang.String _arg0;                    _arg0 = data.readString();                    this.logout(_arg0);                    reply.writeNoException();                    return true;                }            }            return super.onTransact(code, data, reply, flags);        }

onTransact()方法就是service的binder实体对client请求的响应方法,可以看到onTransact()的参数和client中调用的transact()方法参数相同,在client发出请求之后,Binder驱动 将这个请求通过ServiceManager提供的binder引用将请求转到binder的onTransact()方法中,如此service便受到了client的请求,然后在看看service是这么处理这个请求的

还是看login方法,看case TRANSACTION_login 中的处理:
首先取出了client写入到data中的参数,然后调用了login方法,最后将返回值写入了client提供的reply对象中,这时client就可以从reply中读取返回结果了

这就是Binder的请求处理过程,Binder就介绍到这,如果有什么考虑不周,大家可以帮忙提出来

1 0
原创粉丝点击