`
anson_xu
  • 浏览: 516856 次
  • 性别: Icon_minigender_1
  • 来自: 惠州
社区版块
存档分类

aidl 理解

阅读更多

OPhone平台aidl文件不一致导致的问题及解决

OPhone平台开发, 2010-01-04 17:53:06

标签 : aidl文件 Ophone平台

 

       使用OPhone平台Service机制时,如果客户端所用的aidl文件和已安装的Service所使用的aidl文件不一致时会导致接口调用的错误,甚至会导致程序错误退出。比如Service升级时,会在aidl文件里增加或修改接口,如果客户端不更新所使用的aidl文件,这就会出现上述不一致的情况。本文主要分析这个问题的原因和解决方案。

Service
        下面的TestService类里的stub实现了3个方法,test1, test2, test3, 分别返回一个整数。这个service是在com.aidl.service这个包里的。
  1. package com.aidl.service;   
  2. public class TestService extends Service   
  3. {   
  4.     public IBinder onBind(Intent intent) {   
  5.         return binder;   
  6.     }   
  7.     
  8.     private final ITestService.Stub binder = new ITestService.Stub(){   
  9.         public int test1(){   
  10.             return 1;   
  11.         }   
  12.         public int test2(){   
  13.             return 2;   
  14.         }   
  15.         public int test3(){   
  16.             return 3;   
  17.         }   
  18.     };   
  19. }   
  20. 定义aidl: ITestService.aidl   
  21. package com.aidl.service;   
  22. interface ITestService{   
  23.     int test1 ();   
  24.     int test2 ();   
  25.     int test3 ();   
  26. }   
  27. manifest定义   
  28. <application android:label="@string/app_name">   
  29.     <service android:name="com.aidl.service.TestService">   
  30.         <intent-filter>   
  31.             <action android:name="com.aidl.intent.TEST_SERVICE" />   
  32.         </intent-filter>   
  33.     </service>   
  34. </application>  
package com.aidl.service; public class TestService extends Service { public IBinder onBind(Intent intent) { return binder; } private final ITestService.Stub binder = new ITestService.Stub(){ public int test1(){ return 1; } public int test2(){ return 2; } public int test3(){ return 3; } }; } 定义aidl: ITestService.aidl package com.aidl.service; interface ITestService{ int test1 (); int test2 (); int test3 (); } manifest定义 <application android:label="@string/app_name"> <service android:name="com.aidl.service.TestService"> <intent-filter> <action android:name="com.aidl.intent.TEST_SERVICE" /> </intent-filter> </service> </application>
 
       这样TestService作为一个apk,就是提供了一个service。而别的应用,只需要拿到ITestService.aidl文件就可以用这个service了。
 
Client的Activity
       有时候,你实现的service给不同的Activity用,而且并不都是和service在一个包里的,甚至不是一个apk里的,这时要使用service的接口就需要把aidl文件复制到自己的src目录下。
  1. package com.aidl.client;   
  2. import com.aidl.service.ITestService;   
  3. public class TestActivity extends Activity   
  4. {   
  5.     ITestService mService;   
  6.     
  7.     Button testButton1, testButton2, testButton3;   
  8.     
  9.     private Intent intent = new Intent("com.aidl.intent.TEST_SERVICE");   
  10.     
  11.     private ServiceConnection mConnection = new ServiceConnection() {   
  12.         public void onServiceConnected(ComponentName className, IBinder service) {   
  13.             mService = ITestService.Stub.asInterface(service);   
  14.         }   
  15.         public void onServiceDisconnected(ComponentName className) {   
  16.             mService = null;   
  17.         }   
  18.     };   
  19.     
  20.     public void onCreate(Bundle savedInstanceState)   
  21.     {   
  22.         super.onCreate(savedInstanceState);   
  23.         setContentView(R.layout.main);   
  24.         startService(intent);   
  25.     }   
  26.     
  27.     public void onResume(){   
  28.         super.onResume();   
  29.         bindService(intent, mConnection, Context.BIND_AUTO_CREATE);   
  30.            
  31.         testButton1 = (Button)findViewById(R.id.test1);   
  32.         testButton1.setOnClickListener(new Button.OnClickListener(){   
  33.             public void onClick(View v){   
  34.                 try {   
  35.                     showToast(mService.test1());   
  36.                 }catch(RemoteException e){   
  37.                 }   
  38.             }   
  39.         });   
  40.         ...   
  41.     }   
  42.     
  43.     public void onDestroy(){   
  44.         super.onDestroy();   
  45.         unbindService(mConnection);   
  46.     }   
  47.     
  48.     ...   
  49. }  
package com.aidl.client; import com.aidl.service.ITestService; public class TestActivity extends Activity { ITestService mService; Button testButton1, testButton2, testButton3; private Intent intent = new Intent("com.aidl.intent.TEST_SERVICE"); private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { mService = ITestService.Stub.asInterface(service); } public void onServiceDisconnected(ComponentName className) { mService = null; } }; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); startService(intent); } public void onResume(){ super.onResume(); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); testButton1 = (Button)findViewById(R.id.test1); testButton1.setOnClickListener(new Button.OnClickListener(){ public void onClick(View v){ try { showToast(mService.test1()); }catch(RemoteException e){ } } }); ... } public void onDestroy(){ super.onDestroy(); unbindService(mConnection); } ... }
 
      上面的TestActivity在com.aidl.test包里,使用的是TestService的接口,这时需要在这个apk的代码目录里面有一份ITestService.aidl的拷贝。这个TestActivity有3个按钮,分别是test1, test2, test3。预期的情况是,点testX按钮时,调用TestService的testX接口,然后显示一个Toast,内容是TextX方法被调用了。如下图所示
 
 
问题出现
       这时,如果TestService的aidl有改变,比如增加或减少接口,别的使用旧的aidl的应用就会有问题。哪怕是aidl里面的接口顺序变化也会带来问题。(注:一般来讲service的接口一旦发布,是不好轻易改动的。但是在团队协作开发时,这个情况就会出现。)
     下面是TestService的新aidl:
  1. package com.aidl.service;   
  2. interface ITestService{   
  3.     int test2 ();   
  4.     int test1 ();   
  5.     int test3 ();   
  6. }  
package com.aidl.service; interface ITestService{ int test2 (); int test1 (); int test3 (); }
 
     而TestActivity还使用旧的aidl,这时还点test1按钮:
 
     预期应该调用service的test1接口,但是却调了test2接口...
 
客户端与service的通讯
         为什么会出现上述的问题呢?客户端与service的通讯是通过binder进行的,在build的时候,客户端与service两边都会根据aidl文件生成具体的ITestService类。
 
客户端的ITestService
TestActivity是通过调用ITestService.Stub.asInterface(service)来得到Service在本进程的代理。下面是客户端生成的ITestService的asInterface函数,返回一个ITestService.Stub.Proxy的对象。
        
  1.     
  2. public static com.aidl.service.ITestService asInterface(android.os.IBinder obj)   
  3. {   
  4.     if ((obj==null)) {   
  5.         return null;   
  6.     }   
  7.     android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);   
  8.     if (((iin!=null)&&(iin instanceof com.aidl.service.ITestService))) {   
  9.         return ((com.aidl.service.ITestService)iin);   
  10.     }   
  11.     return new com.aidl.service.ITestService.Stub.Proxy(obj);   
  12. }  
 public static com.aidl.service.ITestService asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.aidl.service.ITestService))) { return ((com.aidl.service.ITestService)iin); } return new com.aidl.service.ITestService.Stub.Proxy(obj); }
        Proxy里面实现了test1等方法,而且定义了对应的TRANSACTION_test1等TRANSACTION code。当TestActivity调mService的test1时,就调用了Proxy的test1方法,而test1是调用transact方法进行进程间通讯,把TRANSACTION code通过binder发送到service进程。
  1. private static class Proxy implements com.aidl.service.ITestService{   
  2.     private android.os.IBinder mRemote;   
  3.     Proxy(android.os.IBinder remote){   
  4.         mRemote = remote;   
  5.     }   
  6.     public android.os.IBinder asBinder(){   
  7.         return mRemote;   
  8.     }   
  9.     public java.lang.String getInterfaceDescriptor(){   
  10.         return DESCRIPTOR;   
  11.     }   
  12.     public int test1() throws android.os.RemoteException{   
  13.         android.os.Parcel _data = android.os.Parcel.obtain();   
  14.         android.os.Parcel _reply = android.os.Parcel.obtain();   
  15.         int _result;   
  16.         try {   
  17.             _data.writeInterfaceToken(DESCRIPTOR);   
  18.             mRemote.transact(Stub.TRANSACTION_test1, _data, _reply, 0);   
  19.             _reply.readException();   
  20.             _result = _reply.readInt();   
  21.         }finally {   
  22.             _reply.recycle();   
  23.             _data.recycle();   
  24.         }   
  25.         return _result;   
  26.     }   
  27.     ...   
  28.     //接口的TRANSACTION code   
  29.     static final int TRANSACTION_test1 = (IBinder.FIRST_CALL_TRANSACTION + 0);   
  30.     static final int TRANSACTION_test2 = (IBinder.FIRST_CALL_TRANSACTION + 1);   
  31.     static final int TRANSACTION_test3 = (IBinder.FIRST_CALL_TRANSACTION + 2);   
  32. }  
private static class Proxy implements com.aidl.service.ITestService{ private android.os.IBinder mRemote; Proxy(android.os.IBinder remote){ mRemote = remote; } public android.os.IBinder asBinder(){ return mRemote; } public java.lang.String getInterfaceDescriptor(){ return DESCRIPTOR; } public int test1() throws android.os.RemoteException{ android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_test1, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); }finally { _reply.recycle(); _data.recycle(); } return _result; } ... //接口的TRANSACTION code static final int TRANSACTION_test1 = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_test2 = (IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_test3 = (IBinder.FIRST_CALL_TRANSACTION + 2); }
 
service的ITestService
         service生成的ITestService的TRANSACTION code如下
  1. static final int TRANSACTION_test1 = (IBinder.FIRST_CALL_TRANSACTION + 0);   
  2. static final int TRANSACTION_test2 = (IBinder.FIRST_CALL_TRANSACTION + 1);   
  3. static final int TRANSACTION_test3 = (IBinder.FIRST_CALL_TRANSACTION + 2);  
static final int TRANSACTION_test1 = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_test2 = (IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_test3 = (IBinder.FIRST_CALL_TRANSACTION + 2);
 
     这时,service进程的binder对象会调用onTransact函数,而这个函数是在service端生成的ITestService类里。在这里面,根据收到的TRANSACTION code是TRANSACTION_test1就会调TestService里面ITestService.Stub对象binder里面实现的test1函数然后返回。再通过ipc机制,把返回值发送给客户端进程。
  1. public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {   
  2.     switch (code) {   
  3.         case INTERFACE_TRANSACTION:   
  4.             {   
  5.                 reply.writeString(DESCRIPTOR);   
  6.                 return true;   
  7.             }   
  8.         case TRANSACTION_test1:   
  9.             {   
  10.                 data.enforceInterface(DESCRIPTOR);   
  11.                 int _result = this.test1();   
  12.                 reply.writeNoException();   
  13.                 reply.writeInt(_result);   
  14.                 return true;   
  15.             }   
  16.         case TRANSACTION_test2:   
  17.             {   
  18.                 data.enforceInterface(DESCRIPTOR);   
  19.                 int _result = this.test2();   
  20.                 reply.writeNoException();   
  21.                 reply.writeInt(_result);   
  22.                 return true;   
  23.             }   
  24.         case TRANSACTION_test3:   
  25.             {   
  26.                 data.enforceInterface(DESCRIPTOR);   
  27.                 int _result = this.test3();   
  28.                 reply.writeNoException();   
  29.                 reply.writeInt(_result);   
  30.                 return true;   
  31.             }   
  32.     }   
  33.     return super.onTransact(code, data, reply, flags);   
  34. }  
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_test1: { data.enforceInterface(DESCRIPTOR); int _result = this.test1(); reply.writeNoException(); reply.writeInt(_result); return true; } case TRANSACTION_test2: { data.enforceInterface(DESCRIPTOR); int _result = this.test2(); reply.writeNoException(); reply.writeInt(_result); return true; } case TRANSACTION_test3: { data.enforceInterface(DESCRIPTOR); int _result = this.test3(); reply.writeNoException(); reply.writeInt(_result); return true; } } return super.onTransact(code, data, reply, flags); }
 
问题所在
        如果客户端和service的aidl文件是不一致的,就会出现问题了。
TestService使用新的aidl时
  1. package com.aidl.service;   
  2. interface ITestService{   
  3.     int test2 ();   
  4.     int test1 ();   
  5.     int test3 ();   
  6. }  
package com.aidl.service; interface ITestService{ int test2 (); int test1 (); int test3 (); }
 
  生成的ITestService里面定义的TRANSACTION code如下:
  1. static final int TRANSACTION_test2 = (IBinder.FIRST_CALL_TRANSACTION + 0);   
  2. static final int TRANSACTION_test1 = (IBinder.FIRST_CALL_TRANSACTION + 1);   
  3. static final int TRANSACTION_test3 = (IBinder.FIRST_CALL_TRANSACTION + 2);  
static final int TRANSACTION_test2 = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_test1 = (IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_test3 = (IBinder.FIRST_CALL_TRANSACTION + 2);
 
 
    客户端TestActivity还使用旧的aidl,生成的ITestService里面定义的TRANSACTION code如下:
  1. static final int TRANSACTION_test1 = (IBinder.FIRST_CALL_TRANSACTION + 0);   
  2. static final int TRANSACTION_test2 = (IBinder.FIRST_CALL_TRANSACTION + 1);   
  3. static final int TRANSACTION_test3 = (IBinder.FIRST_CALL_TRANSACTION + 2);  
static final int TRANSACTION_test1 = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_test2 = (IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_test3 = (IBinder.FIRST_CALL_TRANSACTION + 2);
 
      从上面的两组TRANSACTION code可以看出,TRANSACTION code是根据aidl里接口声明的顺序生成的。IBinder.FIRST_CALL_TRANSACTION的值是1,也就是说TRANSACTION_test1的值在客户端里是1,而在service端是2! 而service端onTransact函数里的switch,当收到的code是1的时候,认为是应该调用TRANSACTION_test2对应的test2方法了。所以就出现上面的例子中,诡异的错乱现象了。
 
       所以当aidl里面函数的声明顺序改变,或者新加,删除函数,都会造成TRANSACTION code的值会不同。这样使用旧aidl文件的应用就可能出现问题!
 
解决方案
       当service升级时,为了避免出现上面的问题,应该保证aidl的变化不影响到旧有接口的TRANSACTION code。所以新的aidl的编写有以下几个注意点。
  1. 新加函数接口应该在旧有函数的后面。
  2. 尽量避免删除旧有函数,如果真的要删的话,可以保留函数名字作为占位,返回一个错误码之类的来解决。
  3. 不能改变原来的接口声明顺序。
       当然如果改变原来函数接口,导致函数签名都变了的话,肯定会出错了,不过不是上面的错乱了。如果你非要这样改的话,会被别的工程师骂精神错乱的!
 
       如果是多人协作开发,使用同一个版本库的时候,所有使用service的应用程序,不是把aidl代码cp到自己的目录里,而是建一个文件链接link到service目录里面的aidl。这样在service aidl文件有变化的时候,客户端不需要手动更新aidl文件。
 
      比如上面例子中,TestActivity的client/src/com/aidl/service/目录里面ITestService.aidl就是service/src/com/aidl/service/ITestService.aidl的link。
ln -s service/src/com/aidl/service/ITestService.aidl client/src/com/aidl/service/ITestService.aidl
 
建议
       其实这个问题我觉得属于service机制设计上的一个缺陷。如果客户端和service都是以函数签名而不是code来标志aidl里的接口,在onTransact()里使用函数签名进行判断具体调用哪个接口的话,就能根本上解决这个问题。而字符串的比较和int的比较的开销即使有性能差别,也是可以接受的。这样设计的话对于开发人员来说使用起来和原来一样方便,而且不会出现上述问题。
 
作者简介
        Bear,豆瓣网开发工程师,曾在新浪网,雅虎中国工作,有3年的互联网开发经验,热衷开源项目,通过豆瓣电台手机客户端项目接触Android/OPhone平台,对手机上的应用开发很感兴趣,希望和大家多做交流。beartung@gmail.com
 

(声明:本网的新闻及文章版权均属OPhone SDN网站所有,如需转载请与我们编辑团队联系。任何媒体、网站或个人未经本网书面协议授权,不得进行任何形式的转载。已经取得本网协议授权的媒体、网站,在转载使用时请注明稿件来源。)

分享到:
评论

相关推荐

    ITelephony.aidl和NeighboringCellInfo.aidl

    在Android系统中,AIDL(Android Interface Definition Language)是一...在实际开发中,理解并正确使用这两个接口可以实现高级的电话管理和网络监控功能,但要注意权限管理,避免侵犯用户隐私或违反操作系统安全策略。

    深入理解aidl&service;

    深入理解aidl和service机制。 aidl是android进行系统间通信的借口描述语言,仔细分析由aidl生成的java类,你会发现code是用的是proxy design pattern,能看到这层就会觉得google的设计其实真的是很巧妙。如果需要...

    android aidl 本地和aidl通信,远端和aidl

    在实际开发中,可以通过解压这两个文件,查看源代码来深入理解AIDL的工作原理和应用方法。同时,通过运行示例,可以直观地看到AIDL通信的效果。 总之,AIDL是Android IPC的重要手段,它简化了跨进程通信的复杂性,...

    AIDLDemo,Android aidl 使用demo

    在AIDLDemo项目中,可以深入研究服务器端的`IMyService.aidl`文件、服务实现类(如`MyServiceImpl.java`)和客户端的调用代码(如`MainActivity.java`),以理解AIDL的工作原理和使用方法。 通过AIDLDemo项目的学习...

    AIDL双向绑定kotlin版本

    首先,我们需要理解什么是AIDL。AIDL是一种接口定义语言,它允许开发者声明服务端提供的接口,客户端通过这些接口调用服务端的方法,实现跨进程通信。通常,AIDL用于实现单向通信,即客户端调用服务端的方法,但AIDL...

    aidl.rar_AIDL_AIDL.rar_android AIDL service_android service_andr

    AIDL(Android Interface Definition Language)是Android...通过以上知识点,你可以更好地理解和使用AIDL在Android应用程序中实现服务的跨进程通信。理解并熟练运用AIDL有助于提高Android应用的架构设计和功能实现。

    Android aidl实现传递对象

    首先,我们需要理解AIDL的基本概念。AIDL文件是一种定义接口的文本文件,它以.aidl为扩展名,包含了方法签名和数据类型声明。这些定义会被Android编译器转换成Java代码,生成服务端和服务客户端所需的接口类。AIDL...

    Android Aidl

    在“Aidldemo”中,我们可以看到一个服务端(AIDLServer)和客户端(AIDLClient)的示例,这是为了方便开发者理解和使用AIDL进行跨进程通信。 首先,理解AIDL的基本结构和工作原理至关重要。AIDL文件以`.aidl`为...

    AIDLDemo.zip

    在Android系统中,跨进程通信(IPC,Inter-Process Communication)..."AIDLDemo.zip"的示例代码为你提供了一个清晰的实践教程,通过学习和运行这个示例,你可以深入理解AIDL的工作原理,并能在自己的应用中灵活运用。

    AidlDemo(简单aidl的例子.zip

    **描述解析:** "简单的Android学习源代码 aidl的使用方法 Android 入门学习的资历" 描述中提到这是一份适合初学者的教育资源,它提供了一个AIDL的实际应用案例,帮助学习者理解如何在Android应用中实现跨线程通信。...

    AIDL和JNI使用的小例子

    **Android Interface Definition Language (AIDL)** AIDL是Android中用于进程间通信(IPC, Inter-Process Communication...通过阅读源码和配合博客文章,你可以深入理解这两种技术的工作原理和它们在实际开发中的应用。

    AIDL for HALs测试客户端

    **Android Interface Definition Language (AIDL) 用于 Hardware Abstraction Layers (HALs) 的测试客户端** ...在实际开发过程中,理解和熟练运用这些工具和技术对于提升Android系统的质量至关重要。

    AIDL_AidlService_android_

    总的来说,AIDL是Android平台中进行进程间通信的关键技术,理解并熟练使用AIDL,能够帮助开发者构建更加健壮和高效的Android应用。通过不断的实践和学习,开发者可以更好地掌握AIDL的精髓,提高应用的可扩展性和复用...

    android_aidl例子

    首先,让我们深入理解AIDL的基本概念。AIDL本质上是定义接口的语法,类似于Java中的接口,但它增加了跨进程调用的能力。当你在服务中定义一个AIDL接口,Android会自动生成相应的Stub类,这个Stub类实现了该接口,...

    利用AIDL进程间传输图片例子

    首先,我们需要理解AIDL的基本概念。AIDL是一种简单的接口定义语言,它可以让我们定义一个接口,该接口可以在不同的Android进程中调用。当我们定义了一个AIDL接口后,Android编译器会自动生成相应的Java代码,使得...

    AIDL传递基础数据类型

    在Android系统中,AIDL(Android Interface Definition Language)是一种接口定义语言,用于处理跨进程通信(IPC, Inter-Process ...通过分析项目的代码,可以更直观地理解上述概念,并学习如何在实际应用中应用AIDL。

    AIDL List传递 String传递(android studio)

    首先,让我们理解AIDL的基本结构。AIDL文件是一个文本文件,它定义了一个接口,这个接口包含了可以在不同进程中调用的方法。在服务端,我们需要创建一个AIDL文件,定义需要暴露的接口。例如: ```aidl package ...

    Andorid AIDL模拟实现

    Android AIDL(Android Interface Definition Language)是Android系统提供的一种接口定义语言,用于处理Android应用程序...通过理解AIDL的工作原理和使用方式,开发者可以更有效地设计和实现Android应用的组件间通信。

    AIDL-demo源码

    本教程将深入解析"AIDL-demo"源码,以帮助你理解AIDL的工作原理和实际应用。 **1. AIDL基础知识** AIDL文件是一个文本文件,使用特定的语法来声明方法签名,类似于Java接口。通过AIDL,Android系统会自动生成对应的...

    AIDL如何使用

    理解AIDL的使用对于开发者来说至关重要,因为它解决了Android系统中跨进程数据交换和功能调用的问题。 ### 什么是AIDL AIDL是Android接口定义语言的简称,它的主要作用是定义两个不同进程间通信的接口。通过AIDL,...

Global site tag (gtag.js) - Google Analytics