Android IPC 之AIDL应用(上)

作者: 小鱼人爱编程 | 来源:发表于2021-11-18 12:11 被阅读0次

    前言

    IPC 系列文章:
    建议按顺序阅读。

    Android IPC 之Service 还可以这么理解
    Android IPC 之Binder基础
    Android IPC 之Binder应用
    Android IPC 之AIDL应用(上)
    Android IPC 之AIDL应用(下)
    Android IPC 之Messenger 原理及应用
    Android IPC 之服务端回调
    Android IPC 之获取服务(IBinder)

    上一篇文章分析了如何使用Binder进行进程间通信以及提出了直接使用Binder编码的缺点,本篇阐述如何使用AIDL解决以上缺点。
    通过本篇文章,你将了解到:

    1、如何编写AIDL 文件
    2、如何使用AIDL

    1、如何编写AIDL 文件

    什么是AIDL

    AIDL 是Android Interface Definition Language (Android 接口定义语言)的缩写。

    创建AIDL 文件

    Android Studio本身支持创建AIDL文件,先创建名为IMyServer 的AIDL文件。
    在Module上右键单击:


    image.png

    输入名字:


    image.png
    确定后生成 IMyServer.aidl文件:
    image.png

    可以看出,由于是第一次创建AIDL文件,因此还创建了aidl文件夹并添加了包名作为目录结构,其总体结构如下:

    src/main/aidl/com/fish/myapplication/IMyServer.aidl

    其中/aidl目录与/java、/res目录平级,都在main目录下:

    app/src/main/
    app/src/aidl/
    app/src/res

    AIDL 文件内容

    生成IMyServer.aidl内容如下:

    //包名
    package com.fish.myapplication;
    
    interface IMyServer {
        //aidl 支持的基本数据类型
        //默认生成的方法,可以去掉
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);
    }
    

    其中basicTypes(xx)方法是自动生成的,用来指导我们如何编写方法,可以去掉。
    IMyServer 接口里声明的方法为Server端暴露给外部调用的方法,先为Server添加方法:

    
    //包名
    package com.fish.myapplication;
    
    interface IMyServer {
        //只有一个参数,并且没有返回值
        void say(String word);
        
        //有两个参数,并且返回int
        int tell(String word, int age);
    }
    

    然后编译工程。

    AIDL 编译产物

    编译成功后,切换到Project模式,搜索IMyServer.java:


    image.png

    可以看出,编写的AIDL文件,最终根据一定的规则映射生成Java文件,接着来看看IMyServer.java内容。

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     */
    package com.fish.myapplication;
    public interface IMyServer extends android.os.IInterface
    {
      //默认类实现接口,可以不用关注
      public static class Default implements com.fish.myapplication.IMyServer
      {
        @Override public void say(java.lang.String word) throws android.os.RemoteException
        {
        }
        @Override public int tell(java.lang.String word, int age) throws android.os.RemoteException
        {
          return 0;
        }
        @Override
        public android.os.IBinder asBinder() {
          return null;
        }
      }
    
    
      public static abstract class Stub extends android.os.Binder implements com.fish.myapplication.IMyServer
      {
        //描述符
        private static final java.lang.String DESCRIPTOR = "com.fish.myapplication.IMyServer";
        public Stub()
        {
          //调用Binder方法,将Binder与IInterface 关联起来
          //也就是说Binder持有IInterface引用
          this.attachInterface(this, DESCRIPTOR);
        }
    
        //通过Binder找到关联的IInterface
        public static com.fish.myapplication.IMyServer asInterface(android.os.IBinder obj)
        {
          if ((obj==null)) {
            return null;
          }
          android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
          if (((iin!=null)&&(iin instanceof com.fish.myapplication.IMyServer))) {
            //IBinder引用与调用者同一进程,直接返回IInterface
            return ((com.fish.myapplication.IMyServer)iin);
          }
          //不同进程则返回Proxy,并传入Binder
          return new com.fish.myapplication.IMyServer.Stub.Proxy(obj);
        }
        //返回自身
        @Override public android.os.IBinder asBinder()
        {
          return this;
        }
        
        //重写onTransact(xx)
        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
          java.lang.String descriptor = DESCRIPTOR;
          switch (code)
          {
            //根据code,调用不同的方法
            case INTERFACE_TRANSACTION:
            {
              reply.writeString(descriptor);
              return true;
            }
            case TRANSACTION_say:
            {
              data.enforceInterface(descriptor);
              java.lang.String _arg0;
              //反序列化,读取数据
              _arg0 = data.readString();
              this.say(_arg0);
              reply.writeNoException();
              return true;
            }
            case TRANSACTION_tell:
            {
              data.enforceInterface(descriptor);
              java.lang.String _arg0;
              _arg0 = data.readString();
              int _arg1;
              _arg1 = data.readInt();
              int _result = this.tell(_arg0, _arg1);
              reply.writeNoException();
              //写入回复
              reply.writeInt(_result);
              return true;
            }
            default:
            {
              return super.onTransact(code, data, reply, flags);
            }
          }
        }
        private static class Proxy implements com.fish.myapplication.IMyServer
        {
          private android.os.IBinder mRemote;
          Proxy(android.os.IBinder remote)
          {
            mRemote = remote;
          }
          @Override public android.os.IBinder asBinder()
          {
            return mRemote;
          }
          public java.lang.String getInterfaceDescriptor()
          {
            return DESCRIPTOR;
          }
          @Override public void say(java.lang.String word) throws android.os.RemoteException
          {
            //构造序列化数据
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
              _data.writeInterfaceToken(DESCRIPTOR);
              //写入序列化
              _data.writeString(word);
              //mRemote为远程的IBinder
              boolean _status = mRemote.transact(Stub.TRANSACTION_say, _data, _reply, 0);
              //阻塞等待transact(xx)调用结果
              if (!_status && getDefaultImpl() != null) {
                getDefaultImpl().say(word);
                return;
              }
              _reply.readException();
            }
            finally {
              _reply.recycle();
              _data.recycle();
            }
          }
          @Override public int tell(java.lang.String word, int age) 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);
              _data.writeString(word);
              _data.writeInt(age);
              boolean _status = mRemote.transact(Stub.TRANSACTION_tell, _data, _reply, 0);
              if (!_status && getDefaultImpl() != null) {
                return getDefaultImpl().tell(word, age);
              }
              _reply.readException();
              _result = _reply.readInt();
            }
            finally {
              _reply.recycle();
              _data.recycle();
            }
            return _result;
          }
          public static com.fish.myapplication.IMyServer sDefaultImpl;
        }
        static final int TRANSACTION_say = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_tell = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        public static boolean setDefaultImpl(com.fish.myapplication.IMyServer impl) {
          if (Stub.Proxy.sDefaultImpl == null && impl != null) {
            Stub.Proxy.sDefaultImpl = impl;
            return true;
          }
          return false;
        }
        public static com.fish.myapplication.IMyServer getDefaultImpl() {
          return Stub.Proxy.sDefaultImpl;
        }
      }
      
      //声明的公共方法
      public void say(java.lang.String word) throws android.os.RemoteException;
      public int tell(java.lang.String word, int age) throws android.os.RemoteException;
    }
    

    代码不长,但是初看起来云里雾里的,如果不怎么熟悉以上内容,强烈建议先阅读上篇文章:Android IPC 之Binder应用,再看这篇就很容易了。

    提取重点分析上面的代码。
    定义接口
    定义了IMyServer 接口,该接口里的方法就是根据IMyServer.aidl里声明的方法生成的。

    两个静态类
    Stub是抽象类。
    继承了Binder,重写了onTransact(xx)方法。
    实现了IMyServer 接口,并没有实现里面的方法,这些方法待服务端实现。
    当onTransact(xx)被调用的时候,根据不同的code调用相应的方法。
    在上篇文章里分析的时候,onTransact(xx)与IMyServer 接口是分离的,我们需要手动在onTransact(xx)里调用IMyServer 方法。而此时Stub将两者结合起来了,完成了服务端与Binder驱动的联动。

    Proxy虽然没有继承自Binder,但是持有IBinder引用:mRemote。
    实现了IMyServer,并且实现了其所有方法,每个方法里最终都通过mRemote调用transact(xx)完成了客户端与Binder驱动联动。

    至此,通过这两个类,分别完成了服务端、客户端与Binder的联动。
    继续引用上篇的图:


    image.png

    可以看出,有了AIDL自动生成的类后:

    1、繁杂的switch case 不用自己编写了
    2、序列化反序列化也不用编写了
    3、不再需要编写transact(xx)与onTransact(xx)了

    极大解放了生产力。

    2、如何使用AIDL

    既然IMyServer.java 已经生成了,继续来看看如何使用它。

    编写Server端业务

    public class MyService extends Service {
    
        private final String TAG = "IPC";
    
        //构造内部类
        private IMyServer.Stub stub = new IMyServer.Stub() {
            @Override
            public void say(String word) throws RemoteException {
                Log.d(TAG, "receive say content:" + word + " in server");
            }
    
            @Override
            public int tell(String word, int age) throws RemoteException {
                Log.d(TAG, "receive tell content:" + word + " age:" + age + " in server");
                return age + 1;
            }
        };
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            //Stub 继承自Binder,因此是IBinder类型
            return stub;
        }
    }
    

    首先实现业务接口。
    其次在onBind(xx)里将Binder返回给客户端。
    业务逻辑实现了,等待客户端调用。

    编写客户端业务

    先定义ServiceConnection:

        ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IMyServer iMyServer = IMyServer.Stub.asInterface(service);
                try {
                    iMyServer.say("how are you?");
                    int result = iMyServer.tell("how are you?", 18);
                    Log.d("IPC", "receive return content:" + result + " in client");
                } catch (Exception e) {
    
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    

    service 为IBinder引用,该引用从服务端经过Binder驱动传递而来(不一定是同一个引用)。
    IMyServer.Stub.asInterface(service) 用来寻找该IBinder对应的服务端提供的接口。

    1、当IBinder与调用者同一进程,则IBinder为Binder类型,即为自身定义的Stub。
    2、当IBinder与调用者不是同一进程,则IBinder为BinderProxy类型(为什么是这个类型,后续文章会分析)。

    此处测试的是两个不同的进程,因此IBinder service指向BinderProxy。
    在ServiceConnection里,当绑定成功后调用Proxy里的方法,其内部通过BinderProxy调用transact(xx)。

    上面的逻辑都写了,最后当然需要绑定Service:

        private void bindService() {
            Intent intent = new Intent(this, MyService.class);
            bindService(intent, serviceConnection, BIND_AUTO_CREATE);
        }
    

    来看看打印结果:


    image.png

    可以看出通信成功了。

    需要注意的是:

    以上Demo都是同一个工程里编写的,因此客户端、服务端都能访问IMyServer.Stub,若是在不同的进程,需要写同样的IMyServer.aidl文件。

    让Service在不同的进程运行只需要在AndroidManifest.xml添加如下字段:

            <service android:name=".MyService" android:process=":aidl">
            </service>
    

    绑定后,运行的两个进程如下:


    image.png

    总结AIDL用法

    通过以上Demo可以看出,通过编写AIDL实现IPC。
    服务端仅需要两步:
    第一步
    实现接口对应方法的业务逻辑

    第二步
    在onBind(xx)里将接口关联的Binder返回

    同样的客户端调用服务端仅仅只需要两步:
    第一步
    通过Stub拿到服务端的接口

    第二步
    拿到接口后调用对应的方法

    image.png

    明显的,IMyServer.java 已经为我们实现了连接Binder的功能,屏蔽了对接的Binder细节。客户端调用服务端的方法(Proxy)与服务端进行通信,就像是"直接"调用一般,符合Java一贯的面向对象的思维。

    结合上一篇文章对Binder应用的分析,以及本篇AIDL的分析,我们知道:

    • AIDL 并不是我们熟知的Java、C++语言,而是一种规范。按此规范编写的AIDL文件最终生成对应的.java文件,该文件里实现了客户端调用transact(xx)以及调用服务端的onTransact(xx),通过.java文件就能实现进程间通信。
    • .java文件里将工作分为了两部分:一是客户端的逻辑封装在Proxy里,而服务端的逻辑封装在Stub里,典型的Proxy-Stub(代理-桩)模式。
    • 进程间通信的核心是Binder,AIDL本身并不能实现进程间通信,仅仅是简化了编码的流程。

    接下来将重点分析AIDL 传递自定义数据类型以及定向Tag相关问题。

    本文基于Android 10.0。

    您若喜欢,请点赞、关注,您的鼓励是我前进的动力

    持续更新中,和我一起步步为营学习Android

    1、Android各种Context的前世今生
    2、Android DecorView 必知必会
    3、Window/WindowManager 不可不知之事
    4、View Measure/Layout/Draw 真明白了
    5、Android事件分发全套服务
    6、Android invalidate/postInvalidate/requestLayout 彻底厘清
    7、Android Window 如何确定大小/onMeasure()多次执行原因
    8、Android事件驱动Handler-Message-Looper解析
    9、Android 键盘一招搞定
    10、Android 各种坐标彻底明了
    11、Android Activity/Window/View 的background
    12、Android Activity创建到View的显示过
    13、Android IPC 系列
    14、Android 存储系列
    15、Java 并发系列不再疑惑
    16、Java 线程池系列

    相关文章

      网友评论

        本文标题:Android IPC 之AIDL应用(上)

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