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协议的类,你必须做这些事情:

  1. 使你的类实现Parcelable接口。

  2. 实现writeToParcel,它将对象的当前状态写入一个Parcel

  3. 给你的类添加一个static成员CREATOR,它是一个对象,并实现了Parcelable.Creator接口。

  4. 最后,创建一个.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的其它种类的值。

警告:不要忘记从其它进程接收数据的安全隐患。在这个例子中,RectParcel读取4个数字,但你要确保这些数字在可接受的值的范围以内,而无论调用者要去做什麽。参考Security and Permissions来获取更多关于如何使你的应用更安全并远离病毒的信息。

调用一个IPC方法

这里是一个调用类调用一个AIDL定义的远程接口所要采取的步骤:

  1. 在项目的src/目录下包含.aidl文件。

  2. 声明一个IBinder接口的实例(基于AIDL而产生)。

  3. 实现ServiceConnection

  4. 调用Context.bindService(),传入你的ServiceConnection实现。

  5. 在你的onServiceConnected()实现中,你将接收一个IBinder实例(称为service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)来将返回的参数转换为YourInterface类型。

  6. 调用你的接口中定义的方法。你应该总是捕捉DeadObjectException异常,当连接断开时会抛出这个异常;这是远程方法将会抛出的唯一的异常。

  7. 要断开连接,则通过你的接口的实例调用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.

更多相关文章

  1. Android(安卓)Wifi模块分析(三)
  2. Android中dispatchDraw分析
  3. 浅析Android中的消息机制-解决:Only the original thread that cr
  4. Android四大基本组件介绍与生命周期
  5. Android异步消息机制之Handler
  6. Android(安卓)Service AIDL
  7. Android的Handler机制详解3_Looper.looper()不会卡死主线程
  8. Android调用天气预报的WebService简单例子
  9. android打电话发短信

随机推荐

  1. Android 初始化Setup Wizard——Provisio
  2. Android穿山甲SDK激励视频
  3. Android 应用开发支持不同的语言国际化操
  4. 【原创】Android 引用第三方库文件大全
  5. Android Studio 使用 Gradle 打包 Jar
  6. Android ListView异步加载图片乱序问题,原
  7. Android性能测试用例
  8. Android开发之实现图片自动滚动显示标签
  9. android SD卡学习3-读写文件
  10. Ormlite 介绍 一