Android:IPC之AIDL的学习和总结
为了使得一个程序能够在同一时间里处理许多用户的要求。即使用户可能发出一个要求,也肯能导致一个操作系统中多个进程的运行(PS:听音乐,看地图)。而且多个进程间需要相互交换、传递信息,IPC方法提供了这种可能。IPC方法包括管道(PIPE)、消息排队、旗语、共用内存以及套接字(Socket)。
Android中的IPC方式有Bundle、文件共享、Messager、AIDL、ContentProvider和Socket。
这次我们学习的是Android中的AIDL。
概述
AIDL(Android接口描述语言)是一个IDL语言,它可以生成一段代码,可以是一个在Android设备上运行的两个进程使用内部通信进程进行交互。在Android上,一个进程通常无法访问另一个进程的内存。所以说,如果你想在一个进程中(例如在一个Activity中)访问另一个进程中(例如service)某个对象的方法,你就可以使用AIDL来生成这样的代码来伪装传递各种参数。
Callsmadefromthelocalprocessareexecutedinthesamethreadthatismakingthecall.IfthisisyourmainUIthread,thatthreadcontinuestoexecuteintheAIDLinterface.Ifitisanotherthread,thatistheonethatexecutesyourcodeintheservice.Thus,ifonlylocalthreadsareaccessingtheservice,youcancompletelycontrolwhichthreadsareexecutinginit(butifthatisthecase,thenyoushouldn’tbeusingAIDLatall,butshouldinsteadcreatetheinterfacebyimplementingaBinder).
如果调用发生在本地进程的同一线程中。如果是UI线程,那么AIDL接口调用继续在该线程。如果是其他线程,那么服务代码也在该线程执行。因此如果使用本地线程访问服务,那么服务调用线程是完全可以控制的。(但是这种情况就没有必要使用AIDL,可以用实现一个继承Binder的接口)。
-
Callsfromaremoteprocessaredispatchedfromathreadpooltheplatformmaintainsinsideofyourownprocess.Youmustbepreparedforincomingcallsfromunknownthreads,withmultiplecallshappeningatthesametime.Inotherwords,animplementationofanAIDLinterfacemustbecompletelythread-safe.
远程服务调用会在一个线程池中维护着client端的调用,client必须准备好接受未知线程即将返回的结果,并且多个调用可能同时发生。换而言之,一个AIDL接口的实现必须是完全线程安全的。
-
Theonewaykeywordmodifiesthebehaviorofremotecalls.Whenused,aremotecalldoesnotblock;itsimplysendsthetransactiondataandimmediatelyreturns.TheimplementationoftheinterfaceeventuallyreceivesthisasaregularcallfromtheBinderthreadpoolasanormalremotecall.Ifonewayisusedwithalocalcall,thereisnoimpactandthecallisstillsynchronous.
“oneway”修饰词是来形容远端服务的调用。当使用它的时候,远端服务不会阻塞,它只是发送数据并立即返回。接口的实现最终会收到一个来自远端Binder线程池的正确的回调。如果”oneway”被使用在本地调用,那么对调用没有任何影响,调用的方式还是同步的。
//使用oneway关键字的AIDL接口的所有方法调用都非阻塞式调用,并且方法函数的返回值为void类型,否则会有编译异常提示:onewaymethod''xxx''cannotreturnavalue
语法
AIDL它和Java基本上类似,只是有一些细微的差别(PS:可能Google为了方便Android程序猿使用)。AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。重要的是必须导入所有非内置类型,哪怕是这些类型是在与接口相同的包中。
下边说说AIDL的一些特点:
通常引引用方式传递的其他AIDL生成的接口,必须要import语句声明。
Java编程语言的主要类型(int,boolean等)—不需要import语句。
在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件中支持哪些数据类型呢?
如下所示:
1、基本数据类型(int,long,char,boolean,float,double,byte,short八种基本类型);
2、String和CharSequence;
3、List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
4、Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;
5、Parcelable:所有实现了Parcelable接口的对象;
6、AIDL:所有的AIDL接口本身也可以在AIDL文件中使用;
以上6中数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内。
需要注意的地方:
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout;
(PS:假若传递一个Book对象且没有加指向tag时,则会抛出aidl.exeE49285836type_namespace.cpp:130]''Book''canbeanouttype,soyoumustdeclareitasin,outorinout.异常)
-in表示输入型参数(Server可以获取到Client传递过去的数据,但是不能对Client端的数据进行修改)
-out表示输出型参数(Server获取不到Client传递过去的数据,但是能对Client端的数据进行修改)
-inout表示输入输出型参数(Server可以获取到Client传递过去的数据,但是能对Client端的数据进行修改)。
更多tag相关的内容:AIDL源码解析in、out和inout
使用AIDL实现IPC
实现步骤(官网AIDL样例)
//IRemoteService.aidl
packagecom.example.android;
//Declareanynon-defaulttypesherewithimportstatements
/Exampleserviceinterface/
interfaceIRemoteService{
/RequesttheprocessIDofthisservice,todoevilthingswithit./
intgetPid();
/Demonstratessomebasictypesthatyoucanuseasparameters
andreturnvaluesinAIDL.
/
voidbasicTypes(intanInt,longaLong,booleanaBoolean,floataFloat,
doubleaDouble,StringaString);
这是官网创建的一个简单的AIDL文件。
这次我们自己声明一个包含非默认支持类型的AIDL文件。
AIDL要跨进程通信,其所携带的数据也需要跨进程传输。所以我们首先需要自定自己想要传输的数据类必须其必须实现Parcelable接口从而可以被序列化。
为什么需要序列化呢,为什么不适用Serializable,不知道的同学可以看下这篇文章:Serializable和Parcelable的再次回忆
所以我们先创建需要传输的数据所对应的aidl文件,然后再相同目录下创建对应的Java类文件。这里可能有些同学会疑惑,不是直接创建Java类么。AIDL文件有两种类型,一种是我们上边定义的接口,而另外一种就是非常规类型的数据对象文件。即:如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。详细的使用我们看下边例子:
创建一个Book.aidl文件
在AndroidStudio的项目中先创建对应的aidl包,然后右击选择创建aidl文件,soeasy。
//Book.aidl
packagecom.tzx.aidldemo.aidl;
//Declareanynon-defaulttypesherewithimportstatements
//所有注释掉的内容都是AndroidStudio帮你写的,但是我们不需要。
//我们创建的是aidl数据对象,所以我们只需写出parcelable后面跟对象名。
//parcelabe前的字母‘p’是小写的哦~
parcelableBook;
//interfaceBook{
//
///
//Demonstratessomebasictypesthatyoucanuseasparameters
//andreturnvaluesinAIDL.
///
//voidbasicTypes(intanInt,longaLong,booleanaBoolean,floataFloat,
//doubleaDouble,StringaString);
//}
在Book.aidl的包下创建Book.java类文件
publicclassBookimplementsParcelable{
publicintbookId;
publicStringbookName;
publicBook(){
}
publicBook(intbookId,StringbookName){
this.bookId=bookId;
this.bookName=bookName;
}
//从序列化后的对象中创建原始对象
protectedBook(Parcelin){
bookId=in.readInt();
bookName=in.readString();
}
publicstaticfinalCreatorCREATOR=newCreator(){
//从序列化后的对象中创建原始对象
@Override
publicBookcreateFromParcel(Parcelin){
returnnewBook(in);
}
//指定长度的原始对象数组
@Override
publicBook[]newArray(intsize){
returnnewBook[size];
}
};
//返回当前对象的内容描述。如果含有文件描述符,返回1,否则返回0,几乎所有情况都返回0
@Override
publicintdescribeContents(){
return0;
}
//将当前对象写入序列化结构中,其flags标识有两种(1|0)。
//为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况下都为0.
@Override
publicvoidwriteToParcel(Parceldest,intflags){
dest.writeInt(bookId);
dest.writeString(bookName);
}
@Override
publicStringtoString(){
return"[bookId="+bookId+",bookName=''"+bookName+"'']";
}
在AndroidStudio中如果先创建Java类文件,然后创建AIDL文件则会提示命名重复,但顺序反过来就可以。
创建aidl接口文件IBookManager.aidl
//IBookManager.aidl
packagecom.tzx.aidldemo.aidl;
//通常引用方式传递自定义对象,必须要import语句声明
importcom.tzx.aidldemo.aidl.Book;
interfaceIBookManager{
ListgetBookList();
voidaddBook(inBookbook);
}
这样所有aidl相关的文件就定义完了,我们可以写客户端和服务端了么。然而实际结果表明我们还是无法在客户端或服务费使用aidl类。在这里说一下其实aidl方式只不过是为我们提供模板自动创建aidl对应的Java类文件,只有生成了对应的Java文件之后我们才可以在客户端或服务端使用。Androidstudio中make一下当前的project就会在项目的app/build/source/aidl/包名/debug这个目录下生成对应的aidl类文件(PS:只有aidl接口文件才会生成java类文件)。
make的时候可能提示找不到对应的Book.java文件,我们可以在build.gradle文件中的android{}标签里面添加:
sourceSets{
main{
aidl.srcDirs=[''src/main/java'']
这种情况只适合aidl类文件和对应的java类文件在同一个包下。
好了,现在所有的aidl文件都有了,我们开始写我们的服务交互了~!~!
服务端:
Service服务
/
Createdbytanzhenxing
Date:2016/10/17.
Description:远程服务
/
publicclassBookManagerServiceextendsService{
//支持并发读写
privateCopyOnWriteArrayListmBookList=newCopyOnWriteArrayList<>();
//服务端定义Binder类(IBookManager.Stub)
privateBindermBinder=newIBookManager.Stub(){
@Override
publicListgetBookList()throwsRemoteException{
returnmBookList;
}
@Override
publicvoidaddBook(Bookbook)throwsRemoteException{
mBookList.add(book);
}
};
@Nullable
@Override
publicIBinderonBind(Intentintent){
returnmBinder;
}
并在Manifest文件中声明,将它放在一个新的进程中,这样方便我们演示跨进程通信。
android:process=":server"/>
客户端:
/
Createdbytanzhenxing
Date:2016/10/17.
Description:主界面
/
publicclassMainActivityextendsAppCompatActivityimplementsView.OnClickListener{
privateEditTextbookNameTV;
privateButtonbookAddTV;
privateButtonbookCountTV;
privateTextViewbookInfoTV;
privateIntentbookManagerIntent;
privatebooleanmBound=false;
privateIBookManagerbookManager;
privateServiceConnectionmConnection=newServiceConnection(){
@Override
publicvoidonServiceConnected(ComponentNamename,IBinderservice){
//客户端获取代理对象
bookManager=IBookManager.Stub.asInterface(service);
}
@Override
publicvoidonServiceDisconnected(ComponentNamename){
}
};
@Override
protectedvoidonStart(){
super.onStart();
bookManagerIntent=newIntent(this,BookManagerService.class);
bindService(bookManagerIntent,mConnection,Context.BIND_AUTO_CREATE);
mBound=true;
}
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
privatevoidinitView(){
bookNameTV=(EditText)findViewById(R.id.book_name);
bookAddTV=(Button)findViewById(R.id.book_add);
bookCountTV=(Button)findViewById(R.id.book_count);
bookInfoTV=(TextView)findViewById(R.id.book_info);
}
privatevoidinitListener(){
bookAddTV.setOnClickListener(this);
bookCountTV.setOnClickListener(this);
}
@Override
protectedvoidonStop(){
super.onStop();
if(mBound){
mBound=false;
unbindService(mConnection);
}
}
@Override
publicvoidonClick(Viewv){
switch(v.getId()){
caseR.id.book_add:
addBook();
break;
caseR.id.book_count:
getBookList();
break;
}
}
privatevoidaddBook(){
if(bookManager!=null&&!TextUtils.isEmpty(bookNameTV.getText().toString())){
Bookbook=newBook((int)System.currentTimeMillis(),bookNameTV.getText().toString());
try{
bookManager.addBook(book);
}catch(RemoteExceptione){
e.printStackTrace();
}
}
}
publicvoidgetBookList(){
try{
if(bookManager!=null){
Listlist=bookManager.getBookList();
if(list!=null&&list.size()>0){
StringBuilderbuilder=newStringBuilder();
for(Bookbook:list){
builder.append(book.toString());
builder.append(''\n'');
}
bookInfoTV.setText(builder.toString());
}else{
bookInfoTV.setText("Empty~!");
}
}
}catch(RemoteExceptione){
e.printStackTrace();
运行结果
AIDLDEMO
解析aidl生成的java类
publicinterfaceIBookManagerextendsandroid.os.IInterface{
//根据aidl文件中定义的方法,进行接口声明
publicjava.util.ListgetBookList()
throwsandroid.os.RemoteException;
//根据aidl文件中定义的方法,进行接口声明
publicvoidaddBook(com.tzx.aidldemo.aidl.Bookbook)
throwsandroid.os.RemoteException;
/Local-sideIPCimplementationstubclass./
publicstaticabstractclassStubextendsandroid.os.Binderimplementscom.tzx.aidldemo.aidl.IBookwww.wang027.comManager{
privatestaticfinaljava.lang.StringDESCRIPTOR="com.tzx.aidldemo.aidl.IBookManager";
//定义方法执行code,与客户端同步
staticfinalintTRANSACTION_getBookList=(android.os.IBinder.FIRST_CALL_TRANSACTION+
0);
staticfinalintTRANSACTION_addBook=(android.os.IBinder.FIRST_CALL_TRANSACTION+
1);
/Constructthestubatattachittotheinterface./
publicStub(){
this.attachInterface(this,DESCRIPTOR);
}
/
CastanIBinderobjectintoancom.tzx.aidldemo.aidl.IBookManagerinterface,
generatingaproxyifneeded.
/
publicstaticcom.tzx.aidldemo.aidl.IBookManagerasInterface(
android.os.IBinderobj){
if((obj==null)){
returnnull;
}
android.os.IInterfaceiin=obj.queryLocalInterface(DESCRIPTOR);
if(((iin!=null)&&
(iininstanceofcom.tzx.aidldemo.aidl.IBookManager))){
return((com.tzx.aidldemo.aidl.IBookManager)iin);
}
//生成代理对象
returnnewcom.tzx.aidldemo.aidl.IBookManager.Stub.Proxy(obj);
}
@Override
publicandroid.os.IBinderasBinder(){
returnthis;
}
@Override
publicbooleanonTransact(intcode,android.os.Parceldata,
android.os.Parcelreply,intflags)
throwsandroid.os.RemoteException{
switch(code){
caseINTERFACE_TRANSACTION:{
reply.writeString(DESCRIPTOR);
returntrue;
}
caseTRANSACTION_getBookList:{
data.enforceInterface(DESCRIPTOR);
//调用服务端getBookList()
java.util.List_result=this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
returntrue;
}
caseTRANSACTION_addBook:{
data.enforceInterface(DESCRIPTOR);
com.tzx.aidldemo.aidl.Book_arg0;
if((0!=data.readInt())){
_arg0=com.tzx.aidldemo.aidl.Book.CREATOR.createFromParcel(data);
}else{
_arg0=null;
}
//调用服务端addBook()
this.addBook(_arg0);
reply.writeNoException();
returntrue;
}
}
returnsuper.onTransact(code,data,reply,flags);
}
privatestaticclassProxyimplementscom.tzx.aidldemo.aidl.IBookManager{
privateandroid.os.IBindermRemote;
Proxy(android.os.IBinderremote){
mRemote=remote;
}
@Override
publicandroid.os.IBinderasBinder(){
returnmRemote;
}
publicjava.lang.StringgetInterfaceDescriptor(){
returnDESCRIPTOR;
}
@Override
publicjava.util.ListgetBookList()
throwsandroid.os.RemoteException{
android.os.Parcel_data=android.os.Parcel.obtain();
android.os.Parcel_reply=android.os.Parcel.obtain();
java.util.List_result;
try{
_data.writeInterfaceToken(DESCRIPTOR);
//调用远程服务addBook()
mRemote.transact(Stub.TRANSACTION_getBookList,_data,
_reply,0);
_reply.readException();
_result=_reply.createTypedArrayList(com.tzx.aidldemo.aidl.Book.CREATOR);
}finally{
_reply.recycle();
_data.recycle();
}
return_result;
}
@Override
publicvoidaddBook(com.tzx.aidldemo.aidl.Bookbook)
throwsandroid.os.Remotewww.baiyuewang.netException{
android.os.Parcel_data=android.os.Parcel.obtain();
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);
}
//调用远程服务addBook
mRemote.transact(Stub.TRANSACTION_addBook,_data,_reply,0);
_reply.readException();
}finally{
_reply.recycle();
_data.recycle();
}
IBookManager.java
每个文件结构我们都解析完了,那么aidl到底是怎么实现通信的呢,要让我们自己写一套类似于aidl的那么应该怎么去设计呢?
我们仿aidl画一幅结构图:
AIDL
根据上面这个图,我们就可以写出自己的aidl。
//方法接口
interfaceIBookManager{
finalintCHANGE_MSG=1;
Bookchange(Bookbook);
}
//实现方法的代理类
publicclassProxyimplementsIBookManager{
privatestaticIBindermRemote;
privatestaticProxyasInterface(IBinderservice){
this.mRemote=service;
returnnewProxy();
}
publicBookadd(Bookbook){
Parceldata=Parcel.obtain();
Parcelreply=Parcel.obtain();
try{
data.writeInt(1);
data.writeToParcel(message);
mRemote.transact(CHAT,data,reply,CHANGE_MSG);
reply.readException();
if(0!=reply.readInt()){
returnBook.CREATOR.createFromParcel(_reply);
}
}catch(RemoteExceptione){
e.printStackTrace();
}finally{
data.recycle();
reply.recycle();
}
returnnull;
}
}
//Binder远端实现类
publicclassStubextendsBinderimplementsIBookManager{
@Override
protectedbooleanonTransact(intcode,Parceldata,Parcelreply,intflags)throwsRemoteException{
switch(code){
caseCHANGE_MSG:
if(0!=data.readInt()){
Bookbook=Book.CREATOR.createFromParcel(data)
}
book.name="Server";
reply.writeNoException();
reply.writeInt(1);
book.writeToParcel(reply,android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
returntrue;
default:
returnsuper.onTransact(code,data,reply,flags);
}
}
}
|
|