Android核心架构---Binder通信机制

前言

     写这篇博客还有个小故事:前段时间,公司事情比较少,我闲下来就打算学习下Binder机制,公司有写工作日志的习惯,于是我当天就写的学习Binder机制,结果我们安卓组的小组长看到了= =,然后让我准备一下组内开个交流会,让我给大家分享一下。留下一脸懵逼的我,因为我当天看完Binder机制相关的东西还啥都不懂,没办法,接下一段时间就在恶补Binder相关的知识,于是才有了这篇文章。哈哈,不扯了,接下来进入正题!!!


什么是Binder?


        在这里我引用《Android开发艺术探索》中对Binder的介绍------>直观来说,Binder是Android中的一个类,它实现了IBinder接口;从IPC角度说,Binder是Android中的一种跨进程的通信方式,Binder还可以理解为一种虚拟的物理设备,也就是底层的驱动/dev/binder,该通信方式是Android操作系统所独有的;从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当binderService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个对象,客户端就可以获取服务端提供的服务或数据,也就是可以调用服务端的函数(博主补充:这里的客户端和服务端是指:进程A要访问进程B的一个函数,那么进程A就称为客户端,进程B就是服务端,这是相对而言的)。

         在上面的介绍中其他都比较好理解,”Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁“这句我一会儿会重点在文章的最后给大家重点介绍。


为什么Android使用Binder进行跨进程的通信?


   大家应该都知道我们Android系统就算把所有进程都干掉,最少还会有一个进程在运行也就是我们的Launcher进程,然后从Launcher进入我们的应用,包括接下的startActivity、startService、bindService等操作其实都是在进行进程间通信,也就是说我们Android系统的进程通信时非常频繁的。而如此频繁地进程间通信就要求性能一定要高。而作为Android操作系统通信的安全性也一定是要考虑在内的。基于性能和安全性的考虑,Linux的通信方式都不能同时满足这两个要求所以才有了我们的Binder。


为什么写AIDL可以生成对应java文件?


         一般情况下,我们如果进行进程间通信一般有两种方式:通过AIDL、通过Messenger。而Messenger的底层也是由AIDL来完成的,所以我们今天就以AIDL来为大家讲解,我们进程间通信一般是通过绑定服务和AIDL来完成的,这个比较基础就不细说了。不知道有没有人想过”为什么写一个AIDL类就能生成一个JavaBean类?“其实,安卓编译器根据AIDL生成的这个javaBean其实是一种模板的写法,就类似于AndroidStudio里面通过Gson插件生成Json解析的Javabean.在我们的SDK下有个build_tools文件夹,这里面有安卓各个版本的编译工具,每个版本都会有一个aidl.exe的文件,在编译时就是通过它将AIDL文件编译成了相对应的java文件,这样做的目的是为了简化开发者开发步骤,不用写过多的重复代码,反正是模板类直接用个工具生成就好了。



Binder通信的流程


在看Binder流程之前我建议先自己写一下两个进程间通信的Demo这对于理解Binder的通信机制将会有很大的帮助。

首先看一个Binder通信的大致流程图:




现在场景是:进程A要访问进程B的一个加法运算函数
进程A首先要绑定服务,在此时会调用进程B Service里面的onBind方法,此时Binder驱动会拿到进程B的IBinder引用,进程A在绑定成功的时候会回调ServiceConnection里面的onServiceConnected(ComponentName name, IBinder service)方法,第二个参数就是进程B的IBinder引用,然后我们通过这个引用就可以获取AIDL生成的JavaBean对象然后进程A就会通过这个javaBean对象拿着表示经过JNI调用告诉Binder驱动进程A要去调用哪个进程的的哪个方法,Binder驱动收到之后就会调用进程B相关的加法运算函数,然后将计算结果通过Binder驱动返回给进程A这就是一次Binder通信的大体流程。接下来我们通过代码来分析详细的调用流程。

进程A:

public class MainActivity extends AppCompatActivity {
//这个就是AIDL生成的用来进行进程间通信的JavaBean
private ArithmeticManager mArithmeticManager;
//这个类就类似于一个监听用于回调绑定和解绑服务的(其实服务的绑定与解绑这个消息的传递底层也是由binder来完成的)
private ServiceConnection mServiceConnection = new ServiceConnection() {
        //服务绑定的时候调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //第三步:在进程B绑定完成后会将IBinder引用返回,此处的IBinder不能称作是一个对象,依旧是称为引用它所指向的就是Binder驱动里面的IBinder引用,接下来就要进入到AIDL生成的JavaBean对象里面进行分析,这也是Binder通信机制的核心
            mArithmeticManager = ArithmeticManager.Stub.asInterface(service);
        }
	//服务解绑的时候调用
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnBind = (Button) findViewById(R.id.btn_bind);
        Button btnAdd = (Button) findViewById(R.id.btn_add);
        btnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
		//第一步:绑定服务
                Intent intent = new Intent();
                intent.setAction("binder.binderserver.mainservice");
                bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
            }
        });
        btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mArithmeticManager != null) {
                    try {
			//第四步:开始进行加法运算
                        int num = mArithmeticManager.add(6, 6);
                        Toast.makeText(MainActivity.this, num + "", Toast.LENGTH_SHORT).show();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}

进程B:

public class AddService extends Service {
    //第二步:在进程A绑定的时候会调用进程B的这个方法将进程B的IBinder引用返回(IBinder引用其实是进程通过AIDL文件生成JavaBean的一个描述,类似于镜像一个东西,它包括这个类的所有方法和常量,它被底层的Binder驱动所持有)
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    //通过AIDL文件生成JavaBean的实例化,具体的加法运算就是在这里完成的
    private ArithmeticManager.Stub mBinder = new ArithmeticManager.Stub() {
        @Override
        public int add(int x, int y) throws RemoteException {
            return x + y;
        }
    };

}


AIDL文件:

// ArithmeticManager.aidl
package binder.binderclient;

// Declare any non-default types here with import statements
//其实这就是一个接口然后里面有一个方法,主要看它对应的javaBean才是接下来的核心
interface ArithmeticManager {
    int add(int x,int y);
}

通过AIDL生成的JavaBean:

package binder.binderclient;
//这个接口就是我们在AIDL文件中定义的接口,只不过它又继承了IInterface这个接口IInterface其实里面只有一个asBinder() 
//方法作用是返回当前IBinder对象。ArithmeticManager 接口包含一个内部类Stub和它本身的方法add即加法运算方法(大家看这
//个类之前最好现在源码里看下IBinder和Binder的继承关系以及他们内部的方法这有助于理解这个类里面的代码)
public interface ArithmeticManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
//如大家所看到的这个类继承了Binder对象又实现了ArithmeticManager 接口,继承Binder对象后就会实现Binder对象里面
//的onTransact()方法也就代表stub具有了跨进程通信的能力,具体原因在onTransact()这个方面前面再讲,实现ArithmeticManager 
//就是为了实现asBinder()方法并能调用add()实现具体的运算方法
public static abstract class Stub extends android.os.Binder implements binder.binderclient.ArithmeticManager {
        //这个常量其实一个标识,一般来讲定义为包名,当然如果是我们写也可以定义为其他的,但是进程A和进程B这个常量要保持一致
        private static final java.lang.String DESCRIPTOR = "binder.binderclient.ArithmeticManager";

        /**
         * 这个构造方法做的事是把当前类和标识进行保存,保存后一会儿在下面会用到
         */
        public Stub() {
            (这个代码大家自己可以写Demo然后点进去看一下)
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * 第三步
         * 
         */
        public static binder.binderclient.ArithmeticManager asInterface(android.os.IBinder obj)     {
            //这个地方是进程A传递过来的IBinder引用,首先会判空,如果为空就直接返回空这个很好理解
            if ((obj == null)) {
                return null;
            }
            //这句代码的意思是它会拿着当前的标识去和刚才保存的标识进行比对,如果一样则说明当前绑定的服务和调用方是在同一进程,
            //我直接返回刚才保存的本类对象就可以了然后直接调用本类的add()方法,它的具体实现也是会在service里面,如果不一样则说明
            //当前绑定的服务和调用方不是在同一进程,那么此时我就要把任务交给本类的内部类Proxy来完成了。
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof binder.binderclient.ArithmeticManager))) {
                return ((binder.binderclient.ArithmeticManager) iin);
            }
            return new binder.binderclient.ArithmeticManager.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_add: {
                    //验证身份标识
                    data.enforceInterface(DESCRIPTOR);
                    //将刚才传过来的参数从data里面读出来
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    //调用本类的加法运算方法,也就是第七步,执行完如果有异常就写异常,没有异常就写入执行结果到reply中然后到第八步
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
        //第五步:
        //首先介绍一下这个内部类,Proxy看名字就知道这是一个代理类,也可以说是远程服务在本进程(进程A)的代理,因为我们进程A直接
        //调用进程B的函数是调用不了的,所以我就创建出来一个代理类我把参数都交给这个代理类,然后由这个代理类去访问底层Binder驱动然
        //后Binder驱动会调用进程B对应的方法并将结果返回回来,不得不说这个设计确实精妙
        private static class Proxy implements binder.binderclient.ArithmeticManager {
            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 x, int y) throws android.os.RemoteException {
                //Parcel是个可序列化的对象,这里有个Parcel池的概念,先从池子里面取出来两个Parcel对象,_data用于我们
                //写进去参数传递给进程B,_reply用于接收进程B函数的执行结果
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    //首先进行写标识这个的作用既是告诉Binder驱动我要访问哪一个进程,又能作为认证的一种手段
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //写进去两个参数
                    _data.writeInt(x);
                    _data.writeInt(y);
                    //开始进行远程调用的方法,transact这个方法是在Binder对象里面实现的,第一个参数是
                    //告诉进程B我要调用你哪个方法,第二个、第三个参数刚才已经介绍过了,第四个参数默认
                    //是0,0代表的是当前方法执行后我进程A调用此方法的线程会挂起,直到进程B执行完后给我
                    //返回结果现场才会唤醒,所以如果进程B执行的是耗时操作那我们在一开始调用也就是第四步
                    //的代码要写在子线程中,第四个参数还可以手动设置为FLAG_ONEWAY,它当前方法执行后我不
                    //会挂起当前线程,不论你有没有返回结果,我都会继续走下去。然后Binder驱动会去调用进
                    //程B通过AIDL生成的和进程A一摸一样的javaBean中Stub类里面的onTransact()方法,也就是接下来的第六步。
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    //第八步:执行完后,先读异常,有异常就抛出异常,没有的话就会将结果读出来,将Parcel
                    //对象释放,然后将结果返回。
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
        //这个常量的作用就是标识一下方法
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }
    //第七步,他的具体实现就是在AddService里面实现的
    public int add(int x, int y) throws android.os.RemoteException;
}

看完上面的分析是不是对最初的那个图了解了呢,下面看这个图:




回头想想其实也就是进程A调用AIDL JavaBean的Proxy代理类中的transcat()方法,然后transcat()会通过底层Binder驱动调用进程B中AIDL JavaBean的Stub类中的onTranscat()方法实现的最终调用,如果进程B要调用进程A中的函数也是一样的,理清楚了就会发现从浅层次看也不复杂,如果这些都看懂了再去阅读安卓底层服务间通信的代码就会对其整体的流程有个清晰的了解。


为什么说BinderServiceManager连接各种Manager和相应ManagerService的桥梁?


    

 如果看懂了上面的介绍其实这个就比较容易理解了,稍微看下源码就能理解了,我就只放张图了:




最后补充一点我在最开始说的”进程A首先要绑定服务,在此时会调用进程B Service里面的onBind方法,此时Binder驱动会拿到进程B的IBinder引用”,那么Binder驱动是怎么拿到进程B的IBinder引用的呢?肯定有个地方做了初始化。其实在进程B实例化Stub的时候会调用Stub的构造方法这时也会调用Binder类的构造方法,因为Stub继承了Binder,Binder的构造方法里面有个Native方法init(),就是这个方法调用了底层的驱动,Binder驱动在此时会拿到IBinder引用。大家有兴趣可以去看下源码。

结语

如果本文有什么错误请大家多多指出,最后感谢《Android开发艺术探索》一书,感谢鸿洋大神的博客http://blog.csdn.net/lmj623565791/article/details/38461079 



  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值