AIDL基本介绍
AIDL的概念
AIDL,全称是
Android Interface Definition Language
,也就是Android接口定义语言。所以AIDL也可以算作一种编程语言,它对应的文件以.aidl结尾。这门语言的设计主要是为了进程间的通讯。这个语言其实是一个模版,通过这个模版,系统会为我们 生成一个Binder文件,这个文件才是进程间通讯的关键。 我们可以完全不使用aidl文件去直接写Binder来完成进程间通讯。不过那样写起来重复代码太多而且比较费时。所以一般都是通过aidl文件来让系统生成对应的Binder
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也需要导入。具体的实现可以参考第四步。
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语言中特有的部分,与之对应的还有out
和inout
,我们这里对着三个字段代表的意思来做一下说明:- in:数据只能由客户端流向服务端,服务端对此数据修改不影响客户端。
- out:服务端会收到一个空对象,客户端的对象不会传递过去。但是服务端对这个对象的任何修改,客户端都会被修改。
- inout:类似于java的普通传参,服务端修改参数客户端对应的值也会被修改。
以上三个值称为AIDL中的定向Tag,基本类型的tag默认为in,且只能为in。我们不能为了方便而统一使用
inout
,这样会造成过大的系统开销。而要根据对应的需求选取合适的tag。
在AndroidStudio建立aidl文件
首先如图所示新建对应的文件:
注意红框中的目录结构,这个还是比较坑的。新建aidl时候,会在main下建立一个aidl文件夹,里面会在对应包名先建立aidl文件。 而自己的序列化类
Book.java
和序列化类的申明Book.aidl
要分别放在java目录对应的位置和aidl目录对应的位置。否则编译会不通过。
系统生成文件——Binder分析
什么是Binder
直观的来说,Binder是Android中的一个类,它实现了IBinder接口。同时Binder也是Android中的一种跨进程通讯方式。在我的前几篇文章中的流程图中,Binder一般会被绘制为一个中间层,所以Binder也可以被理解为一种虚拟的物理设备,用来在两个进程中间传递消息。
Binder的作用
Binder具体的作用是这样的:当
bindService
的时候,服务端会返回一个包含了服务端业务调用的Binder
对象,通过这个对象,客户端就可以获取服务端提供的服务或者数据,这里的服务既可以是普通的服务也可以是基于AIDL的服务。在Android中,Binder主要用在Service中,包括AIDL和Messenger的底层实现。其中普通Service的Binder不涉及进程间通讯,较为简单。这里主要通过AIDL生成的Binder来分析一下Binder的作用。
由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的具体实现
具体要实现的需求
这个例子主要是实现如下功能:
- 客户端:包含一个输入框、一个添加按钮和一个内容展示区。点击添加,会调用服务端端代码添加书本信息,此时等待服务端的回调,收到回调后显示当前服务端的书籍列表到界面。
- 服务端:收到客户端的添加请求后,把书加入到集合,返回添加成功的消息到客户端。
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; } }
客户端实现(源码)
首先我们需要绑定服务端,而绑定服务端就需要
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"); } };
绑定服务端时,需要注册服务端添加新书的监听,所以需要实例化
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); } } };
一切准备就绪,这时我们可以在
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); }
最后记得在
onDestory()
中与服务端解绑@Override protected void onDestroy() { super.onDestroy(); //解绑服务端 unbindService(mConnection); }
服务端实现(源码)
RemoteCallbackList
介绍RemoteCallbackList
是系统专门提供的,用于删除跨进程listener的接口。支持任意的AIDL接口。这个利用的是跨进程对象在底层的Binder对象是同一个,通过这个同一个对象,我们就可以找出要解注册的客户端,并把它删除。实现对应的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); } };
添加书籍是,回调客户端方法的实现
/** * 当有新书添加的时候,服务端通过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开发艺术探索》一书以及网上一些其他作者的观点。这篇文章比较长,感谢大家能够把它看完。最后如果文章中有什么错误请及时在评论中指出。
相关文章: