前言:本文是跟随书本Android开发艺术探索的学习总结,虽然说自己也看了下源码,但是还停留在Binder运作的表层,并不设计Binder深处的运行细节。简单的说,只到AIDL这一层,并不深入。其实和网上大多数文章都重复,所以如过你想看深层次的,就可以右上角啦。
第一部分:介绍Binder
Binder是Android我们提供的跨进程通信方式。
Binder主要涉及4大模块,分别是Binder Client、Binder Server、ServerManager和Binder Driver。其中Binder Driver位于内核空间,而其余三者位于用户空间。
用网络访问来类比的话,Binder Client 类似于客户端,Binder Server类似于于服务端,ServerManager类似于DNS服务器,而Binder Driver类似于路由器,它们的关系如图:
Binder Client 和 Binder Server 之间的跨进程通信统一通过Binder Driver转发。
具体流程是:Server在生成一个Binder实体的同时会为其绑定一个名字并将这个名字封装成一个数据包交 Driver,如果该Binder是新的,那 Driver就会为其在内核空间中创建相应的Binder实体节点和一个对该节点的引用,并将引用传递给ServerManger。而ServerManager就会把该Binder的引用和名字插入数据表中,就跟DNS中存储域名到IP地址的映射原理类似。此时我们的Client就可以通过ServerManger来获取Binder的引用了。
底层的Binder Driver和ServerManger部分逻辑已经由Android帮我们封装好,所以我们只需要自己手动生成Binder Server和Binder Client就好了。而对于Binder Server服务端的生成,Android也提供了一个简单的方式,就是AIDL。
第二部分:AIDL解析
先看看怎么使用
使用AIDL的流程可以分为服务端和客户端两个方面。
服务端:
- 首先用AS提供的方法创建一个IBookManager.aidl文件,
package io.github.hoooopa.androidart.book; //包名
import io.github.hoooopa.androidart.book.Book; //Book.java的路径导入
记得还需要在app的build.gradle中添加sorceset
interface IBookManager {
注意一下Book是自定义数据类,所以还需要定义一个Book.aidl文件
List getBookList();
void addBook(in Book book);//非直接支持的类型需要 in、out、inout参数哦
}
- 然后MakeProject后拿到AS为我们生成的最重要的文件IBookManager.java。
//IBookManager.java
//把里头N多东西先删除了就会发现这个结构很简单,
这个IBookManager继承于IInterface,IIterface接口里只有一个方法:IBinder asBinder();
public interface IBookManager extends IInterface{
里面有个Stub类很重要,继承IBookManager接口 ,
public static abstract class Stub extends Binder implements IBookManager {
而Stub 里还有个内部类 Proxy也很重要,也继承IBookManager接口。
private static class Proxy implements IBookManager {
}
}
- 然后再定义一个服务端类BookBinder,注意继承于上面的Stub,如下:
//BookBinder.java
public class BookBinder extends IBookManager.Stub {
@Override
public List getBookList() throws RemoteException {
return null; 在这里执行我们服务端的具体操作
}
@Override
public void addBook(Book book) throws RemoteException {
这里也是呦
}
}
- 然后定义一个process属性的运行在独立进程中的Service,用于和客户端进行联络交互。如下:
//AIDLService.java
public class AIDLService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new BookBinder(); 这个BookBinder就在上面↑,继承自IBookManager.Stub
}
}
所以服务端最主要的就是3个文件,第一个是用于和客户端联络的Service,第二个是执行方法的服务端,第三个就是执行通信的IBookManager——该文件也可以不用AS生成,可以自己手撕呦
客户端:
客户端就很简单了,绑定服务,然后获取Binder,
//Activity.java中
private IBookManager IBookBinder; //IbookManager是系统生成的那个
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
IBookBinder = IBookManager.Stub.asInterface(iBinder);这个操作和平时绑定service的操作很类似嘛
}
@Override public void onServiceDisconnected(ComponentName componentName) {
}
};
然后在我们的Activity的onCreate中绑定服务端中的Service
bindService(new Intent("io.github.hoooopa.androidart.chapter2.AIDLService."),conn,BIND_AUTO_CREATE);
然后就是调用方法就好了
try {
IBookBinder.addBook(new Book(1,"Android"));
} catch (RemoteException e) { 调用方法一定要加try/catch
e.printStackTrace();
}
客户端走3步,第一步new一个ServiceConnection(),获取到IbookManager对象。第二步和远程Service建立绑定,第三步就是调用接口方法就好了。这里注意两点就是调用接口要加try/catch并且这个接口可能是个耗时方法。
所以我们看到不管是定义AIDL文件还是Service或者客户端的使用,都不复杂。真正复杂的东西在系统为我们生成的那个Stub及其Proxy内部类中。所以接下来瞅瞅这个内部类是咋工作的。
Stub类与Proxy解析
从外向内看这个IBookManager,如下:
public interface IBookMangaer extends IInterface {
public List< Book> getBookList() throws RemoteException;
public void addBook(Book book) throws RemoteException;
public static abstract class Stub extends Binder implements IBookManager {}
}
把很多影响阅读的代码删掉了以后,很清楚就能发现,这就是一个接口继承了另一个接口,然后内部还有个抽象静态类。另外的getBookList()和addBook()不正是之前定义的接口方法么。
所有可以在Binder中传输的接口都需要继承Interface这个接口,Interface接口定义如下:
就一个方法,返回一个Ibinder类型的对象
public interface IInterface {
IBinder asBinder();
}
所以IBookManger这个接口是不是异常简单。本身2个方法,然后继承自IInterface,总共3个方法
所以我们还是继续往里看Stub这个类。因为之前介绍使用流程的时候也说了我们的服务端的执行类BookBinder extends IBookManager.Stub,所以Stub类必然是重头戏。
详细的解释看下面的代码里吧
//IBookManager.java中
注:(Stub是abstract的)由于Stub继承了IBookManager,而IBookManager继承自Iinterface,
所以该类或其子类需要实现把 getBookList,addbook,asBinder()三个方法。
由于Stub实现了asBinder,所以继承Stub的子类需要实现getBookList和addBook。__应该是这样的吧-。-
static abstract class Stub extends Binder implements IBookManager { //这个Binder类,继承自IBinder。
下面的这三个static final是用来标志的
private static final String DESCRIPTOR = "io.github.hoooopa.androidart.book.IBookManager"; 这个是Binder的唯一标志
static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);这两个整型用于标志get和add两个方法
构造方法,传入的是IInterface对象和上面的标志名。
这个标志名很重要
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
//下面这个方法还记么,在我们客户端获取Binder实体的地方用到了。IBookManger.Stub.asInterface(service)
1. 还记得这个obj是怎么来的么。
ServiceConnection中获取的,也就是说要了解这个obj的本质,就得去看ServiceConnection的源码。
不过可以猜测一下,bindService是绑定服务,而绑定的Service服务中的onBinder()方法返回的是
我们自定义的BandkBinder对象。所以这个obj应该就是那个BankBinder对象。当然还需要转换一下。
2. 另外这个转换过程是用进程来区分的,具体的操作在obj.queryLocalInterface中实现。
如果BookBinder是存在于本地进程(即Service没有开启process),那么就返回本地的那个
如果不是,就返回下面的Stub.proxy对象。(这里有一个代理模式)
public static IBookManager asInterface(IBinder obj) {
if ((obj==null)) {//如果
return null; //就
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof IBookManager))) { //如果
return ((IBookManager)iin); //就
}
return new IBookManager.Stub.Proxy(obj);//否则,就
}
@Override public IBinder asBinder() {
return this;
}
//下面这个方法是重写父类的,onTransact在父类中的被调用逻辑目前我看不大懂,
反正这个方法很重要就是了。
注意:这个方法是运行在服务端的Binder线程池中的。
在讲AIDL的使用的地方,我们知道有一个类是专门用来执行服务端的操作,那里是对数据的加工过程。
而数据传输的过程都在Binder.java内部中实现了,
下边这个方法的工作应该就是个如何写入和读取数据的过程吧。
对了这里如果返回false的话就不给链接,所以可以在这做权限认证
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: 这个标志很熟吧,就上面定义的标志,用来区分调用啥方法
{
data.enforceInterface(DESCRIPTOR);
List _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: 这个也在上面定义了
{
data.enforceInterface(DESCRIPTOR);
Book _arg0;
if ((0!=data.readInt())) {
_arg0 = Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
内部类,出现上面的asInterface()方法中。表示:如果是远程的Binder,就让我来做
private static class Proxy implements IBookManager {}
}
看完了这些代码,其实就知道了Stub中的逻辑并不复杂:
- 构造器中向父类传入自己的实例和名字。
- 然后提供一个接口给客户端来获取自己这个实例(具体的底层操作在父类中实现)
- 规定数据的写入和读取方法(数据的传输过程又是被封装在父类中)
所以可以说大多数的工作都被Binder类做了,而Binder类中的重要工作也是交给了Binder driver去中的。
还有一个内部类Proxy再讲一讲。在上面的代码中我们看到,这个类是在跨进程中才会被用到。作用的讲解也放在代码中哈。如下:
private static class Proxy implements IBookManager {
mRemote就是在Stub类中传入的那个obj,就是位于远程的BankBinder
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override public IBinder asBinder()
{
return mRemote;
}
public String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
注这个方法是运行与客户端的。在内部使用mRemote.transact来调用服务端的方法
@Override
public List< Book> getBookList() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
List _result;
try {
_data.writeInterfaceToken(DESCRIPTOR); //Token
//调用transact方法来发起RPC(远程过程调用)请求,同时挂起当前线程。然后服务端的onTransact方法会被调用
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
//直到RPC过程返回后,当前线程继续执行,并从_reply中取出返回结果;
_reply.readException();
_result = _reply.createTypedArrayList(io.github.hoooopa.androidart.book.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
//最后返回_reply中的数据
return _result;
}
这方法和上面的方法一样,调用远程服务端的tracnsact方法。只不过没有返回数据
@Override public void addBook(Book book) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
就Proxy而言,它是运行在客户端中的,最后调用的mRemote.transact()方法的内部操作是位于服务端中的,具体的操作又最后会到底层中去。
所以虽然一直说Stub这个是重头戏,但是其实这些类个文件其实也并没有做多少事情,Binder的真正精髓还是需要在更深处,就在文章最开始的那个图里的交互处体现出来啊。
AIDL就说到这——话说讲到最后都迷糊了:AIDL到底是啥,就是那个AIDL以及系统根据AIDL生成的那个文件吗?-。-
Binder底层的话,见下面的这个链接吧。
https://blog.csdn.net/zjd934784273/article/details/67068329
第三部分:Binder连接池
《Android开发艺术探索》中还提到了Binder连接池。用来解决N个AIDL就要生成N个配套Service这种尴尬的局面。
我们新建一个AIDL文件IBinderPool.aidl里面只有一个接口
interface IBinderPool{
IBinder queryBinder(int binderCode);
}
并MakeProject一下,然后用一个IBinderPoolImpl继承IBinderPool.Stub。
//BinderPoolImpl.java
public static class BinderPoolImpl extends IBinderPool.Stub{
public BinderPoolImpl{
super();//这个super会调用父类的构造器里的那个 this.attachInterface(this, DESCRIPTOR);方法,
//传入的是IBinderPool.Stub自身
}
@Override
public IBinder queryBinder(int binderCode) throws RemoteException{
IBinder binder = null;
Switch(binderCode){ //根据binderCode来返回对应的IBinder
case xxxxx : binder = new SercurityImpl();//这个Impl熟悉不和IBinderPoolImpl一样都是继承自各自的xxxx.Stub。
…
…
default:break;return null;
}
}
这样我们就清楚了当客户端调用IBinderPoolImpl的时候就可以调用 queryBinder(int binderCode)方法通过binderCode来返回特定的IBinder(Binder类implements IBinder接口)。
然后我们建立的联络用的Service的onBind()方法里返回上面这个new IBinderPoolImpl 就好了。而具体的操作
这样就能返回对应的Binder啦。然后客户端就可以拿到各自需要的Binder去玩耍啦。
当然如果要用到项目里还需要做很多操作,比如说可以绑定死亡代理,考虑并发什么的。
具体代码可见《Adnroid艺术探索》P112。
第四部分:注意事项
使用AIDL的时候坑比较多。
-
AIDL并不支持所有的数据类型。
AIDL文件中支持的数据类型有:基本类型,String和CharSequence,ArrayList和Map并且内部元素都必须能够被AIDL支持,实现了Parcelable接口的对象,AIDL接口本身。Serializable和Parcelable的异同:Serializable和Parcelable都能实现序列化,Serializable是Java提供的序列化接口,使用简单,但是开销比较大,因为序列化和反序列化过程都需要大量I/O操作。Parcelable是Android中的序列化方式,使用起来比较麻烦,但是效率很高。另外要注意的是Parcelable主要用于内存序列化上,如果要把对象序列化到存储设备或者序列化之后进行网络传输还是建议使用Serializable。
如果AIDL中使用了自定义Parcelable对象,那么还需要新建要给同名AIDL文件,并且该对象需要显示的import。
AIDL中除了基本数据类型,其他类型参数必须标上in、out、inout
客户端最好不要在UI线程调用服务端的方法,因为服务端的方法可能需要执行很久。但是服务端的方法本身是运行在线程池里的,所以Binder方法采用同步方式实现即可。
AIDL中使用接口的问题:这个记不太清了。先留着吧
第五部分:其他细节
-
Binder意外死亡的问题:
服务端进程可能意外停止,这时我们需要重新连接服务,有两种方式可以实现:
方式一是使用linkToDeath给Binder设置一个死亡代理,这样当Binder死亡时,我们就会收到通知,也就可以重新发起连接请求恢复连接。
死亡代理设置方式:声明一个DeathRecipient对象,在方法binderDied()中重新解除之前绑定的binder代理并重新绑定远程服务。在客户端绑定远程服务后,调用binder.linkToDeath()传入DeathRecipient对象,设置死亡代理。
方式二在onServiceDisconnected中重连远程服务。
两者的区别在于后者在客户端UI线程中被回调,可以访问UI,而前者不行。 -
Binder连接的权限验证问题:
我们不希望任何人都可以连接我们的远程服务,这个时候就需要加入权限验证功能。
有两种方式可以实现:方法一在onBind中进行权限验证,方法二是在服务端的onTransact方法中进行验证(还记得上面说的:返回false表示链接失败吗~) 文件共享的IPC方式最好不要用SharedPreference,因为系统内存中会有一份SP文件的缓存,所以在多进程模式下对SP的读写操作就会不可靠,如果是高并发的读写,那么SP会有很大的几率丢失数据
其他的进程间通信方式有文件共享、Messager、Socket、Bundle、ContentProvider。
这里面的区别或者给上别人的链接吧。
https://www.sogou.com/link?url=hedJjaC291OB0PrGj_c3jHbZOvsIrdBKsJUM538-RvZw7KPKo-CjB-0PsIg9LKFipYWeeMDqtvQ.
最后感谢《Android开发艺术探索》、《Android源码设计模式解析与实战》和1Offer同学。