[翻译]Android接口定义语言 (AIDL)
AIDL(Android接口定义语言)与你可能使用过的其它的IDLs是类似的。它允许你定义客户端与service协调一致的编程接口,以便于彼此之间使用进程间通信(IPC)机制进行通信。在Android上,一个进程通常不能访问另一个进程的内存。可以说,它们需要把它们的对象分解为操作系统能够理解的原始数据类型,并在进程之间按次序排列对象。那种排列对象的代码写起来是很乏味的,因此Android通过AIDL来为你处理这些事情。
注意:只有在你允许来自于不同的应用的客户端访问你的service以实现IPC,并想要在你的service中处理多线程时,才需要使用AIDL。如果你不需要跨不同应用执行并发IPC,你应该通过实现一个Binder来创建你的接口,或者如果你想要执行IPC,但不需要处理多线程,可以使用一个Messenger来实现你的接口。无论哪种,请确保在实现一个AIDL之前,你理解了Bound Services。/
在开始设计你的AIDL接口之前,请意识到对于一个AIDL接口的调用是直接的函数调用(大概指的是阻塞调用,在调用一个IPC方法时,客户端线程会一直等待,直到service端处理完成并返回)。你不应该假设调用所发生的线程。依赖于调用是来自于本进程的一个线程,还是一个远端进程,则会发生不同的事情。特别地:
发起于本进程的调用将在发起调用相同的线程中执行。如果这是你的UI线程,则线程将继续在AIDL接口中执行。如果它是另外一个线程,那它将是执行你的service代码的线程。因此,如果只有本地线程访问service,你可以完全控制在哪个线程中来执行它(但如果是那种情况,则你不应该使用AIDL,而应该通过实现一个Binder来创建接口)。
调用来自于远端进程,并从平台维护的你自己的进程中的一个线程池派发。你必须为调用可能来自于未知线程做好准备,多个调用可能在同一时刻发生(Service是一个对象,而不是一个线程,这里的未知线程大概指的是binder线程,这里的意思大概是说,AIDL service类要按照线程安全类的标准来构造)。换句话说,一个AIDL接口的实现必须完全是线程安全的。
单路行为的远程调用。当使用时,一个远程调用不发生阻塞;它仅仅是发送事务数据并立即返回。接口的实现最终将它作为来自于Binder线程池的一个普通远端调用来接收。如果单路被用于一个本地调用,则没有影响,调用依然是同步的。
定义一个AIDL接口
你必须使用Java编程语言语法在一个.aidl文件中定义你的AIDL接口,然后同时保存在service所在的应用和其他要bind到service的应用的源代码中(在src/目录下)。
当你编译每个包含.aidl文件的应用时,Android SDK工具会基于.aidl文件产生一个IBinder接口,并把它保存在项目的gen/目录下。Service必须适当的实现IBinder接口。客户端应用可以bind到service并调用来自于IBinder的方法来执行IPC。
要使用AIDL来创建一个bounded service,则遵从以下几个步骤:
创建.aidl文件
这个文件通过方法签名定义了编程接口。
实现接口
Android SDK工具基于.aidl文件以Java编程语言产生一个接口。这个接口具有一个名为Stub的内部抽象类,该抽象类扩展了Binder并实现了来自于你的AIDL接口的方法。你必须扩展Stub类并实现那些方法。
将接口暴露给客户端
实现一个Service并覆写onBind()来返回你的Stub类的实现。
注意:在你首次发布你的AIDL接口之后,对它的任何修改都必须要保持向后兼容,以避免破坏使用了你的service的其他应用。即,由于你的.aidl文件必须被复制到其他的应用以使它们能够访问你的service的接口,你必须维护对于原始接口的支持。
1. 创建.aidl文件
AIDL使用了一种简单的语法供你声明一个接口,接口可以有一个或多个方法,每个方法可以接收一些参数并返回值。参数和返回值可以是任何类型,甚至是其他的AIDL-generated接口。
你必须使用Java编程语言构造.aidl文件。每个.aidl文件必须定义一个单独的接口,并且只需要接口声明和方法签名。
默认情况下,AIDL支持下述数据类型:
Java编程语言中的所有原始数据类型(比如int, long, char, boolean, 等等)
String
CharSequence
List
List中的所有元素必须是所列出的被支持的数据类型,或某种其它的AIDL-generated接口,或你已经声明的parcelables。一个List可能被用作一个"generic"类(比如,List<String>)。另外一边实际接收到的具体类总是一个ArrayList,尽管产生的方法使用List接口。
Map
Map中的所有元素必须是所列出的被支持的数据类型,或某种其它的AIDL-generated接口,或你已经声明的parcelables。泛型maps,(比如那些形如Map<String,Integer>的maps)是不被支持的。另外一边实际接收到的具体类总是一个HashMap,尽管产生的方法使用Map接口。
对于没有列出的每种额外的类型你都必须包含一个import声明,即使它们定义在与你的接口相同的package。
当定义你的service接口时,请意识到:
方法可以接收0个或多个参数,并返回一个值或void。
所有的非原始数据类型需要一个指示标记来表明数据的流向。in, out, 或者inout(请参考下面的例子)。
原始数据类型默认是in,其他则不是。
注意:你必须把方向限制在真正需要的方向,因为对于参数的排列是昂贵的。
.aidl文件中的所有代码注释被包含进了产生的IBinder接口中(除了那些在import和package声明前面的注释)。
只支持方法;你不能在AIDL中暴露static域。
这里有一个.aidl文件的例子:
//IRemoteService.aidlpackagecom.example.android;//Declareanynon-defaulttypesherewithimportstatements/**Exampleserviceinterface*/interfaceIRemoteService{/**RequesttheprocessIDofthisservice,todoevilthingswithit.*/intgetPid();/**Demonstratessomebasictypesthatyoucanuseasparameters*andreturnvaluesinAIDL.*/voidbasicTypes(intanInt,longaLong,booleanaBoolean,floataFloat,doubleaDouble,StringaString);}
把你的.aidl文件保存在你的工程的src/目录下,当你编译你的应用时,SDK工具会在你的工程的gen/目录下产生IBinder接口文件。所产生的文件名与.aidl文件名匹配,但扩展名为.java(比如,IRemoteService.aidl产生IRemoteService.java)。
如果你使用Eclipse,增量编译几乎可以立即产生binder类。如果你不使用Eclipse,Ant工具在你下一次编译你的应用时产生binder类——你应该在你写完.aidl文件之后立即通过ant debug (或ant release) 编译你的工程,以使你的代码可以再次链接产生的类。
2. 实现接口
当你编译你的应用时,Android SDK工具产生一个以你的.aidl文件命名的.java接口文件。产生的接口包含一个名为Stub的子类,它是一个它的父接口的抽象实现 (比如,YourInterface.Stub),并声明了来自于.aidl文件的所有方法。
注意:Stub也定义了一些辅助方法,最值得注意的就是asInterface()了,它接收一个IBinder (通常是传递给客户端的onServiceConnected()回调方法的那个)并返回一个stub接口的实例。参考调用一个IPC方法部分,来获取更多关于如何做强制类型转换的信息。
要实现产生自.aidl的接口,则扩展产生的Binder接口 (比如YourInterface.Stub)并实现继承自.aidl文件的方法。
这里有一个称为IRemoteService的接口(由上面的例子IRemoteService.aidl定义)的一个示例实现:
privatefinalIRemoteService.StubmBinder=newIRemoteService.Stub(){publicintgetPid(){returnProcess.myPid();}publicvoidbasicTypes(intanInt,longaLong,booleanaBoolean,floataFloat,doubleaDouble,StringaString){//Doesnothing}};
现在mBinder是一个Stub类的实例(一个Binder),它为service定义了IPC接口。在下一步中,这个实例会被暴露给客户端,以使它们能够与service交互。
当实现你的AIDL接口时有一些规则应该注意:
进入的调用不保证在主线程执行,因此你需要从一开始就考虑多线程,并适当的构建你的service以实现线程安全。
默认情况下,IPC调用是同步的。如果你知道service需要好多毫秒的时间来完成一个请求,则你不应该在activity的主线程中调用,因为它可能挂起应用(Android可能显示一个"Application is Not Responding"对话框)——你通常应该在客户端的另外一个线程中来调用它们。
你throw的exceptions不会被发回调用者。
3. 将接口暴露给客户端
为你的service实现了接口之后,你需要把它暴露给客户端,以使它们可以bind到它。要将你的service暴露出来,则扩展Service并实现onBind()来返回一个你的实现了产生的Stub的类的实例(如前面的讨论)。这里是一个示例service,它将IRemoteService例子接口暴露给客户端。
publicclassRemoteServiceextendsService{@OverridepublicvoidonCreate(){super.onCreate();}@OverridepublicIBinderonBind(Intentintent){//ReturntheinterfacereturnmBinder;}privatefinalIRemoteService.StubmBinder=newIRemoteService.Stub(){publicintgetPid(){returnProcess.myPid();}publicvoidbasicTypes(intanInt,longaLong,booleanaBoolean,floataFloat,doubleaDouble,StringaString){//Doesnothing}};}
现在当一个客户端(比如一个activity)调用bindService()来连接这个service时,客户端的onServiceConnected()回调将接收由service的onBind()方法返回的mBinder实例。
客户端必须也有访问接口类的权限,因此,如果客户端和service在不同的应用中,则客户端的应用必须具有一份.aidl文件的拷贝放在它的src/目录(其产生android.os.Binder接口——提供客户端访问AIDL方法的权限)下。
当客户端在onServiceConnected()回调中接收了IBinder,它必须调用YourServiceInterface.Stub.asInterface(service)来把返回的参数转换为YourServiceInterface类型。比如:
IRemoteServicemIRemoteService;privateServiceConnectionmConnection=newServiceConnection(){//CalledwhentheconnectionwiththeserviceisestablishedpublicvoidonServiceConnected(ComponentNameclassName,IBinderservice){//FollowingtheexampleaboveforanAIDLinterface,//thisgetsaninstanceoftheIRemoteInterface,whichwecanusetocallontheservicemIRemoteService=IRemoteService.Stub.asInterface(service);}//CalledwhentheconnectionwiththeservicedisconnectsunexpectedlypublicvoidonServiceDisconnected(ComponentNameclassName){Log.e(TAG,"Servicehasunexpectedlydisconnected");mIRemoteService=null;}};
更多示例代码,请参考ApiDemos中的RemoteService.java类。
透过IPC传递对象
如果你有一个类,你想要通过IPC接口将它从一个进程发送到另一个进程,那么你可以那样做。然而,你必须确保你的类的代码在IPC通道的另一端也是可以访问的,并且你的类必须支持Parcelable接口。支持Parcelable接口是很重要的,因为它允许Android系统来把对象分解为可以被跨进程处理原始数据类型。
要创建一个支持Parcelable协议的类,你必须做这些事情:
使你的类实现Parcelable接口。
实现writeToParcel,它将对象的当前状态写入一个Parcel。
给你的类添加一个static成员CREATOR,它是一个对象,并实现了Parcelable.Creator接口。
最后,创建一个.aidl文件,它声明了你的parcelable类 (如下面的Rect.aidl文件所显示的那样)。如果你在使用一个定制的编译过程,则不要把.aidl文件添加到你的build。类似于C语言中的头文件,这个.aidl文件不被编译。
AIDL使用代码中它产生的这些方法和成员来排序和解序你的对象。
比如,这里是一个Rect.aidl文件,用于创建一个parcelable的Rect类。
packageandroid.graphics;//DeclareRectsoAIDLcanfinditandknowsthatitimplements//theparcelableprotocol.parcelableRect;
这里是一个Rect类如何实现Parcelable协议的例子。
importandroid.os.Parcel;importandroid.os.Parcelable;publicfinalclassRectimplementsParcelable{publicintleft;publicinttop;publicintright;publicintbottom;publicstaticfinalParcelable.Creator<Rect>CREATOR=newParcelable.Creator<Rect>(){publicRectcreateFromParcel(Parcelin){returnnewRect(in);}publicRect[]newArray(intsize){returnnewRect[size];}};publicRect(){}privateRect(Parcelin){readFromParcel(in);}publicvoidwriteToParcel(Parcelout){out.writeInt(left);out.writeInt(top);out.writeInt(right);out.writeInt(bottom);}publicvoidreadFromParcel(Parcelin){left=in.readInt();top=in.readInt();right=in.readInt();bottom=in.readInt();}}
Rect类中的成员排列组织相当简单。看一下Parcel上的其它方法,来了解你可以写入一个Parcel的其它种类的值。
警告:不要忘记从其它进程接收数据的安全隐患。在这个例子中,Rect从Parcel读取4个数字,但你要确保这些数字在可接受的值的范围以内,而无论调用者要去做什麽。参考Security and Permissions来获取更多关于如何使你的应用更安全并远离病毒的信息。
调用一个IPC方法
这里是一个调用类调用一个AIDL定义的远程接口所要采取的步骤:
在项目的src/目录下包含.aidl文件。
声明一个IBinder接口的实例(基于AIDL而产生)。
实现ServiceConnection。
调用Context.bindService(),传入你的ServiceConnection实现。
在你的onServiceConnected()实现中,你将接收一个IBinder实例(称为service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)来将返回的参数转换为YourInterface类型。
调用你的接口中定义的方法。你应该总是捕捉DeadObjectException异常,当连接断开时会抛出这个异常;这是远程方法将会抛出的唯一的异常。
要断开连接,则通过你的接口的实例调用Context.unbindService()。
关于调用IPC service的一些说明:
对象是跨进程引用计数的。
你可以发送匿名的对象作为方法参数。
更多关于binding到一个service的信息,请阅读Bound Services文档。
这里是一些实例代码,演示了调用一个AIDL-created service,来自于ApiDemos工程中的Remote Service示例。
publicstaticclassBindingextendsActivity{/**Theprimaryinterfacewewillbecallingontheservice.*/IRemoteServicemService=null;/**Anotherinterfaceweuseontheservice.*/ISecondarymSecondaryService=null;ButtonmKillButton;TextViewmCallbackText;privatebooleanmIsBound;/***Standardinitializationofthisactivity.SetuptheUI,thenwait*fortheusertopokeitbeforedoinganything.*/@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.remote_service_binding);//Watchforbuttonclicks.Buttonbutton=(Button)findViewById(R.id.bind);button.setOnClickListener(mBindListener);button=(Button)findViewById(R.id.unbind);button.setOnClickListener(mUnbindListener);mKillButton=(Button)findViewById(R.id.kill);mKillButton.setOnClickListener(mKillListener);mKillButton.setEnabled(false);mCallbackText=(TextView)findViewById(R.id.callback);mCallbackText.setText("Notattached.");}/***Classforinteractingwiththemaininterfaceoftheservice.*/privateServiceConnectionmConnection=newServiceConnection(){publicvoidonServiceConnected(ComponentNameclassName,IBinderservice){//Thisiscalledwhentheconnectionwiththeservicehasbeen//established,givingustheserviceobjectwecanuseto//interactwiththeservice.Wearecommunicatingwithour//servicethroughanIDLinterface,sogetaclient-side//representationofthatfromtherawserviceobject.mService=IRemoteService.Stub.asInterface(service);mKillButton.setEnabled(true);mCallbackText.setText("Attached.");//Wewanttomonitortheserviceforaslongasweare//connectedtoit.try{mService.registerCallback(mCallback);}catch(RemoteExceptione){//Inthiscasetheservicehascrashedbeforewecouldeven//doanythingwithit;wecancountonsoonbeing//disconnected(andthenreconnectedifitcanberestarted)//sothereisnoneedtodoanythinghere.}//Aspartofthesample,telltheuserwhathappened.Toast.makeText(Binding.this,R.string.remote_service_connected,Toast.LENGTH_SHORT).show();}publicvoidonServiceDisconnected(ComponentNameclassName){//Thisiscalledwhentheconnectionwiththeservicehasbeen//unexpectedlydisconnected--thatis,itsprocesscrashed.mService=null;mKillButton.setEnabled(false);mCallbackText.setText("Disconnected.");//Aspartofthesample,telltheuserwhathappened.Toast.makeText(Binding.this,R.string.remote_service_disconnected,Toast.LENGTH_SHORT).show();}};/***Classforinteractingwiththesecondaryinterfaceoftheservice.*/privateServiceConnectionmSecondaryConnection=newServiceConnection(){publicvoidonServiceConnected(ComponentNameclassName,IBinderservice){//Connectingtoasecondaryinterfaceisthesameasany//otherinterface.mSecondaryService=ISecondary.Stub.asInterface(service);mKillButton.setEnabled(true);}publicvoidonServiceDisconnected(ComponentNameclassName){mSecondaryService=null;mKillButton.setEnabled(false);}};privateOnClickListenermBindListener=newOnClickListener(){publicvoidonClick(Viewv){//Establishacoupleconnectionswiththeservice,binding//byinterfacenames.Thisallowsotherapplicationstobe//installedthatreplacetheremoteservicebyimplementing//thesameinterface.bindService(newIntent(IRemoteService.class.getName()),mConnection,Context.BIND_AUTO_CREATE);bindService(newIntent(ISecondary.class.getName()),mSecondaryConnection,Context.BIND_AUTO_CREATE);mIsBound=true;mCallbackText.setText("Binding.");}};privateOnClickListenermUnbindListener=newOnClickListener(){publicvoidonClick(Viewv){if(mIsBound){//Ifwehavereceivedtheservice,andhenceregisteredwith//it,thennowisthetimetounregister.if(mService!=null){try{mService.unregisterCallback(mCallback);}catch(RemoteExceptione){//Thereisnothingspecialweneedtodoiftheservice//hascrashed.}}//Detachourexistingconnection.unbindService(mConnection);unbindService(mSecondaryConnection);mKillButton.setEnabled(false);mIsBound=false;mCallbackText.setText("Unbinding.");}}};privateOnClickListenermKillListener=newOnClickListener(){publicvoidonClick(Viewv){//Tokilltheprocesshostingourservice,weneedtoknowits//PID.Convenientlyourservicehasacallthatwillreturn//tousthatinformation.if(mSecondaryService!=null){try{intpid=mSecondaryService.getPid();//Notethat,thoughthisAPIallowsustorequestto//killanyprocessbasedonitsPID,thekernelwill//stillimposestandardrestrictionsonwhichPIDsyou//areactuallyabletokill.Typicallythismeansonly//theprocessrunningyourapplicationandanyadditional//processescreatedbythatappasshownhere;packages//sharingacommonUIDwillalsobeabletokilleach//other'sprocesses.Process.killProcess(pid);mCallbackText.setText("Killedserviceprocess.");}catch(RemoteExceptionex){//Recovergracefullyfromtheprocesshostingthe//serverdying.//Justforpurposesofthesample,putupanotification.Toast.makeText(Binding.this,R.string.remote_call_failed,Toast.LENGTH_SHORT).show();}}}};//----------------------------------------------------------------------//Codeshowinghowtodealwithcallbacks.//----------------------------------------------------------------------/***Thisimplementationisusedtoreceivecallbacksfromtheremote*service.*/privateIRemoteServiceCallbackmCallback=newIRemoteServiceCallback.Stub(){/***Thisiscalledbytheremoteserviceregularlytotellusabout*newvalues.NotethatIPCcallsaredispatchedthroughathread*poolrunningineachprocess,sothecodeexecutingherewill*NOTberunninginourmainthreadlikemostotherthings--so,*toupdatetheUI,weneedtouseaHandlertohopoverthere.*/publicvoidvalueChanged(intvalue){mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG,value,0));}};privatestaticfinalintBUMP_MSG=1;privateHandlermHandler=newHandler(){@OverridepublicvoidhandleMessage(Messagemsg){switch(msg.what){caseBUMP_MSG:mCallbackText.setText("Receivedfromservice:"+msg.arg1);break;default:super.handleMessage(msg);}}};}
Done.
更多相关文章
- Android(安卓)Wifi模块分析(三)
- Android中dispatchDraw分析
- 浅析Android中的消息机制-解决:Only the original thread that cr
- Android四大基本组件介绍与生命周期
- Android异步消息机制之Handler
- Android(安卓)Service AIDL
- Android的Handler机制详解3_Looper.looper()不会卡死主线程
- Android调用天气预报的WebService简单例子
- android打电话发短信