Android IPC之AIDL详解

AIDL基本介绍

  1. AIDL的概念

    AIDL,全称是Android Interface Definition Language,也就是Android接口定义语言。所以AIDL也可以算作一种编程语言,它对应的文件以.aidl结尾。

    这门语言的设计主要是为了进程间的通讯。这个语言其实是一个模版,通过这个模版,系统会为我们 生成一个Binder文件,这个文件才是进程间通讯的关键。 我们可以完全不使用aidl文件去直接写Binder来完成进程间通讯。不过那样写起来重复代码太多而且比较费时。所以一般都是通过aidl文件来让系统生成对应的Binder

  2. AIDL支持的数据类型

    在AIDL文件中,并不是所有的文件都可以使用,所以大家需要知道AIDL文件支持的数据类型包括哪些,这里给大家列出来:

    • 基本的数据类型(byte、short、int、long、char、boolean、float、double)
    • String和CharSequence
    • List:支持ArrayList且其中的元素都是AIDL支持的类型
    • Map:支持HashMap且其中的key、value都是AIDL支持的类型
    • 所有实现了Parcelable接口的对象
    • AIDL接口本身

    以上就是AIDL所有支持的数据类型,其中每个Parcelable类型的对象必须在文件中导入才能使用。因为AIDL是跨进程的,所以即使在同一个包中,Parcelable也需要导入。具体的实现可以参考第四步。

  3. AIDL的语法

    AIDL文件可以分为两类,我们下面来举例查看:

    • 用来说明Parcelable类的文件

      每个在AIDL中用到的Parcelable的类都需要一个和类名相同的申明文件。文件以.aidl结尾,文件内容如下:

      // 文件名是:Book.aidl
      package com.wscq.aidltest.aidl;
      
      //aidl中用到了实现了序列化的类Book,所以这里需要申明一下
      parcelable Book;
    • 用来定义接口调用的文件

      //文件名是IBookManager
      package com.wscq.aidltest.aidl;
      //这里需要导入使用到的Book类,即使在同一个包中也需要导入
      import com.wscq.aidltest.aidl.Book;
      
      interface IBookManager {
       List<Book> getBookList();
       //添加书的接口
       void addBook(in Book book);
      }

      我们可以注意到,在addBook()这一个方法中,有个in这是AIDL语言中特有的部分,与之对应的还有outinout,我们这里对着三个字段代表的意思来做一下说明:

      • in:数据只能由客户端流向服务端,服务端对此数据修改不影响客户端。
      • out:服务端会收到一个空对象,客户端的对象不会传递过去。但是服务端对这个对象的任何修改,客户端都会被修改。
      • inout:类似于java的普通传参,服务端修改参数客户端对应的值也会被修改。

      以上三个值称为AIDL中的定向Tag,基本类型的tag默认为in,且只能为in。我们不能为了方便而统一使用inout,这样会造成过大的系统开销。而要根据对应的需求选取合适的tag。

  4. 在AndroidStudio建立aidl文件

    首先如图所示新建对应的文件:

    AIDL新建

    注意红框中的目录结构,这个还是比较坑的。新建aidl时候,会在main下建立一个aidl文件夹,里面会在对应包名先建立aidl文件。 而自己的序列化类Book.java和序列化类的申明Book.aidl要分别放在java目录对应的位置和aidl目录对应的位置。否则编译会不通过。

系统生成文件——Binder分析

  1. 什么是Binder

    直观的来说,Binder是Android中的一个类,它实现了IBinder接口。同时Binder也是Android中的一种跨进程通讯方式。在我的前几篇文章中的流程图中,Binder一般会被绘制为一个中间层,所以Binder也可以被理解为一种虚拟的物理设备,用来在两个进程中间传递消息。

  2. Binder的作用

    Binder具体的作用是这样的:当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个对象,客户端就可以获取服务端提供的服务或者数据,这里的服务既可以是普通的服务也可以是基于AIDL的服务。

    在Android中,Binder主要用在Service中,包括AIDL和Messenger的底层实现。其中普通Service的Binder不涉及进程间通讯,较为简单。这里主要通过AIDL生成的Binder来分析一下Binder的作用。

  3. 由aidl生成的Binder文件分析

    这里我们的AIDL文件使用上面AIDL语法中的文件。在AndroidStudio中AIDL生成的Binder文件位于工程对应的/build/generated/source/aidl/debug/目录下,具体的包名和AIDL的包名对应,类名与.aidl类名对应,不过生成的是.java文件。

    来看一下这个类的整体结构。这个类继承了IInterface接口,同时这个类也是一个接口。这个接口申明了两个方法,也就是IBookManager.aidl中的方法。然后声明了一个内部类Stub。这个Stub就是一个Binder类。在Stub内部还有个代理类Proxy,在跨进程通讯中个,它会是客户端的代理方法。

    public interface IBookManager extends IInterface {
    //声明内部类Stub,这个就是一个Binder类。
       public static abstract class Stub extends Binder implements IBookManager {
        //Binder的唯一标识,一般用当前Binder的类名表示
           private static final String DESCRIPTOR = "com.wscq.aidltest.aidl.IBookManager";
    
           //...
    
           //客户端的代理类Proxy
           private static class Proxy implements IBookManager {
               //...
           }
        //这两个整型的ID用于标识在跨进程调用中。客户端到底调用的是哪个方法
           static final int TRANSACTION_getBookList = (FIRST_CALL_TRANSACTION + 0);
           static final int TRANSACTION_addBook = (FIRST_CALL_TRANSACTION + 1);
       }
    //声明两个方法,也就是IBookManager.aidl中的方法
       public List<Book> getBookList() throws RemoteException;
       public void addBook(Book book) throws RemoteException;
    }

    这个类的大体结构也就如上图所示。具体每个类的实现细节,可以参考我的另一篇文章

AIDL的具体实现

  1. 具体要实现的需求

    这个例子主要是实现如下功能:

    • 客户端:包含一个输入框、一个添加按钮和一个内容展示区。点击添加,会调用服务端端代码添加书本信息,此时等待服务端的回调,收到回调后显示当前服务端的书籍列表到界面。
    • 服务端:收到客户端的添加请求后,把书加入到集合,返回添加成功的消息到客户端。
  2. AIDL文件实现(源码

    这里AIDL包含三个.aidl文件和一个Book的Parcelable对象。

    // 文件名是:Book.aidl
    package com.wscq.aidltest.aidl;
    
    //aidl中用到了实现了序列化的类Book,所以这里需要申明一下
    parcelable Book;
    //IBookManager.aidl类
    package com.wscq.aidltest.aidl;
    import com.wscq.aidltest.aidl.Book;
    import com.wscq.aidltest.aidl.IOnNewBookArrivedListener;
    
    interface IBookManager {
       List<Book> getBookList();
       void addBook(in Book book);
       //这两个方法用来在客户端绑定和解绑服务端
       void registerListener(IOnNewBookArrivedListener listener);
       void unregisterListener(IOnNewBookArrivedListener listener);
    }
    //IOnNewBookArrivedListener.aidl
    package com.wscq.aidltest.aidl;
    import com.wscq.aidltest.aidl.Book;
    
    interface IOnNewBookArrivedListener {
       //服务端用来通知客户端新书添加成功
       void onNewBookArrived(in Book newBook);
    }
    //实现了Parcelable的类,为了能够在aidl中传递
    public class Book implements Parcelable {
       public int bookId;
       public String bookName;
    
       public Book(int bookId, String bookName) {
           this.bookId = bookId;
           this.bookName = bookName;
       }
    }
  3. 客户端实现(源码
    1. 首先我们需要绑定服务端,而绑定服务端就需要ServiceConnection类,所以先实例化ServiceConnection

      private ServiceConnection mConnection = new ServiceConnection() {
        // 服务连接后
        @Override
        //服务连接以后执行此方法
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 通过aidl中的方法,把Binder转化为AIDL本身
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                //赋值给全局变量,方便随时通过他来获取服务端信息
                mRemoteBookManager = bookManager;
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
      
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteBookManager = null;
            Log.i(TAG, "binder died");
        }
      };
    2. 绑定服务端时,需要注册服务端添加新书的监听,所以需要实例化IOnNewBookArrivedListener,同时用一个Handler来处理显示服务端对应的书籍信息等操作。

      private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
      };
      
      private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.d(TAG, "receive new book:" + msg.obj);
                    try {
                        //这里更新显示的书籍信息。也就是TextView.setText()
                        updateContent(mRemoteBookManager.getBookList());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
      };
    3. 一切准备就绪,这时我们可以在onCreate()方法中调用绑定服务端的方法了

      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById();
        setListener();
        //通过intent来和服务端实现绑定
        bindService();
      
      }
      //绑定服务端
      private void bindService() {
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
      }
    4. 最后记得在onDestory()中与服务端解绑

      @Override
      protected void onDestroy() {
        super.onDestroy();
        //解绑服务端
        unbindService(mConnection);
      }
  4. 服务端实现(源码)
    1. RemoteCallbackList介绍

      RemoteCallbackList是系统专门提供的,用于删除跨进程listener的接口。支持任意的AIDL接口。这个利用的是跨进程对象在底层的Binder对象是同一个,通过这个同一个对象,我们就可以找出要解注册的客户端,并把它删除。

    2. 实现对应的Binder

      private Binder mBinder = new IBookManager.Stub() {
      
        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.d(TAG, "service_onCreate()");
            return mBookList;
        }
      
        @Override
        public void addBook(Book book) throws RemoteException {
            Log.d(TAG, "service_onCreate()");
            onNewBookArrived(book);
        }
      
        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
        }
      
        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
      };
    3. 添加书籍是,回调客户端方法的实现

      /**
      * 当有新书添加的时候,服务端通过aidl调用客户端,然后显示添加新书成功,如果此处是耗时操作,需要通过handler来执行
      */
      private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
            if (l != null) {
                try {
                    l.onNewBookArrived(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        // 这个要和beginBroadcast配对使用,就算仅仅是获取个数
        mListenerList.finishBroadcast();
      }

小结

到这里,这个AIDL的分析就结束了,这边文章主要参考了《Android开发艺术探索》一书以及网上一些其他作者的观点。这篇文章比较长,感谢大家能够把它看完。最后如果文章中有什么错误请及时在评论中指出。


相关文章:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值