AIDL使用
Android的IPC(跨进程通信)方式有Binder、AIDL、Messenger、文件共享等,AIDL是最常用的方案。但是由于项目中用的不多,所以不熟悉,特意将AIDL的使用过程记录一下,供日后查看。
AIDL接口支持的格式
AIDL的语法与Java接口的语法是一致的,但是不支持定义接口常量 ,并且对于数据的类型是有要求的,仅支持以下几种格式:
- Java的原语类型:
int
、long
、char
、bolean
等。 String
CharSequence
- 实现
Parcelable
接口的数据类型
List
List 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或Parcelable 类型。您可选择将 List 用作“泛型”类(例如,List)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList。Map
Map 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap。
AIDL使用方法
AIDL实现需要3步,分别是:
- 创建 .aidl 文件
- 创建Service并实现aidl生成的Sub类
- 客户端bindService调用Service
下面以书店的例子举例说明这三个步骤。
实现一个书店的aidl,完成添加图书与查询图书信息的功能。
1.创建.aidl文件
使用AndroidStudio可以很方便的创建AIDL文件,将鼠标放在文件夹目录上面,然后右键选择创建AIDL,如下图所示。
IBookManager.aidl
根据我们的需求,需要设计两个方法,分别是void addBook(Book book)
和Book getBook(String name)
,下面让我们创建一个IBookManager.aidl
的文件,如下:
//IBookManager.aidl
package cn.bearever.android.book;
interface IBookManager {
void addBook(in Book book);
Book getBook(String name);
}
注意 在aidl里面,非原语参数需要用in
、out
或者inout
标记,例如上面的void addBook (in Book book)
。
Book.aidl
如果只是这样的话,编译会失败,提示Book找不到:
cn.bearever.android.book.Book: couldn't find import for class cn.bearever.android.book.Book
接着我们在IBookManager.aidl
的文件夹下创建Book.aidl
文件,如下:
// Book.aidl
package cn.bearever.android.book;
parcelable Book;
这里的关键在于parcelable Book;
,它指向了一个实现了Parcelable的Book类,所以我们还需要实现一个Book.java的类。需要注意的是
Book.java的代码位置不能放在aidl文件夹,需要放在java代码的文件夹下
,例如src\main\java\cn\bearever\android\book\Book.java
。
Book.java
//Book.java
public class Book implements Parcelable {
public String name;
public int money;
public Book() {
}
public Book(String name, int money) {
this.name = name;
this.money = money;
}
protected Book(Parcel in) {
name = in.readString();
money = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(money);
}
@Override
public int describeContents() {
return 0;
}
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];
}
};
@NonNull
@Override
public String toString() {
return "书名:" + name + ",价格:" + money;
}
}
注意
以为这样就可以了?还没有的!前面我们虽然使用Book.aidl
指定了需要用到Book.java
类,但是这个类的具体路径还没有确定,需要在IBookManager.aidl
里面通过import
指定,例如:
// IBookManager.aidl
package cn.bearever.android.book;
import cn.bearever.android.book.Book; //这一句话就是指定Book的路径!!!!
interface IBookManager {
void addBook(in Book book);
Book getBook(String name);
}
创建Service并实现aidl生成的Sub类
实现了aidl
接口之后AndroidStudio会自动生成一个与aidl同名的java文件,具体生成的代码就不贴上来了,下面我们来实现aidl的接口定义的功能。在java代码文件夹里面创建一个BookService.java
的Service,并实现将IBookManager.Sub
的实现作为Binder
返回。
BookService.java
//BookService.java
public class BookService extends Service {
private BookBinder mBinder;
private HashMap<String, Book> mBookMap;
@Override
public void onCreate() {
super.onCreate();
mBinder = new BookBinder();
mBookMap = new HashMap<>();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class BookBinder extends IBookManager.Stub {
@Override
public void addBook(Book book) throws RemoteException {
if (book == null) {
return;
}
mBookMap.put(book.name, book);
}
@Override
public Book getBook(String name) throws RemoteException {
return mBookMap.get(name);
}
}
}
Service实现之后我们就可以将其注册到AndroidManifest.xml里面了,并为其设置运行的进程,例如:
<application>
<service
android:name=".BookService"
android:process=":remote" />
</application>
注意 接口的访问是多线程进行的,一定要注意线程安全问题!HashMap是线程不安全的,需要根据业务场景使用线程安全的方案。
前面两个步骤创建的aidl完整代码文件结构如下:
3.客户端bindService调用Service
下面我们在主进程里面来访问remote进程的数据,创建一个MainActivity.java
,绑定BookService,通过其返回的service转成IBookManager的接口,然后调用其方法试试。
//MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private IBookManager mBookManager;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = IBookManager.Stub.asInterface(service); //注意这里是通过Sub.asInterface转成接口的
try {
//添加图书
mBookManager.addBook(new Book("哈利波特", 100));
Log.d(TAG, "添加一本图书");
//获取图书信息
Book book = mBookManager.getBook("哈利波特");
Log.d(TAG, "图书信息:" + book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent();
intent.setClass(this, BookService.class);
bindService(intent, connection, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
运行程序打印如下:
2020-04-23 11:53:04.789 10288-10288/cn.bearever.android.test D/MainActivity: 添加一本图书
2020-04-23 11:53:04.790 10288-10288/cn.bearever.android.test D/MainActivity: 图书信息:书名:哈利波特,价格:100
再看一下运行的进程数量,确实是两个,说明我们的跨进程通信成功了。
结语
AIDL是Android里面最常用的IPC方案,虽然对于单进程的项目使用不到,但还是要掌握的。对于Android而言,一个进程就是一个应用,跨进程通信其实也是跨应用通信的解决方案。一搬情况下我们并不希望自己的进程被别人访问,可以加入权限校验,这个部分就不展开了。对于AIDL本身需要注意的要点有:
- AIDL支持的数据类型及Parcelable的实现
- 主动import需要的类路径
- 方法调用是多线程环境的,要注意线程安全问题。