AIDL使用

作者: 逐鹿者不见山 | 来源:发表于2020-10-27 23:29 被阅读0次

    1、定义

    AIDL(Android Interface Define Language) 是IPC进程间通信方式的一种.用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码.

    2、使用

    2.1)实例说明

    假设一个情景我们需要计算a+b,我们需要在客户端传递两个参数a和b,然后将参数传递给服务端(另一个进程)来进行计算,计算结果传递给客户端。

    2.2)新建一个项目作为服务端,在项目中创建AIDL文件

    命名为IImoocAIDL.aidl,点击同步sycn project,查看build内是否自动生成IImoocAIDL文件

    // IImoocAIDL.aidl
    package com.mecury.aidltest;
    // Declare any non-default types here with import statements
    interface IImoocAIDL {    
        //计算num1 + num2    
        int add(int num1,int num2);
    }
    
    

    2.3)自动生成AIDL文件

    根据自动生成的AIDL文件分析AIDL通信原理:
    文件有一个叫做proxy的类,这是一个代理类,这个类运行在客户端中,其实AIDL实现的进程间的通信并不是直接的通信,客户端和服务端都是通过proxy来进行通信的:客户端调用的方法实际是调用是proxy中的方法,然后proxy通过和服务端通信将返回的结果返回给客户端。

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: F:\\AS for android\\AIDLTest\\aidlclient\\src\\main\\aidl\\com\\mecury\\aidltest\\IImoocAIDL.aidl
     */
    package com.mecury.aidltest;
    // Declare any non-default types here with import statements
    
    public interface IImoocAIDL extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements com.mecury.aidltest.IImoocAIDL {
            private static final java.lang.String DESCRIPTOR = "com.mecury.aidltest.IImoocAIDL"; //Binder的唯一标识
    
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an com.mecury.aidltest.IImoocAIDL interface,
             * generating a proxy if needed.
             */
            //将服务端的Binder对象转换成客户需要的AIDL对象,转换区分进程,客户端服务端位于同一进程,返回服务端的
            //Stub对象本身;否则返回的是系统的封装后的Stub.proxy对象。
            public static com.mecury.aidltest.IImoocAIDL asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof com.mecury.aidltest.IImoocAIDL))) {
                    return ((com.mecury.aidltest.IImoocAIDL) iin);
                }
                return new com.mecury.aidltest.IImoocAIDL.Stub.Proxy(obj);
            }
            //返回当前Binder对象
            @Override
            public android.os.IBinder asBinder() {
                return this;
            }
    
            //运行在服务端的Binder线程池中,接受到proxy的值并进行相应的处理
            @Override
            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_add: {
                        data.enforceInterface(DESCRIPTOR);
                        //读取客户端传递过来再data中存储的方法的参数
                        int _arg0;
                        _arg0 = data.readInt();
                        int _arg1;
                        _arg1 = data.readInt();
                        //调用方法
                        int _result = this.add(_arg0, _arg1);
                        //将计算结果写入reply中,如果是void,就不需要返回值,如果是需要返回值,则会调用reply.writeInt(_result);
                        reply.writeNoException();
                        reply.writeInt(_result);
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags); //向Transact传递数据
            }
    
            //代理类,运行在客户端
            private static class Proxy implements com.mecury.aidltest.IImoocAIDL {
                private android.os.IBinder mRemote; //声明一个IBinder对象
    
                Proxy(android.os.IBinder remote) {
                    mRemote = remote;
                }
                //返回当前Binder对象
                @Override
                public android.os.IBinder asBinder() {
                    return mRemote;
                } 
    
                public java.lang.String getInterfaceDescriptor() {
                    return DESCRIPTOR;
                }
    
                //客户端调用此方法,传递进来num1和num2两个参数,
                @Override
                public int add(int num1, int num2) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    int _result;
                    try {
                        //向_data中写入参数
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeInt(num1);
                        _data.writeInt(num2);
                        //通过transact方法向服务端传递参数,并调用了方法,返回的结果写入_reply中
                        //第一个参数为唯一码,从000001开始,有几个方法就有几个,两边app的aidl方法顺序不能乱
                        mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.readInt();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
            //标识位
            static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        }
    
        //计算num1 + num2
        public int add(int num1, int num2) throws android.os.RemoteException;
    }
    
    

    代码中方法说明:
    (1)DESCRIPTION
    Binderd的唯一标识,一般用当前的类名表示。
    asInterface(android.os.IBinder obj)
    用于将服务端的Binder对象转换为客户端需要的AIDL接口类型的对象,转换区分进程,客户端服务端位于同一进程,返回服务端的 //Stub对象本身;否则返回的是系统的封装后的Stub.proxy对象。
    (2)asBInder
    返回Binder对象
    (3)onTransact
    此方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理。
    (4)Proxy#add
    此方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reple和返回值对象_result,然后将该方法的参数信息写入_data中;接着调用transact方法来发RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程返回的结果,写入_result中。

    2.4)新建客户端

    File-》new–》new module–》phone & table module。命名为aidlclient.java。同样要在客户端创建AIDL文件,里面的包名和所在位置要求完全一样。

    2.5)在服务端创建一个Service用来监听客户端连接请求

    public class IRemoteService extends Service {
    
        //客户端绑定service时会执行
        @Override
        public IBinder onBind(Intent intent) {
            return iBinder;
        }
    
        private IBinder iBinder = new IImoocAIDL.Stub(){
    
            @Override
            public int add(int num1, int num2) throws RemoteException {
                Log.e("TAG","收到了来自客户端的请求" + num1 + "+" + num2 );
                return num1 + num2;
            }
        };
    }
    
    

    2.6)在AndroidMainfest.xml注册Service

    <service android:name=".IRemoteService"
                android:process=":remote"
                android:exported="true">
                <intent-filter>
                    <action android:name="com.mecury.aidltest.IRomoteService"/>
                </intent-filter>
    </service>
    
    

    2.7)客户端绑定服务并调用服务端方法

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private EditText num1;
        private EditText num2;
        private Button button;
        private TextView text;
    
        private IImoocAIDL iImoocAIDL;
    
        private ServiceConnection conn = new ServiceConnection() {
    
            //绑定服务,回调onBind()方法
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iImoocAIDL = IImoocAIDL.Stub.asInterface(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                iImoocAIDL = null;
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            bindService();
            initView();
    
        }
    
        private void initView() {
            num1 = (EditText) findViewById(R.id.num1);
            num2 = (EditText) findViewById(R.id.num2);
            button = (Button) findViewById(R.id.button);
            text = (TextView) findViewById(R.id.text);
    
            button.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            int num11 = Integer.parseInt(num1.getText().toString());
            int num22 = Integer.parseInt(num2.getText().toString());
    
            try {
                int res = iImoocAIDL.add(num11,num22);
                text.setText(num11 +"+"+ num22 +"="+ res);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
        private void bindService() {
    
            Intent intent = new Intent();
            //绑定服务端的service
            //intent.setAction("com.mecury.aidltest.IRomoteService");
            //新版本(5.0后)必须显式intent启动 绑定服务
    //参数1:包名,被启动的程序的包名
    //参数2:全类名,被启动的程序的全类名
            intent.setComponent(new ComponentName("com.mecury.aidltest","com.mecury.aidltest.IRemoteService"));
            //绑定的时候服务端自动创建
            bindService(intent,conn, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(conn);
        }
    }
    
    

    3、AIDL支持的数据类型

    • 基本数据类型(int、long、char 等)
    • String 和 CharSequence
    • List:只支持ArrayList,里面的每个元素都必须被AIDL支持。
    • Map: 只支持HashMap, 里面的每个元素都必须被AIDL支持。
    • Parcelable: 所有实现了Parcelable接口的对象
    • AIDL: 所有的AIDL接口本身也可以在AIDL文件中使用

    4、需要注意的点

    如果客户端和service的aidl文件是不一致的,就会出现问题了。
    当TestService使用新的aidl时
    view plaincopy to clipboardprint?
    package com.aidl.service;
    interface ITestService{
    int test2 ();
    int test1 ();
    int test3 ();
    }
    生成的IImoocAIDL 里面定义的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);

    客户端IImoocAIDL 还使用旧的aidl,生成的IImoocAIDL 里面定义的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/mecury/aidltest/目录里面IImoocAIDL .aidl就是service/src/com/mecury/aidltest/IImoocAIDL .aidl的link。
    

    ln -s service/src/com/mecury/aidltest/ITestService.aidl client/src/com/mecury/aidltest/IImoocAIDL .aidl

    相关文章

      网友评论

        本文标题:AIDL使用

        本文链接:https://www.haomeiwen.com/subject/imvfvktx.html