`
ice.k
  • 浏览: 286651 次
  • 性别: Icon_minigender_1
  • 来自: 荷兰
社区版块
存档分类
最新评论

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

阅读更多
http://www.ophonesdn.com/article/show/170

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

Service
        下面的TestService类里的stub实现了3个方法,test1, test2, test3, 分别返回一个整数。这个service是在com.aidl.service这个包里的。
view plaincopy to clipboardprint?
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目录下。
view plaincopy to clipboardprint?
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:
view plaincopy to clipboardprint?
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的对象。
       
view plaincopy to clipboardprint?
  
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进程。
view plaincopy to clipboardprint?
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如下
view plaincopy to clipboardprint?
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机制,把返回值发送给客户端进程。
view plaincopy to clipboardprint?
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时
view plaincopy to clipboardprint?
package com.aidl.service; 
interface ITestService{ 
    int test2 (); 
    int test1 (); 
    int test3 (); 


  生成的ITestService里面定义的TRANSACTION code如下:
view plaincopy to clipboardprint?
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如下:
view plaincopy to clipboardprint?
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的编写有以下几个注意点。
新加函数接口应该在旧有函数的后面。
尽量避免删除旧有函数,如果真的要删的话,可以保留函数名字作为占位,返回一个错误码之类的来解决。
不能改变原来的接口声明顺序。
       当然如果改变原来函数接口,导致函数签名都变了的话,肯定会出错了,不过不是上面的错乱了。如果你非要这样改的话,会被别的工程师骂精神错乱的!

       如果是多人协作开发,使用同一个版本库的时候,所有使用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网站所有,如需转载请与我们编辑团队联系。任何媒体、网站或个人未经本网书面协议授权,不得进行任何形式的转载。已经取得本网协议授权的媒体、网站,在转载使用时请注明稿件来源。)
分享到:
评论

相关推荐

    OPhone平台2D游戏引擎实现 总汇

    OPhone平台2D游戏引擎实现总汇主要涵盖了在OPhone操作系统上开发2D游戏的关键技术和实践过程。OPhone是基于Android系统的一个中国移动定制版,它提供了丰富的API和工具集,使得开发者可以充分利用其特性来创建高质量...

    OPhone UI开发 游戏编程 H264解码器移植到OPhone OPhone平台编写网络

    OPhone UI开发者指南 OPhone联网应用开发中的线程管理与界面更新 OPhone平台开发环境常见问题 OPhone游戏编程 流媒体程序开发之:H264解码器移植到OPhone 如何在OPhone平台编写网络应用

    播思OPhone平台概述

    ### 播思OPhone平台概述 #### 一、引言 随着移动互联网技术的快速发展,智能手机成为人们日常生活中不可或缺的一部分。为了满足不同用户群体的需求,市场上涌现出了各种各样的移动操作系统。播思公司推出的OPhone...

    ophone sdk linux 1.0 setup

    5. **Android兼容性**:虽然Ophone基于Android,但可能存在特定的API差异,开发者需要了解如何处理兼容性问题。 6. **模拟器与真机调试**:使用SDK中的模拟器测试应用,或者通过USB连接真机进行调试。 7. **打包与...

    OPhone平台架构和主要开发组件借鉴.pdf

    OPhone平台是基于Linux内核和开放手机联盟(OHA)的Android系统构建的,由中国移动创新研发,具有独特的用户界面,并对多个关键组件进行了增强和优化,如浏览器能力、WAP兼容性以及多媒体和游戏功能。该平台的架构...

    创业计划书-中国移动OPhone平台网络公关传播方案

    《中国移动OPhone平台网络公关传播方案》是一份深入探讨如何在移动互联网领域,特别是中国移动OPhone平台上进行有效网络公关传播的创业计划书。该计划书的核心目标是利用网络媒体和公关策略,提升中国移动OPhone平台...

    OPhone平台Native开发与JNI机制详解

    文件列表中的"OPhone平台Native开发与JNI机制详解 - 技术文章 - OPhone SDN [OPhone 开发者网络].mht"是一个MHT文件,通常包含网页的完整内容,包括文字、图片、样式和脚本。这个文件很可能是上述博客的离线版本,...

    安装OPhone SDK详细过程

    【OPhone SDK安装详解】 OPhone SDK,全称为Open Mobile Phone SDK,是基于Android SDK的扩展,主要用于开发针对OPhone操作系统的应用程序。OPhone是中国移动推出的一个基于Linux的智能手机操作系统,它融合了...

    OPhone SDK for Windows

    OPhone SDK for Windows是专为Windows操作系统设计的一款开发工具包,它主要用于构建和优化基于OPhone操作系统的移动...同时,OPhone SDK还可能提供API文档和开发者社区支持,帮助开发者解决问题并学习新的开发技巧。

    ophone多媒体编程

    7.1.3 OPhone平台支持的媒体格式 276 7.1.4 选择合适的媒体文件 277 7.2 音频和视频播放 277 7.2.1 三种不同的数据源 278 7.2.2 MediaPlayer的状态 281 7.2.3 音乐播放器实例 284 7.2.4 播放视频 296 7.3 ...

    Ophone SDK

    5. **库文件**: 提供必要的库文件,包括编译和运行Ophone应用所必需的动态链接库和静态库。 **二、开发流程** 1. **设置环境**: 首先,开发者需要安装Ophone SDK,配置开发环境,如安装Java JDK,设置PATH环境变量...

    Ophone平台蓝牙编程之蓝牙聊天分析(一)

    ### Ophone平台蓝牙编程之蓝牙聊天分析 #### 一、Ophone平台蓝牙聊天应用概述 在本篇文章中,我们将深入探讨Ophone平台上的蓝牙聊天应用开发。通过详细解析一个具体的蓝牙聊天示例程序,旨在帮助读者理解如何在...

    Android/Ophone开发完全讲义源码

    在Android和Ophone开发领域,源码是学习与探索系统工作原理、优化应用...同时,源码实践将有助于开发者解决实际问题,提升代码质量和应用性能。对于有志于从事Android和Ophone开发的程序员来说,这是一份宝贵的资源。

    Ophone API中文版(打包)

    Ophone API中文版是一个专为Ophone平台开发者提供的软件开发工具包,它包含了丰富的功能接口和文档,使得开发者能够更加方便地进行Ophone应用的开发。Ophone是中国移动推出的一款基于Linux内核和Google Android系统...

    OPhone平台与MobileMarket发布公关传播案.pptx

    【OPhone平台与MobileMarket发布公关传播案】的讲解 OPhone平台是中国移动推出的一款基于Android操作系统深度定制的智能手机平台,旨在提供一个开放且具有无限想象空间的技术平台,以满足不同用户群体的需求。...

    Android/OPhone开发完全讲义 Android 完整代码

    这本书旨在帮助开发者全面掌握Android和OPhone平台的开发技能,从基础到高级,覆盖了整个移动应用开发的生命周期。 在Android开发部分,该讲义首先介绍了Android平台的基础知识,包括Android系统架构、开发环境搭建...

    APK文件关联器,支持Ophone

    标题中的“APK文件关联器,支持Ophone”指的是一个工具,主要功能是帮助用户在非Android系统(比如Ophone,这是中国移动推出的一个基于Linux的智能手机操作系统)上安装和管理APK格式的应用程序。APK是Android应用的...

Global site tag (gtag.js) - Google Analytics