本博客以此文Android:学习AIDL,这一篇文章就够了(上)作为参考,这篇文章给予我很大的帮助,非常感谢。
首先介绍一下跨进程通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法。下面是维基百科的介绍:
进程是计算机系统分配资源的最小单位(严格说来是线程)。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。通常,使用进程间通信的两个应用可以被分为客户端和服务器(见主从式架构),客户端进程请求数据,服务端响应客户端的数据请求。有一些应用本身既是服务器又是客户端,这在分布式计算中,时常可以见到。这些进程可以运行在同一计算机上或网络连接的不同计算机上。
在安卓中,我们可以通过AIDL来完成跨进程(不同的应用间)的通信。AIDL全称是Android Interface Definition Language,也就是接口定义语言。正如上面提到的,我们之所以需要IPC是因为一个应用进程通常无法访问另一个应用进程的内存,为了能够在进程间进行通信,进程需要将其对象分解成可供操作系统理解的原语,并将其编组为可供操作的对象,这也正是AIDL存在的原因。下面将介绍AIDL的相关概念和具体用法:
1. AIDL的语法:
它的语法与java类似,不过有如下区别:
(1)文件类型是aidl而不是java;
(2)支持的数据类型:基本类型(byte、short、int、long、double、float、boolean、char)以及String和CharSequence;
(3)List集合,其中的元素必须是AIDL支持的类型之一,或者是其他AIDL生成的接口,或者是parcelable可序列化对象;同时支持泛型;
(4)Map对象,与List一样,但是不支持泛型;
(5)定向tag:取值有三种in、out、inout,各自含义如下:
(注:String和CharSequence类型的参数只能使用in修饰,而且不要滥用inout,这可能会造成过多的系统开销)
2. AIDL文件
AIDL的文件大概可以分为两种类型:
(1)定义可序列化的实现Parcelable接口的数据类(可扩展AIDL传递参数所支持的数据类型);
(2)定义客户端可调用的,由服务端来实现的方法接口(类似于接口,并不涉及具体的实现);
介绍完相关概念后,下面将使用一个小例子来实现跨进程(应用)调用方法进行通信的完整过程。
3. 具体用法
大概可以分为如下步骤:
(1)创建用于跨进程传递的数据模型类;
(2)生成对应的aidl文件,包括两个文件(一个用于引入序列化对象供其他AIDL文件使用,另一个用于声明待实现的方法接口列表);
(3)服务端实现待调用的方法;
(4)向客户端公开接口;
(5)通过IPC传递对象;
3.1 定义数据模型类
首先在作为客户端的应用AidlClientDemo项目中定义如下文件:
package com.zjhtest.aidlclient;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class Book implements Parcelable {
private String name;
private int price;
public Book(String name, int price) {
this.name = name;
this.price = price;
}
protected Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
public static final Creator CREATOR = new Creator() {
@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;
}
/**
* 如果仅重写writeToParcel方法,那么Book类的实例化对象在AIDL中仅会支持为in的定向tag
*/
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeInt(price);
}
/**
* 实现readFromParcel方法,那么Book类的实例化对象在AIDL中还可以支持为out及inout的定向tag
* @param dest Parcel对象,用于存储和传输数据
*/
public void readFromParcel(Parcel dest) {
name = dest.readString();
price = dest.readInt();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@NonNull
@Override
public String toString() {
return "Book{ " +
"name: " +
name +
", price: " +
price +
" }";
}
}
上面的代码中,我们定义了一个Book类,并实现了Parcelable接口,使其为可序列化的。这里需要注意的一点是,我们如果想要实例化的book对象能够声明为out或者是inout,需要实现readFromParcel方法。
3.2 生成对应的aidl文件
在Android Studio中,我们可以通过在app文件夹上右键点击New,再选中AIDL File进行创建。
Book.aidl:
// Book.aidl
// 这个文件的作用是引入了一个序列化对象Book供其他的AIDL文件使用
package com.zjhtest.aidlclient;
parcelable Book;
BookManager.aidl文件:
// BookManager.aidl
package com.zjhtest.aidlclient;
import com.zjhtest.aidlclient.Book;
interface BookManager {
List getBooks();
void addBook(in Book book);
}
上面声明的getBooks和addBook方法就是服务端需要实现的方法;
此时项目的结构如下:
注意aidl目录和java目录处于同级目录下,而且具有同样的包路径。
3.3 服务端实现待调用的方法
在实现待调用的方法之前,先要将3.1和3.2在客户端中定义的Book类和对应的Book.aidl以及BookManager.aidl文件拷贝到AidlServerDemo中。同时必须保证包路径与客户端完全一致(com.zjhtest.aidlclient),否则会出现如下异常:
java.lang.IllegalStateException: Could not execute method of the activity
接着需要创建一个服务类AIDLService,在这个服务中需要实现待调用方法并返回一个IBindler实例供客户端使用(客户端正式通过这个IBinder实例调用服务端的方法)。具体实现如下:
package com.zjhtest.aidlclient;
......
public class AIDLService extends Service {
private static final String TAG = "AIDLService";
private List mBooks = new ArrayList<>();
private final BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public List getBooks() throws RemoteException {
synchronized (this) {
Log.d(TAG, "invoking server getBook method, now the list is " + mBooks);
if (mBooks != null) {
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (this) {
if (book == null) {
Log.d(TAG, "Book is null from client (In)");
book = new Book("defaultBook", 88);
}
if (!mBooks.contains(book)) {
mBooks.add(book);
}
Log.d(TAG, "invoking server addBook method, now the list is " + mBooks);
}
}
};
@Override
public void onCreate() {
super.onCreate();
Book initBook = new Book("initBook", 22);
mBooks.add(initBook);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, String.format("onBind execute, intent is %s", intent.toString()));
return mBookManager;
}
}
上面的代码中做了如下事情:
(1)onCreate在服务创建时初始化一个Book实例,并保存到mBooks集合中;
(2)实现根据自定义接口生成的BookManager.Stub,并实例化得到mBookManager(这个对象就是一个IBinder实例);
(3)重写的onBind方法中返回mBookManager,供客户端使用;
接下来需要到AndroidManifest.xml文件中注册这个服务:
上面需要注意两点:
(1)如果我们希望这个服务能够被外部进程所调用,android:exported属性必须设置为true;
(2)必须在intent-filter中指定自定义的action字符串常量,这个action字符串常量在客户端应用AidlClientDemo中构建启动服务的Intent中会用到;
之后我们就需要启动该服务,在MainActivity中:
package com.zjhtest.aidlclient;
......
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity Server";
private boolean mBound = false;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "AIDLService is connected");
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.d(TAG, "AIDLService is disconnected");
mBound = false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Intent intent = new Intent(this, AIDLService.class);
// startService(intent);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
Intent intent = new Intent(this, AIDLService.class);
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// Intent intent = new Intent(this, AIDLService.class);
// stopService(intent);
}
}
需要注意在服务端应用启动服务AIDLService时,必须通过bindService的方式,不能通过startService,熟悉服务的同学应该都知道,startService启动的服务,只会执行onCreate方法,并不会执行onBind方法,这样也就无法生成IBinder实例返回给客户端了。
这样AIDLServiceDemo服务端应用的代码基本就完成了,下面是项目结构:
3.3 通过IPC传递对象
接下来,客户端应用功能AIDLClientDemo可以指定启动服务端应用AIDLServerDemo的AIDLService服务,并跨进程调用其实现的getBooks和addBook方法。回到我们的AIDLClientDemo应用中,在MainActivity中启动该服务:
package com.zjhtest.aidlclient;
......
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity Client";
private static final String AIDL_SERVER_PACKAGE = "com.zjhtest.aidlserver"; // 跨进程调用目标进程的唯一包名
private static final String AIDL_SERVER_SERVICE_ACTION = "com.zjhtest.aidlserver.service"; // 跨进程调用目标进程的服务action
// 由AIDL文件生成的BookManager接口
private BookManager mBookManager;
private boolean mBound = false;
private List mBooks;
private Button mAddBookBtn;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "client: service connected");
mBound = true;
mBookManager = BookManager.Stub.asInterface(iBinder);
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks(); // 跨进程调用com.zjhtest.aidlserver应用的getBooks方法
Log.d(TAG, "client: get books is " + mBooks);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.d(TAG, "client: service disconnected");
mBound = false;
}
};
private void addBook() {
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return ;
}
if (mBookManager == null) {
return ;
}
Book book = new Book("Android权威编程指南", 98);
try {
mBookManager.addBook(book); // 跨进程调用com.zjhtest.aidlserver应用的addBook方法
Log.d(TAG, "client: newBook is " + book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void attemptToBindService() {
Intent intent = new Intent();
intent.setPackage(AIDL_SERVER_PACKAGE);
intent.setAction(AIDL_SERVER_SERVICE_ACTION);
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAddBookBtn = findViewById(R.id.addBookBtn);
mAddBookBtn.setOnClickListener(this);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
Log.d(TAG, "attemptToBindService execute");
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.addBookBtn:
addBook();
}
}
}
然后在真机上分别安装应用AIDLServerDemo和AIDLClientDemo,先启动AIDLServer,再启动AIDLClient,此时logcat日志中输出:
2020-06-20 00:39:50.117 5389-5389/? D/MainActivity Client: attemptToBindService execute
2020-06-20 00:39:50.320 5389-5389/? D/MainActivity Client: client: service connected
2020-06-20 00:39:50.321 5389-5389/? D/MainActivity Client: client: get books is [Book{ name: initBook, price: 22 }]
getBooks方法跨进程调用成功,AIDLClient获取到了AIDLServer应用启动AIDLService服务时在onCreate中初始化的Book对象。
然后点击AIDLClient的addBook按钮,客户端logcat日志输出如下:
2020-06-20 00:45:00.843 5389-5389/com.zjhtest.aidlclient D/MainActivity Client: client: newBook is Book{ name: Android权威编程指南, price: 98 }
服务端logcat日志输出如下:
2020-06-20 00:45:00.842 5869-5901/com.zjhtest.aidlserver D/AIDLService: invoking server addBook method, now the list is [Book{ name: initBook, price: 22 }, Book{ name: Android权威编程指南, price: 98 }]
同样成功调用addBook方法,客户端传递新创建的Book实例,服务端接收后将其add到mBooks集合中。
通过上面这个简单的例子,大致了解了AIDL的使用过程,但是对于原理性的东西还需要继续深究,可以查看官方文档。
AIDLClient完整项目地址:https://gitlab.com/zhoujhpaul/aidlclientdemo
AIDLServer完整项目地址:https://gitlab.com/zhoujhpaul/aidlserverdemo