应用Binder探索(下)

在应用Binder探索(上)中提到Binder测试的一个demo程序,主要功能为,有一个名为BookManagerService的远程服务,它持有一个用于保存Book对象的list,并对外提供该list的get和add方法,而客户端Activity绑定服务并访问其接口。在本文将描述getBookList和addBook方法的调用流程,其中涉及到Binder的通信。

一、asBinder与asInterface

        我们知道客户端持有Proxy,是对BinderProxy包装,而服务端持有Stub,继承了Binder。对于IInterface接口的asBinder方法和Stub类的asInterface静态方法,它们起到什么作用?为什么要有它们?可以看下代码:

//Stub类
        public Stub() {
		this.attachInterface(this, DESCRIPTOR);
	}
	public static IBookManager asInterface(IBinder obj) {
		if ((obj == null)) {
			return null;
		}
		android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
		if (((iin != null) && (iin instanceof IBookManager))) {
			return ((IBookManager) iin);
		}
		return new IBookManager.Stub.Proxy(obj);
	}
	@Override
	public android.os.IBinder asBinder() {
		return this;
	}

//Proxy类
        private IBinder mRemote;
	Proxy(IBinder remote) {
		mRemote = remote;
	}
	@Override
	public IBinder asBinder() {
		return mRemote;
	}
         我们看到asInterface方法,传入的参数类型是IBinder(因为Binder和BinderProxy都继承了IBinder接口,这里的参数可接收Binder或BinderProxy对象)。接着是queryLocalInterface(String)方法: 

//Binder实现
   private IInterface mOwner;
   private String mDescriptor;
   public void attachInterface(IInterface owner, String descriptor) {
      mOwner = owner;
      mDescriptor = descriptor;
   }
   public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }
//BinderProxy实现
   public IInterface queryLocalInterface(String descriptor) {
     return null;
   }
  1. 传入的是Binder对象,就会返回mOwner,而mOwner是在Stub构造函数里调用attachInterface(this,DESCRIPTOR)赋值的,也就是说仅仅返回它自己。
  2. 传入的是BinderProxy对象,把其作为Proxy类构造函数参数,new一个Proxy对象返回。

        总的来说,asInterface方法就是把IBinder转化成IBookManager,在客户端输入BinderProxy对象,输出Proxy对象,在服务端输入Binder对象,输出Stub对象。而此时asBinder方法就能一目了然,与asInterface恰恰相反,在服务端通过Stub类返回的是this,即BInder对象,而在客户端通过Proxy类返回的是mRemote,即BinderProxy对象。在客户端调用asBinder获取BinderProxy对象有什么作用呢(在服务端调用asBinder作用不大)?BinderProxy对象可以ping服务端,判断连接是否存活,甚至还能传入连接死亡通知的监听,而asInterface方法获取Stub对象或Proxy对象有什么作用呢?就是我们接下来要讲的。


二、getBookList和Parcel

1. getBookList调用流程

        首先在客户端Activity通过onServiceConnected回调拿到BinderProxy对象,从而生成Proxy对象,进而调用Proxy的getBookList方法,代码如下:

	@Override
	public List<Book> getBookList()throws RemoteException {
		Parcel _data = Parcel.obtain();
		Parcel _reply = Parcel.obtain();
		List<Book> _result;
		try {
			_data.writeInterfaceToken(DESCRIPTOR);
			mRemote.transact(Stub.TRANSACTION_getBookList, _data,_reply, 0);
			_reply.readException();
			_result = _reply.createTypedArrayList(Book.CREATOR);
		} finally {
			_reply.recycle();
			_data.recycle();
		}
		return _result;
	}
        这里涉及到Parcel和Book以及BinderProxy对象mRemote,它们都有什么关系呢?Book实现了Parcelable接口,具有可序列化能力,而Parcel类具有能处理序列化数据的能力,用于处理Book传输前的基本操作,BinderProxy具有传输能力。有一种很形象的说法,实现了Parcelable接口的Book对象是货物,而Parcel类是包装货物的箱子,writeInterfaceToken用于标明箱子地址,BinderProxy是快递员,负责把装好箱子的货物运输到服务端。

        回到代码,从Parcel对象池中获取两个对象,分别是用于存储输入参数的_data对象和接收返回值的_reply对象,writeInterfaceToken给_data对象打上标识,由于getBookList没有输入参数,因此没有_data的填充,直接调用BinderProxy的transact方法传输出去了(底下native代码,Binder驱动,直到服务端),默认情况下,Binder服务端交互是非oneway模式,因此程序会被阻塞在transact方法里,直到服务端返回数据。

        接着数据传递到服务端onTransact响应,Stub的getBookList方法被调用,代码如下:

        @Override
	public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
		switch (code) {
... ... ...
		case TRANSACTION_getBookList: {
			data.enforceInterface(DESCRIPTOR);
			List<Book> _result = this.getBookList();
			reply.writeNoException();
			reply.writeTypedList(_result);
			return true;
		}
... ... ...
		return super.onTransact(code, data, reply, flags);
	}

        @Override
     public List<Book> getBookList() throws RemoteException {
          return mBookList;
     }

        服务端BookManagerService接收到客户端请求,Stub的onTransact方法被回调,code表示getBookList方法标识,data是客户端传来的参数,reply是服务端返回的数据。代码里首先是enforceInterface验证DESCRIPTOR(与客户端的writeInterfaceToken相对应),然后是调用服务端getBookList的具体实现,返回List对象,接下来就是把数据填充reply,writeNoException表示接口正常执行无异常,writeTypeList是list对象的数据填充,它们会根据简单协议写数据,最后返回客户端请求。

        到了客户端,也就是transact方法后,parcel对象reply调用readException(),有异常则会被抛出,否则执行createTypedArrayList,构造出一个客户端的list对象返回到客户端activity调用处。

2. Parcel

2.1 Parcel的Exception处理

readException:

public final void readException() {
    int code = readExceptionCode();
    if (code != 0) {
        String msg = readString();
        readException(code, msg);
    }
}

public final int readExceptionCode() {
    int code = readInt();
    if (code == EX_HAS_REPLY_HEADER) {
        int headerSize = readInt();
        if (headerSize == 0) {
            Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
        } else {
            StrictMode.readAndHandleBinderCallViolations(this);
        }
        // And fat response headers are currently only used when
        // there are no exceptions, so return no error:
        return 0;
    }
    return code;
}

public final void readException(int code, String msg) {
    switch (code) {
        case EX_SECURITY:
            throw new SecurityException(msg);
        case EX_BAD_PARCELABLE:
            throw new BadParcelableException(msg);
        case EX_ILLEGAL_ARGUMENT:
            throw new IllegalArgumentException(msg);
        case EX_NULL_POINTER:
            throw new NullPointerException(msg);
        case EX_ILLEGAL_STATE:
            throw new IllegalStateException(msg);
    }
    throw new RuntimeException("Unknown exception code: " + code
            + " msg " + msg);
}
       readException首先会readExceptionCode,里面readInt,从序列化数据的开头读取int大小的数据(EX_HAS_REPLY_HEADER也是无异常的一种,用于GatheredViolations,不影响暂时不用管,因为其最后还是返回0),readExceptionCode返回值是0表示无异常,readException便可返回了;而非零表示有异常(不同异常拥有不同异常码),然后ReadString,序列化数据int之后字符串String大小的数据被读取,表示异常的message信息,包含int和String两个参数的readException被调用,用于new并抛出一个java异常。

writeException:

public final void writeException(Exception e) {
    int code = 0;
    if (e instanceof SecurityException) {
        code = EX_SECURITY;
    } else if (e instanceof BadParcelableException) {
        code = EX_BAD_PARCELABLE;
    } else if (e instanceof IllegalArgumentException) {
        code = EX_ILLEGAL_ARGUMENT;
    } else if (e instanceof NullPointerException) {
        code = EX_NULL_POINTER;
    } else if (e instanceof IllegalStateException) {
        code = EX_ILLEGAL_STATE;
    }
    writeInt(code);
    StrictMode.clearGatheredViolations();
    if (code == 0) {
        if (e instanceof RuntimeException) {
            throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
    }
    writeString(e.getMessage());
}

public final void writeNoException() {
    if (StrictMode.hasGatheredViolations()) {
        writeInt(EX_HAS_REPLY_HEADER);
        final int sizePosition = dataPosition();
        writeInt(0);  // total size of fat header, to be filled in later
        StrictMode.writeGatheredViolationsToParcel(this);
        final int payloadPosition = dataPosition();
        setDataPosition(sizePosition);
        writeInt(payloadPosition - sizePosition);  // header size
        setDataPosition(payloadPosition);
    } else {
        writeInt(0);
    }
}
        writeNoException直接writeInt 0 值,即使是写EX_HAS_REPLY_HEADER,在readException时会当做无异常处理。接下来是writeException,通常在catch语句块里调用,用于把catch来的exception写入到序列化数据里,如果写入的exception是可识别的,那么就可成功写入到序列化数据里,否则writeException方法会抛出一个runtime异常。

2.2 getBookList Parcel的包装与解包

服务端包装List<Book>对象返回,writeTypedList(List<Book>):

    public final <T extends Parcelable> void writeTypedList(List<T> val) {
        if (val == null) {
            writeInt(-1);
            return;
        }
        int N = val.size();
        int i=0;
        writeInt(N);
        while (i < N) {
            T item = val.get(i);
            if (item != null) {
                writeInt(1);
                item.writeToParcel(this, 0);
            } else {
                writeInt(0);
            }
            i++;
        }
    }

//Book.java
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(bookId);
        out.writeString(bookName);
    }

        writeTypedList是把List<Book>对象转化成序列化数据,首先如果List<Book>对象为null,则序列化数据里写入int值-1并返回,否则序列化数据写入list长度,然后循环写入Book对象的成员变量,假如list里有空对象存在,写入int值0表示。
客户端解包获取List<Book>对象,createTypedArrayList(Parcelable.Creator<T>):

    public final <T> ArrayList<T> createTypedArrayList(Parcelable.Creator<T> c) {
        int N = readInt();
        if (N < 0) {
            return null;
        }
        ArrayList<T> l = new ArrayList<T>(N);
        while (N > 0) {
            if (readInt() != 0) {
                l.add(c.createFromParcel(this));
            } else {
                l.add(null);
            }
            N--;
        }
        return l;
    }

//Book.java
    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    private Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }


        createTypedArrayList是把序列化数据转换成List<Book>对象,首先读取出序列化数据开始存的list长度int信息,然后用于构建一个ArrayList对象,之后就是循环读序列化数据然后填充到L该ArrayList对象中,如果读到0,add一个空对象,最后改ArrayList对象作为返回值返回。

        值得注意的是,Book的writeToParcel和构造函数Book(Parcel),里面字段write打包的顺序要和read解包顺序要对应上,否则会得到一堆错误的数据。

        addBook的流程与上类似,不在说明。至此demo的应用Binder探索已全部讲完。


三、Binder扩展

        上面demo通过bindService,让客户端Activity进程能与服务端BookManagerService进程能相互通信,然而这种方式与在客户端activity里调用getSystemService获取远程服务并与其交互的方式有什么区别呢?参照网络上的资源加上自己的理解,在此做个记录。

        两者通信原理都是Binder机制,getSystemService方式的服务在一开始便向serviceManager注册的binder实体和名字(列表保存着各个服务的BinderProxy及其包装类),这样客户端便获得该服务的Binderproxy对象,从而进行交互,这种Binder被称为实名Binder;相对应的bindService方式,并没有向serviceManager发起注册和请求,这种Binder称为匿名Binder,匿名Binder为通信双方建立一条私密通道,只要服务端没有把自己的binder对象发给别的进程,别的进程就无法通过穷举或猜测等方式获得该Binder的引用,向该服务端发送请求,然而这种方式,客户端是如何获取服务端的匿名binder的呢?

        原来,服务端可以通过已经建立好的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。什么意思呢?我们知道A进程只要拿到B进程的BinderProxy对象,便可与进程B通信,而BinderProxy的获取方式有两种,前面提到过

  1. 客户端通过查询ServiceManager获取,一开始要注册好,也就是实名Binder方式。
  2. 客户端和服务端都连接到第三方的实名binder通信进程上,相互交换各自的Binder,也就是匿名Binder方式。

        举个例子,客户端Activity调用bindService,通过实名ActivityManagerService的Binder连接到了AMS,而在新进程里创建的BookManagerSevice服务端也同样要也连接到了AMS上,当bindService执行到服务端,BookManagerService会返回自己的Binder对象,该Binder是没有在ServiceManager注册过得,然后通过第三方AMS实名Binder传输,最终作为BinderProxy返回给客户端Activity,这个传送匿名Binder是通过Parcel的writeStrongBinder/readStrongBinder。

bindService的具体流程图,来自于网络


Binder在用户空间和内核空间的一次拷贝完成一次交互,来自于网络




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值