配色: 字号:
Android:IPC之AIDL的学习和总结
2016-12-17 | 阅:  转:  |  分享 
  
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);

}

}

}

献花(0)
+1
(本文系thedust79首藏)