Android中的Binder机制二(匿名Binder)

一、前言

之前学习了实名Binder《实名Binder》和AIDL的基本使用《AIDL的使用》,本篇文章在上两篇的基础上继续学习匿名Binder,并结合aidl中的例子理解Binder在应用层面上具体是如何通信的,文中分析所用到的代码有些是前两篇文章中,这里就不在重复粘贴了。

二、匿名Binder

平常开发过程中,通过aidl和binderService方式获取的Binder是不会注册到SM中的,这种Binder就是匿名Binder,匿名Binder要依赖于实名Binder进行传递,比如Service中的onBind方法返回的Binder对象是需要通过AMS来传递给客户端的(具体的过程请参考Service的绑定过程《Service的绑定过程》)。

在之前的aidl例子中,RemoteService的onBind方法返回了一个LocalBinder对象,它继承并实现了IBookManager.Stub,这个对象就是一个匿名的Binder对象,当客户端binderService的时候,会调用到RemoteService的onBind方法,其返回值LocalBinder对象会通过AMS发布出去,最终会通过ServiceConnection的onServiceConnected方法将LocalBinder信息(注意是信息,不是LocalBinder对象本身,因为这个过程是客户端和AMS进行跨进程通信的过程,在经过Binder驱动的时候,不能直接传递java对象,而是转换成C/C++可识别的结构体进行传递数据的)回调给客户端,客户端的onServiceConnected拿到LocalBinder信息后,在通过Stub的asInterface方法获取LocalBinder在本地的代理对象,由这个过程可以知道,LocalBinder这个匿名Binder是需要依赖于AMS这个实名Binder进行传递的。下面结合之前的aidl例子梳理下Binder的通信过程是怎么样的。

三、结合AIDL分析Binder的通信

从AIDL文件自动生成的IBookManager.java类中可以知道,其核心功能的实现在Stub和Stub的内部类Proxy两个类中。

  • Stub: Stub继承了Binder又实现了IBookManager接口,所以Stub既是个Binder类又具备客户端所需要的业务能力。
  • Proxy: Proxy的构造方法中会拿到一个Binder代理对象,它对Binder代理对象进行了包装,同时Proxy又实现了IBookManager接口,所以它具备Binder的能力也具备客户端所需要的业务能力。

我们在客户端中绑定服务的时候,会在连接的回调方法onServiceConnected中调用Stub的asInterface方法获取IBookManager类型的一个对象,然后用这个对象去调用IBookManager里面的业务方法。下面就以调用IBookManager里面的addBook方法为例来跟踪一下整个调用过程,首先先看下ServiceConnection 的代码:

    //声明IBookManager类型的对象
    private IBookManager mIBookManager;

    //创建连接
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //关键代码1,获取IBookManager类型的对象
            mIBookManager = IBookManager.Stub.asInterface(service);

			//关键代码2,打印出mIBookManager的真实类型
            if (mIBookManager instanceof IBookManager.Stub) {
                Log.e("znh", "mIBookManager instanceof IBookManager.Stub");
            }
            Log.e("znh", "mIBookManager.getClass().getName():" + mIBookManager.getClass().getName());
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

由关键代码1可知,通过Stub的asInterface方法获取到了一个IBookManager 类型的对象,然后使用这个对象调用IBookManager 里的业务方法,由于这个对象不是我们自己new出来的,所以它的真实类型我们就不好直接判定,那么这个对象的真实类型是什么呢,我们又是怎样调用这个对象里的方法的呢,这里要分两种情况进行讨论:

1、客户端和服务端在同一个进程

在这种情况下,我们先通过关键代码2的log打印直观的看一下mIBookManager的真实类型,下面附上打印结果:

 mIBookManager instanceof IBookManager.Stub
 mIBookManager.getClass().getName():com.znh.aidl.server.RemoteService$LocalBinder

从上面的打印结果可知,mIBookManager的真实类型就是服务端的LocalBinder对象本身,而LocalBinder继承了Stub,所以它也是Stub类型的。当我们调用mIBookManager的addBook方法的时候,就是直接调用Stub里的addBook方法,Stub里的addBook方法的具体实现是由RemoteService的内部类LocalBinder完成的,所以最终调用的是服务端中LocalBinder的addBook方法。这个调用过程不涉及进程间通信,是一个常规调用过程。
上面通过log打印的方式直观的看出了mIBookManager的真实类型和调用过程,下面根据相关代码深入分析一下,首先先看下Stub的asInterface方法和asBinder方法:

Stub的asInterface方法:

		/**
         * Cast an IBinder object into an com.znh.aidl.server.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.znh.aidl.server.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            
            //关键代码3,如果客户端和服务端是同一个进程,条件成立,直接返回服务端本地对象
            if (((iin != null) && (iin instanceof com.znh.aidl.server.IBookManager))) {
                return ((com.znh.aidl.server.IBookManager) iin);
            }

			//关键代码4,如果客户端和服务端不是一个进程,就创建一个服务端本地对象的代理对象
            return new com.znh.aidl.server.IBookManager.Stub.Proxy(obj);
        }

Stub的asBinder方法:

			//返回当前Binder对象
		   @Override
            public android.os.IBinder asBinder() {
                return this;
            }

由asInterface方法的代码可知,如果客户端和服务端在同一个进程,那么该方法的参数就是LocalBinder的真身,是在RemoteService的onBinder方法里返回的LocalBinder对象,在关键代码3处,经过强转就直接返回该真身,那么此时调用LocalBinder的addBook方法是怎样一个过程呢,经过分析可知,LocalBinder继承了Stub,也就是说LocalBinder是Stub的实现类,通过asBinder方法可知,返回的当前Binder对象this就是Stub对象本身,也就是Stub的具体实现类服务端的LocalBinder本地对象,那么就可以直接调用Stub里面的addBook方法,不会走transact和onTransact逻辑,这个调用过程不跨进程。

2、客户端和服务端不在同一个进程

在这种情况下,我们还是先通过关键代码2的log打印直观的看一下mIBookManager的真实类型,下面附上打印结果:

 mIBookManager.getClass().getName():com.znh.aidl.server.IBookManager$Stub$Proxy

从上面的打印结果可知,此时mIBookManager的真实类型是Stub的内部类Proxy,另外通过Stub的asInterface方法中的关键代码4处也可看出,该方法返回的是Proxy类型的对象。
此时asInterface方法的参数不再是服务端的LocalBinder本地对象,而是LocalBinder的一个代理对象。这点可以从Proxy的asBinder中看出:

Proxy的asBinder相关方法:

           private android.os.IBinder mRemote;

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

			//返回远程的Binder代理对象
            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

那么此时调用mIBookManager的addBook方法是怎样一个过程呢,经过上面的分析可知,mIBookManager是Proxy类型的,那么就会调用Proxy类里面的addBook方法,Proxy的addBook方法如下:

Proxy的addBook方法:

		    /**
             * 添加图书
             */
            @Override
            public void addBook(com.znh.aidl.server.Book book) throws android.os.RemoteException {
            
				//关键代码5,创建携带参数信息的对象
                android.os.Parcel _data = android.os.Parcel.obtain();

				//关键代码6,创建携带返回值信息的对象
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }

					//关键代码7,调用LocalBinder代理对象的transact发起远程调用
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
  • 通过关键代码5可知,会创建一个Parcel 类型的对象_data用来携带客户端调用方法时的参数信息。
  • 通过关键代码6可知,会创建一个Parcel 类型的对象_reply 用来携带服务端给客户端的返回值。
  • 通过关键代码7可知,mRemote就是LocalBinder的代理对象,这个代理对象会调用它的transact方法发起远程请求,在transact方法中会调用服务端LocalBinder的本地对象的onTransact方法,由于LocalBinder的onTransact方法是继承Stub的,所以最终调用的是Stub的onTransact方法。
  • 这个方法是运行在客户端的,当客户端发起该方法的远程调用时,客户端当前线程会被挂起,直到服务端返回结果,客户端当前线程才会继续运行,所以这是一个同步过程。服务端的方法执行很可能是耗时操作,所以发起远程调用时尽量不要在客户端的主线程中,以免出现ANR。

在上面的远程调用中,会调用到服务端Stub的onTransact方法,下面分析一下Stub的onTransact方法:

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_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.znh.aidl.server.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }

				//关键代码8,真正的处理addBook的逻辑
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.znh.aidl.server.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.znh.aidl.server.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

该方法是运行在服务端的Binder线程池中的,服务端通过code来匹配客户端要调用的是哪个方法,在关键代码8处可知TRANSACTION_addBook是客户端调用addBook方法的code值。匹配到相应的方法后,就从data中取出该方法需要的参数信息,然后进行逻辑处理,最终将处理结果放到reply中返回给客户端。这样整个调用过程就结束了。

总之,如果客户端和服务端在同一个进程中,客户端要调用服务端某个对象的方法是直接获取到服务端的本地对象进行操作的。如果客户端和服务端不在同一个进程中,那么客户端要调用服务端某个对象的方法,会从SM中获取到服务端该对象的一个代理对象进行操作的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章