AIDL:Android Interface Definition Language。Android接口定义语言,是Android为方便进程间通信而设计的一门语言。
Androi进程间通信除却AIDL还有多种方式,如: 1.Bundle/Intent传递数据 2.ContentProvider 3.文件/数据库 4.Socket 5.BroadcastReceiver 6.Binder
如果对Android进程比较了解的人肯定会质疑说我遗漏了Messenger,是的但是我想说Messenger就是基于AIDL实现的。
7.Messenger
那疑问又来了,既然已经有了这么多通信方式为什么还要设计AIDL?或者说既然已经有了Messenger为什么还要使用AIDL呢?
AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理。 而Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。
接下来以代码为例,详细讲解AIDL.
Android Studio下新建一个项目(Moudle或者Proiect),本文新建的是Moudle。 鼠标点击项目名—->new—->AIDL—->AIDL file.会自动在src下生成aidl文件夹及对应的包。 项目结构如下:
如果AIDL中传递的是基本的数据类型,则不需要这个步骤。本文为了演示效果,传递JavaBean对象。
// Book.aidl package com.demon.aidlservice; //一句代码,声明Book为parcelable类型 parcelable Book;定义基本数据变量,完成构造方法和setter/getter。 然后implements Parcelable,Android studio 直接alt+enter就可自动生成所需代码。 最后手动实现readFromParcel(Parcel dest),如代码所示即可。
public class Book implements Parcelable { private int id; private String name; public Book() { } public Book(int id, String name) { this.id = id; this.name = name; } protected Book(Parcel in) { id = in.readInt(); name = in.readString(); } public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(id); parcel.writeString(name); } /** * 参数是一个Parcel,用它来存储与传输数据 * 必须实现用于自动生成BookManger中调用 * @param dest */ public void readFromParcel(Parcel dest) { //注意,此处的读值顺序应当是和writeToParcel()方法中一致的 id = dest.readInt(); name = dest.readString(); } }用于管理传递的数据。
package com.demon.aidlservice; // 必须是完成的包名 import com.demon.aidlservice.Book; interface BookManger { List<Book> getBooks(); //in out inout可以指定数据的传递方向 void addBook(inout Book book); }由于Android默认.java文件只能在java文件目录下编译,为了是Book.java在aidl文件编译通过必须在gradle中添加如下代码。
android { ..... sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } } ..... }PS:必须先编译通过,才能继续编写服务端代码。 编译的过程中会自动在generated—source—aidl—debug—-包名文件夹下生成BookManger.java。这样服务端才能调用BookManger对象。 此时若编译通过,程序能运行起来。就说明没有问题,接下来编写服务端代码即可。
核心是重写BookManger.Stub方法用于建立一个Binder对象,用于传递数据。可见AIDL的实现部分也使用了Binder机制。然后再重写数据处理方法,对需要传递的数据进行处理就行了。其余代码比较简单,就不介绍了。
public class MyService extends Service { private static final String TAG = "MyService"; List<Book> bookList = new ArrayList<>(); private BookManger.Stub mBinder = new BookManger.Stub() { @Override public List<Book> getBooks() throws RemoteException { return bookList; } @Override public void addBook(Book book) throws RemoteException { book.setId(100); bookList.add(book); } }; @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate: "); Book book = new Book(1, "Java"); bookList.add(book); } @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind: "); return mBinder; } }至于MainActivity,因为我们把这个程序当做一个服务端程序,所以MainActivity什么内容都可以不需要。编译运行,如果没问题,接下来编写客户端程序即可。
客户端程序项目目录结构如下:
直接将服务端的AIDL相关的整个文件夹复制过来即可,注意AIDL所在的包名必须与服务端的相同,切勿修改。 注意: java.lang.SecurityException: Binder invocation to an incorrect interface 如果遇到如上异常,属于进程间通信异常,很可能是因为服务端与客户端的AIDL包名不一致所导致。
为了使.java在AIDL文件夹中通过编译,需要修改项目gradle文件,如同上面第4步即可。
然后编译运行,如果没问题,直接继续。
根据需求直接编写Activity的事务,本文仅为了演示,故代码很简单,如下:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private Button aidl; private BookManger manger;//获取AIDL对象 private List<Book> bookList = new ArrayList<>(); private boolean flag; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, "onCreate: "); setContentView(R.layout.activity_main); aidl = (Button) findViewById(R.id.aidl); aidl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (!flag) { BindService(); } for (int i = 0; i < bookList.size(); i++) { Book book = bookList.get(i); Log.i(TAG, "onClick: " + book.getId() + "," + book.getName()); } } }); } @Override protected void onStart() { super.onStart(); Log.i(TAG, "onStart: "); BindService(); } /** * 连接服务端 */ private void BindService() { Intent intent = new Intent(); intent.setAction("com.demon.aidlservice.MyService"); //Android5.0后隐式调用,必须调用此方法,service所在的包名 intent.setPackage("com.demon.aidlservice"); bindService(intent, connection, BIND_AUTO_CREATE); } private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { Log.i(TAG, "onServiceConnected: 服务器已连接!"); manger = BookManger.Stub.asInterface(iBinder); Book book = new Book(2, "Java"); try { manger.addBook(book); bookList = manger.getBooks(); } catch (RemoteException e) { e.printStackTrace(); } flag = true; } @Override public void onServiceDisconnected(ComponentName componentName) { Log.i(TAG, "onServiceDisconnected: 服务器连接断开!"); flag = false; } }; @Override protected void onDestroy() { super.onDestroy(); if (flag) { unbindService(connection); flag = false; } } }其实代码与正常的service没有太大的区别,主要是在AIDL提供的接口上进行操作。
结果符合预期。
AIDL是一门跨进程Client——Service间通信机制语言,没有熟练掌握前还是比较抽象的。 最好还是自己能写一个demo走一下流程,就会很清晰。
下载:http://download.csdn.net/detail/demonliuhui/9923204 不想下载,只看代码的点这里,GitHub: AIDLClient:https://github.com/DeMonLiu623/DeMonTest/tree/master/AIDLClient AIDLService:https://github.com/DeMonLiu623/DeMonTest/tree/master/AIDLService