一.概述
Android跨进程通信的方式有Bundle,文件共享,Messenger,AIDL,socket.其中Messenger底层也是用到了AIDL.所以理解AIDL的使用以及原理,对于我们去理解进程间通信是有很大的帮助。今天,就AIDL的使用做一个详细的介绍。
二.支持的数据类型
- 八种基本数据类型:byte、char、short、int、long、float、double、boolean;
- String,CharSequence;
- 实现了Parcelable接口的数据类型;
- List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象;
- Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象;
三.服务端编码
这里使用一个例子来说明AIDL的使用,需要实现的功能是:客户端通过绑定服务端的Service的方式来调用服务端的方法,获取服务端的书籍列表并向其添加书籍,实现应用间的数据共享
服务端的代码如下:
新建一个项目,项目的包名为:com.demo.aidlservice
接着右键新建AIDL文件,命名为Book.创建完成后,系统会自动创建一个aidl文件夹,在里面找到了我们刚才创建的Book.aidl文件,Book.aidl有一个默认的方法,可以删除。
然后我们来定义Book实体类,Book类有bookId和bookName两个属性,并且让这个类实现Parcelable 接口
package com.demo.aidlservice;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = 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];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
接着,修改Book.aidl文件,声明Book类为parcelable
// Book.aidl
package com.demo.aidlservice;
parcelable Book;
// Declare any non-default types here with import statements
然后,我们再创建一个IBookManager.aidl文件,里面定义了getBookList和addBook两个方法,这里需要注意,我们需要手动去导入包名。
// IBookManager.aidl
package com.demo.aidlservice;
import com.demo.aidlservice.Book;
// Declare any non-default types here with import statements
interface IBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List<Book> getBookList();
void addBook(in Book book);
}
然后,我们需要去clean下工程,使系统生成我们需要的文件,真正起到进程间通信的是系统帮我生成的这个文件。这个文件里面有内部静态抽象类 Stub,后面需要用到来生成Binder对象。
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\maguifeng\\android-code\\AIDLService\\app\\src\\main\\aidl\\com\\demo\\aidlservice\\IBookManager.aidl
*/
package com.demo.aidlservice;
// Declare any non-default types here with import statements
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.demo.aidlservice.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.demo.aidlservice.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.demo.aidlservice.IBookManager interface,
* generating a proxy if needed.
*/
public static com.demo.aidlservice.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.demo.aidlservice.IBookManager))) {
return ((com.demo.aidlservice.IBookManager)iin);
}
return new com.demo.aidlservice.IBookManager.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@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)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(descriptor);
java.util.List<com.demo.aidlservice.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(descriptor);
com.demo.aidlservice.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.demo.aidlservice.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.demo.aidlservice.IBookManager
{
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;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public java.util.List<com.demo.aidlservice.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.demo.aidlservice.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.demo.aidlservice.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.demo.aidlservice.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public java.util.List<com.demo.aidlservice.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.demo.aidlservice.Book book) throws android.os.RemoteException;
}
介绍完了如何定义aidl接口。现在现在需要来创建一个 Service 供客户端远程绑定了,命名为BookManagerService
package com.demo.aidlservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import java.util.ArrayList;
import java.util.List;
/**
* <pre>
*
* Created by MarvinMa
* Time : 2019/5/8-9:50
* Desc :
* </pre>
*/
public class BookManagerService extends Service {
private List<Book> bookList = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
initData();
}
private void initData() {
bookList.add(new Book(1,"语文"));
bookList.add(new Book(2,"数学"));
bookList.add(new Book(3,"英语"));
}
private IBookManager.Stub binder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return bookList;
}
@Override
public void addBook(Book book) throws RemoteException {
if(book != null){
bookList.add(book);
}
}
};
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
客户端要找到服务端的Service,需要指定服务端的包名,还要设置action
<service android:name=".BookManagerService">
<intent-filter>
<action android:name="com.demo.aidlservice.BookManagerService"></action>
</intent-filter>
</service>
至此,服务端的代码编写完毕
四.客户端编码
客户端需要再创建一个新的工程,包名命名为 com.demo.aidlclient
首先,我们需要把服务端的aidl接口和Book类完整复制过来,这里注意服务端包名和客户端的包名要保持一致。
然后,我们在客户端的MainActivity的布局新增两个按钮,获取和新增书籍按钮,分别向服务端获取数据以及新增数据。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.demo.aidlclient.MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/btn_getBookList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取书籍" />
<Button
android:id="@+id/btn_addBook"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加书籍" />
</LinearLayout>
package com.demo.aidlclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.demo.aidlservice.Book;
import com.demo.aidlservice.IBookManager;
import com.demo.aidlservice.R;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<Book> bookList;
private IBookManager iBookManager;
private boolean isConnect;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
findViewById(R.id.btn_getBookList).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(isConnect){
try {
bookList = iBookManager.getBookList();
} catch (RemoteException e) {
e.printStackTrace();
}
logUtils();
}
}
});
findViewById(R.id.btn_addBook).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isConnect){
try {
iBookManager.addBook(new Book(4,"这是客户端向服务端添加的书籍化学"));
} catch (RemoteException e) {
e.printStackTrace();
}
logUtils();
}
}
});
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookManager = IBookManager.Stub.asInterface(service);
isConnect = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
isConnect = false;
}
};
private void bindService() {
Intent intent = new Intent();
intent.setPackage("com.demo.aidlservice");
intent.setAction("com.demo.aidlservice.BookManagerService");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private void logUtils() {
for (Book book : bookList) {
Log.e("MainActivity", book.bookId + "/" + book.bookName);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(isConnect){
unbindService(serviceConnection);
}
}
}
最后通过日志来观察服务端和客户端的数据变化
五.总结
总结下AIDL使用:服务端首先要创建一个Service用来监听客户端的请求,然后创建一个AIDL文件,声明暴露给客户端的接口,最后在Service中实现这个aidl即可。客户端首先要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所需要的类型。接着调用AIDL中的方法。