专栏名称: 安卓开发精选
伯乐在线旗下账号,分享安卓应用相关内容,包括:安卓应用开发、设计和动态等。
目录
相关文章推荐
开发者全社区  ·  奇女子!钓男朋友翻车了 ·  17 小时前  
鸿洋  ·  Android×AI×鸿蒙生态周刊#2|跨端 ... ·  昨天  
开发者全社区  ·  离谱瓜!约会8个女同事的大厂男 ·  2 天前  
51好读  ›  专栏  ›  安卓开发精选

学习 AIDL,这一篇文章就够了(下)

安卓开发精选  · 公众号  · android  · 2016-09-29 09:21

正文

(点击 上方公众号 ,可快速关注)


来源:伯乐在线专栏作者 - lypeer

链接:http://android.jobbole.com/84588/

点击 → 了解如何加入专栏作者


前言


上一篇博文介绍了关于AIDL是什么,为什么我们需要AIDL,AIDL的语法以及如何使用AIDL等方面的知识,这一篇博文将顺着上一篇的思路往下走,接着介绍关于AIDL的一些更加深入的知识。强烈建议大家在看这篇博文之前先看一下上一篇博文: 学习 AIDL,这一篇文章就够了(上)


注:文中所有代码均源自上一篇博文中的例子。

另:在看这篇博文之前,建议先将上一篇博文中的代码下载下来或者敲一遍,然后确定可以正常运行后再接着看。因为文中有大量对于具体代码的分析以及相关代码片段之间的跳转,如果你手头没有一份完整代码的话很容易看得一头雾水,最后浪费了你的时间也浪费了这篇博文。


正文


1,源码分析:AIDL文件是怎么工作的?


进行到上一篇文章的最后一步,我们已经学会了AIDL的全部用法,接下来让我们透过现象看本质,研究一下究竟AIDL是如何帮助我们进行跨进程通信的。


我们在上一篇提到过,在写完AIDL文件后,编译器会帮我们自动生成一个同名的 .java 文件——也许大家已经发现了,在我们实际编写客户端和服务端代码的过程中,真正协助我们工作的其实是这个文件,而 .aidl 文件从头到尾都没有出现过。这样一来我们就很容易产生一个疑问: 难道我们写AIDL文件的目的其实就是为了生成这个文件么? 答案是肯定的。 事实上,就算我们不写AIDL文件,直接按照它生成的 .java 文件那样写一个 .java 文件出来,在服务端和客户端中也可以照常使用这个 .java 类来进行跨进程通信。 所以说AIDL语言只是在简化我们写这个 .java 文件的工作而已,而要研究AIDL是如何帮助我们进行跨进程通信的,其实就是研究这个生成的 .java 文件是如何工作的。


1.1,这个文件在哪儿?


要研究它,首先我们就需要找到它,那么它在哪儿呢?在这里:



它的完整路径是:app->build->generated->source->aidl->debug->com->lypeer->ipcclient->BookManager.java(其中 com.lypeer.ipcclient是包名,相对应的AIDL文件为 BookManager.aidl )。在Android Studio里面目录组织方式由默认的 Android 改为 Project 就可以直接按照文件夹结构访问到它。


1.2,从应用看原理


和我一贯的分析方式一样,我们先不去看那些冗杂的源码,先从它在实际中的应用着手,辅以思考分析,试图寻找突破点。首先从服务端开始,刨去其他与此无关的东西,从宏观上我们看看它干了些啥:


private final BookManager . Stub mBookManager = new BookManager . Stub () {

@Override

public List getBooks () throws RemoteException {

// getBooks()方法的具体实现

}

@Override

public void addBook ( Book book ) throws RemoteException {

// addBook()方法的具体实现

}

};

public IBinder onBind ( Intent intent ) {

return mBookManager ;

}


可以看到首先我们是对 BookManager.Stub 里面的抽象方法进行了重写——实际上,这些抽象方法正是我们在 AIDL 文件里面定义的那些。 也就是说,我们在这里为我们之前定义的方法提供了具体实现。 接着,在 onBind() 方法里我们将这个 BookManager.Stub 作为返回值传了过去。


接着看看客户端:


private BookManager mBookManager = null ;

private ServiceConnection mServiceConnection = new ServiceConnection () {

@Override

public void onServiceConnected ( ComponentName name , IBinder service )

mBookManager = BookManager . Stub . asInterface ( service );

//省略

}

@Override

public void onServiceDisconnected ( ComponentName name ) {

//省略

}

};

public void addBook ( View view ) {

//省略

mBookManager . addBook ( book );

}


简单的来说,客户端就做了这些事:获取 BookManager 对象,然后调用它里面的方法。


现在结合服务端与客户端做的事情,好好思考一下,我们会发现这样一个怪事情:它们配合的如此紧密,以至于它们之间的交互竟像是同一个进程中的两个类那么自然!大家可以回想下平时项目里的接口回调,基本流程与此一般无二。明明是在两个线程里面,数据不能直接互通,何以他们能交流的如此愉快呢?答案在 BookManager.java 里。


1.3,从客户端开始


一点开 BookManager.java ,我发现的第一件事是:BookManager 是一个接口类! 一看到它是个接口,我就知道,突破口有了。 为什么呢?接口意味着什么?方法都没有具体实现。但是明明在客户端里面我们调用了 mBookManager.addBook() !那么就说明我们在客户端里面用到的 BookManager 绝不仅仅是 BookManager,而是它的一个实现类!那么我们就可以从这个实现类入手,看看在我们的客户端调用 addBook() 方法的时候,究竟 BookManager 在背后帮我们完成了哪些操作。首先看下客户端的 BookManager 对象是怎么来的:


public void onServiceConnected ( ComponentName name , IBinder service )

mBookManager = BookManager . Stub . asInterface ( service );

}


在这里我首先注意到的是方法的传参:IBinder service 。这是个什么东西呢?通过调试,我们可以发现, 这是个 BinderProxy 对象。 但随后我们会惊讶的发现:Java中并没有这个类!似乎研究就此陷入了僵局——其实不然。在这里我们没办法进一步的探究下去,那我们就先把这个问题存疑,从后面它的一些应用来推测关于它的更多的东西。


接下来顺藤摸瓜去看下这个 BookManager.Stub.asInterface() 是怎么回事:


public static com . lypeer . ipcclient . BookManager asInterface ( android . os . IBinder obj ) {

//验空

if (( obj == null )) {

return null ;

}

//DESCRIPTOR = "com.lypeer.ipcclient.BookManager",搜索本地是否已經

//有可用的对象了,如果有就将其返回

android . os . IInterface iin = obj . queryLocalInterface ( DESCRIPTOR );

if ((( iin != null ) && ( iin instanceof com . lypeer . ipcclient . BookManager ))) {

return (( com . lypeer . ipcclient . BookManager ) iin );

}

//如果本地没有的话就新建一个返回

return new com . lypeer . ipcclient . BookManager . Stub . Proxy ( obj );

}


方法里首先进行了验空,这个很正常。第二步操作是调用了 queryLocalInterface() 方法,这个方法是 IBinder 接口里面的一个方法,而这里传进来的 IBinder 对象就是上文我们提到过的那个 service 对象。由于对 service 对象我们还没有一个很清晰的认识,这里也没法深究这个 queryLocalInterface()方法:它是 IBinder 接口里面的一个方法,那么显然,具体实现是在 service 的里面的,我们无从窥探。但是望文生义我们也能体会到它的作用,这里就姑且这么理解吧。第三步是创建了一个对象返回——很显然,这就是我们的目标,那个实现了 BookManager 接口的实现类。果断去看这个 BookManager.Stub.Proxy 类:


private static class Proxy implements com . lypeer . ipcclient . BookManager {

private android . os . IBinder mRemote ;

Proxy ( android . os . IBinder remote ) {

//此处的 remote 正是前面我们提到的 IBinder service

mRemote = remote ;

}

@Override

public java . util . List com . lypeer . ipcclient . Book > getBooks () throws android . os . RemoteException {

//省略

}

@Override

public void addBook ( com . lypeer . ipcclient . Book book ) throws android . os . RemoteException {

//省略

}

//省略部分方法

}


看到这里,我们几乎可以确定:Proxy 类确实是我们的目标,客户端最终通过这个类与服务端进行通信。


那么接下来看看 getBooks() 方法里面具体做了什么:


@Override

public java . util . List com . lypeer . ipcclient . Book > getBooks () throws android . os . RemoteException {

//很容易可以分析出来,_data用来存储流向服务端的数据流,

//_reply用来存储服务端流回客户端的数据流

android . os . Parcel _data = android . os . Parcel . obtain ();

android . os . Parcel _reply = android . os . Parcel . obtain ();

java . util . List com . lypeer . ipcclient . Book > _result ;

try {

_data . writeInterfaceToken ( DESCRIPTOR );

//调用 transact() 方法将方法id和两个 Parcel 容器传过去

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

_reply . readException ();

//从_reply中取出服务端执行方法的结果

_result = _reply . createTypedArrayList ( com . lypeer . ipcclient . Book . CREATOR );

} finally {

_reply . recycle ();

_data . recycle ();

}

//将结果返回

return _result ;

}


在这段代码里有几个需要说明的地方,不然容易看得云里雾里的:


  • 关于 _data 与 _reply 对象:一般来说,我们会将方法的传参的数据存入_data 中,而将方法的返回值的数据存入 _reply 中——在没涉及定向 tag 的情况下。如果涉及了定向 tag ,情况将会变得稍微复杂些,具体是怎么回事请参见这篇博文:你真的理解AIDL中的in,out,inout么?

  • 关于 Parcel :简单的来说,Parcel 是一个用来存放和读取数据的容器。我们可以用它来进行客户端和服务端之间的数据传输,当然,它能传输的只能是可序列化的数据。具体 Parcel 的使用方法和相关原理可以参见这篇文章:Android中Parcel的分析以及使用

  • 关于 transact() 方法:这是客户端和服务端通信的核心方法。调用这个方法之后,客户端将会挂起当前线程,等候服务端执行完相关任务后通知并接收返回的 _reply 数据流。关于这个方法的传参,这里有两点需要说明的地方:

  • 方法 ID :transact() 方法的第一个参数是一个方法 ID ,这个是客户端与服务端约定好的给方法的编码,彼此一一对应。在AIDL文件转化为 .java 文件的时候,系统将会自动给AIDL文件里面的每一个方法自动分配一个方法 ID。

  • 第四个参数:transact() 方法的第四个参数是一个 int 值,它的作用是设置进行 IPC 的模式,为 0 表示数据可以双向流通,即 _reply 流可以正常的携带数据回来,如果为 1 的话那么数据将只能单向流通,从服务端回来的 _reply 流将不携带任何数据。

    注:AIDL生成的 .java 文件的这个参数均为 0。


上面的这些如果要去一步步探究出结果的话也不是不可以,但是那将会涉及到 Binder 机制里比较底层的东西,一点点说完势必会将文章的重心带偏,那样就不好了——所以我就直接以上帝视角把结论给出来了。


另外的那个 addBook() 方法我就不去分析了,殊途同归,只是由于它涉及到了定向 tag ,所以有那么一点点的不一样,有兴趣的读者可以自己去试着阅读一下。接下来我总结一下在 Proxy 类的方法里面一般的工作流程:


  • 1,生成 _data 和 _reply 数据流,并向 _data 中存入客户端的数据。

  • 2,通过 transact() 方法将它们传递给服务端,并请求服务端调用指定方法。

  • 3,接收 _reply 数据流,并从中取出服务端传回来的数据。


纵观客户端的所有行为,我们不难发现,其实一开始我们不能理解的那个 IBinder service 恰恰是客户端与服务端通信的灵魂人物——正是通过用它调用的 transact() 方法,我们得以将客户端的数据和请求发送到服务端去。从这个角度来看,这个 service 就像是服务端在客户端的代理一样——你想要找服务端?要传数据过去?行啊!你来找我,我给你把数据送过去——而 BookManager.java 中的那个 Proxy 类,就只能沦为二级代理了,我们在外部通过它来调动 service 对象。


至此,客户端在 IPC 中进行的工作已经分析完了,接下来我们看一下服务端。


1.4,接着看服务端


前面说了客户端通过调用 transact() 方法将数据和请求发送过去,那么理所当然的,服务端应当有一个方法来接收这些传过来的东西:在 BookManager.java 里面我们可以很轻易的找到一个叫做 onTransact() 的方法——看这名字就知道,多半和它脱不了关系,再一看它的传参(int code, android.os.Parcel data, android.os.Parcel reply, int flags) ——和 transact() 方法的传参是一样的!如果说他们没有什么 py 交易把我眼珠子挖出来当泡踩!下面来看看它是怎么做的:


@Override

public boolean onTransact ( int code , android . os







请到「今天看啥」查看全文