(点击
上方公众号
,可快速关注)
来源:伯乐在线专栏作者 - 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