AIDL
标签(空格分隔): 跨进程通讯
目录
AIDL(Android Interface Definition Language),接口定义语言
为什么要设计这门语言
实现进程间通讯(特定规格和特定方法下)
可传递的数据类型
- 8种基本类型:byte,boolean,char,short,int,long,float,double
- String类型
- CharSequence
- List (支持泛型)
- Map (不支持泛型)
- 实现Parcelable接口的对象
定向Tag
AIDL的定向Tag表示在跨进程通讯中,信息的流向
Tag符 | 作用 | 表现 |
---|---|---|
in | 数据只能从客户端流向服务端 | 服务端可以接收到一个完整的对象,且客户端不会因为服务端对该对象的操作而改变 |
out | 数据只能从服务端流向客户端 | 服务端会接收到该对象的空对象,但服务端对接收到的空对象有任何修改之后,客户端将会同步变动 |
inout | 数据可以在服务端和客户端之间双向流通 | 服务端可以接收到客户端发过来的完整对象,并且客户端会同步服务端对该对象的所有操作 |
下面做个简单的图来展示:
Tag符 | 服务端是否接收到完整对象 | 客户端是否同步服务端对Object的操作 |
---|---|---|
in | √ | × |
out | × | √ |
inout | √ | √ |
注意: Java中的八种基本类型和String、CharSequence默认并且只能使用in
。
实现步骤
1 服务端配置
1.1 创建*.aidl
文件
我们以 Book.aidl
为例,创建完成之后,Android Studio会自动给我们创建aidl
与java
包同级
创建后,里面的初始化代码为:
// Book.aidl
package com.martin.aidlblog;
// Declare any non-default types here with import statements
interface Book {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
1.2 创建数据Model
这时候,我们创建Book.java
文件,在Book.aidl
相同包下。写入成员变量
private String name;
private int price;
private boolean nowRead;
private int readPage;
并且创建成员变量的 set/get 方法,空构造方法以及方便创建对象的全有属性构造方法。
这是一个Model类的基本创建过程。但AIDL通讯中,数据对象必须是Parcelable对象,所以我们将Book.java
实现Parcelable接口,最终代码如下:
public class Book implements Parcelable{
private String name;
private int price;
private boolean nowRead;
private int readPage;
public Book() {
}
public Book(String name, int price, boolean nowRead, int readPage) {
this.name = name;
this.price = price;
this.nowRead = nowRead;
this.readPage = readPage;
}
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;
}
public boolean isNowRead() {
return nowRead;
}
public void setNowRead(boolean nowRead) {
this.nowRead = nowRead;
}
public int getReadPage() {
return readPage;
}
public void setReadPage(int readPage) {
this.readPage = readPage;
}
protected Book(Parcel in) {
name = in.readString();
price = in.readInt();
nowRead = in.readByte() != 0;
readPage = in.readInt();
}
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.writeString(name);
dest.writeInt(price);
dest.writeByte((byte) (nowRead ? 1 : 0));
dest.writeInt(readPage);
}
}
注意:这里默认生成的模板类,只能支持 in
的定向Tag,因为生成的方法里面,只有writeToParcel()
,如果想要支持out
或inout
定向Tag的话,还需要实现readFromParcel()
方法,但这个方法并不在Parcelable里面,需要我们手写。万幸,有writeToParcel()
方法作为参考:
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
dest.writeByte((byte) (nowRead ? 1 : 0));
dest.writeInt(readPage);
}
public void readFromParcel(Parcel dest) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
name = dest.readString();
price = dest.readInt();
nowRead = dest.readByte() == 1;
readPage = dest.readInt();
}
再生成 toString()
方法,方便后面展示日志
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
", nowRead=" + nowRead +
", readPage=" + readPage +
'}';
}
1.3 书写.aidl文件内容
Book.aidl和Book.Java文件之所以同名,是因为我们要将两个文件进行关联,所以这两个文件的包名需要一致,并且,我们将Book.aidl的内容改写成:
// Book.aidl
package com.example.marti.aidlstudy.aidl;
//第一类AIDL文件
//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
// 注意:Book.aidl 与 Book.java 包名需要一致
parcelable Book;
接下来创建一个AIDL接口,并且定义几个方法:
// BookManager.aidl
package com.martin.aidlblog;
// 第二类AIDL文件
// 在同一包下,也需要导包
import com.martin.aidlblog.Book;
interface BookManager {
// 定向tag == in
Book inBook(in Book book);
// 定向tag == out
Book outBook(out Book book);
// 定向 inout
Book inoutBook(inout Book book);
// 获取书籍集合
List<Book> getBooks();
}
这两个.aidl文件创建完成,我们将项目 builder 一下,如果没有报错,那么AIDL的操作就成功了。
编辑器会自动将我们的第二类AIDL文件编译成.java文件,存放在以下目录:
我们定义的方法被编译后的样子:
// 定向tag == in
public com.martin.aidlblog.Book inBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;
// 定向tag == out
public com.martin.aidlblog.Book outBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;
// 定向 inout
public com.martin.aidlblog.Book inoutBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;
// 获取书籍集合
public java.util.List<com.martin.aidlblog.Book> getBooks() throws android.os.RemoteException;
1.4 创建服务端Service
现在创建的Service就没有路径限制了,我们在java包下,随便位置创建一个AIDLService
文件。
我们怎么让Service
和BookManager
产生联系呢?
现在看BookManager.java
文件,我们发现文件中的内部类 Stub
继承了android.os.Binder
:
我们立刻联想到Service
里面的onBind()
方法,返回值是IBinder
,而IBinder
是Binder
的父类!
所以,Service内部逻辑:
public class AIDLService extends Service {
// 维护一个 书籍 集合
private List<Book> books = new ArrayList<>();
public AIDLService() {
}
private BookManager.Stub manager = new BookManager.Stub() {
@Override
public Book inBook(Book book) throws RemoteException {
resetBook(book);
return book;
}
@Override
public List<Book> getBooks() throws RemoteException {
return books;
}
@Override
public Book outBook(Book book) throws RemoteException {
resetBook(book);
return book;
}
@Override
public Book inoutBook(Book book) throws RemoteException {
resetBook(book);
book.setNowRead(true);
return book;
}
};
// 重设 书籍 内容
private void resetBook(Book book) {
book.setPrice(91);
book.setReadPage(23);
books.add(book);
}
@Override
public IBinder onBind(Intent intent) {
return manager;
}
@Override
public void onCreate() {
super.onCreate();
}
}
Service的java文件内容写完,我们再到AndroidManifest.xml
文件中,给Service添加意图过滤器:
<service
android:name=".AIDLService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.martin.aidlblog" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
1.5 修改配置信息
大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?
- 修改app的
build.gradle
文件里android
内添加以下内容:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
这样Gradle在构建项目时,也会自动遍历aidl包。
2 客户端配置
2.1 文件拷贝
我们将服务端的aidl
包直接copy到客户端项目中,位置要一样,与java包平级。
2.2 创建连接
所谓创建连接,就是打开服务端的服务:
private void bindService() {
Intent intent = new Intent();
intent.setAction("com.martin.aidlblog");
intent.setPackage("com.martin.aidlblog");
bindService(intent, connection, BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
manager = BookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
connection = null;
}
};
到这里,我们已经实现了进程间通讯的功能。但是前面说的定向Tag好像并没有什么体现。
2.3 展示 定向Tag
我们在客户端设置四个Button,分别调用我们在 BookManager.aidl
里面设置的四个方法,整体代码如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "客户端";
private BookManager manager;
private TextView txtIn;
private TextView txtGet;
private TextView txtInout;
private TextView txtOut;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
txtIn = findViewById(R.id.txt_in);
txtGet = findViewById(R.id.txt_get);
txtInout = findViewById(R.id.txt_inout);
txtOut = findViewById(R.id.txt_out);
}
private void bindService() {
Intent intent = new Intent();
intent.setAction("com.martin.aidlblog");
intent.setPackage("com.martin.aidlblog");
bindService(intent, connection, BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
manager = BookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
connection = null;
}
};
public void inBook(View view) {
try {
Book sendBook = getBook();
Book book = manager.inBook(sendBook);
txtIn.setText(sendBook.toString());
Log.e(TAG, "add: 客户端 书籍信息 :" + sendBook);
Log.e(TAG, "add: 服务端 返回书籍信息:" + book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void outBook(View view) {
try {
Book sendBook = getBook();
Book book = manager.outBook(sendBook);
txtOut.setText(sendBook.toString());
Log.e(TAG, "read: 客户端 书籍信息 :" + sendBook);
Log.e(TAG, "read: 服务端 返回书籍信息:" + book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void inoutBook(View view) {
try {
Book sendBook = getBook();
Book book = manager.inoutBook(sendBook);
txtInout.setText(sendBook.toString());
Log.e(TAG, "now: 客户端 书籍信息 :" + sendBook);
Log.e(TAG, "now: 服务端 返回书籍信息:" + book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void get(View view) {
try {
List<Book> books = manager.getBooks();
txtGet.setText(books.toString());
Log.e(TAG, "get: 书籍数量 " + books.size());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public Book getBook() {
return new Book("自动获取的名字", 66, false, 35);
}
@Override
protected void onResume() {
super.onResume();
bindService();
}
@Override
protected void onPause() {
super.onPause();
unbindService(connection);
}
}
先运行服务端,再运行客户端,分别点击按钮,查看获取的内容:
- inBook()
12-01 17:38:36.516 7536-7536/com.martin.aidlclientblog E/客户端: add:
客户端 书籍信息 :Book{name='自动获取的名字', price=66, nowRead=false, readPage=35}
12-01 17:38:36.516 7536-7536/com.martin.aidlclientblog E/客户端: add:
服务端 返回书籍信息:Book{name='自动获取的名字', price=91, nowRead=false, readPage=23}
- outBook()
12-01 17:43:04.444 7536-7536/com.martin.aidlclientblog E/客户端: read:
客户端 书籍信息 :Book{name='null', price=91, nowRead=false, readPage=23}
12-01 17:43:04.444 7536-7536/com.martin.aidlclientblog E/客户端: read:
服务端 返回书籍信息:Book{name='null', price=91, nowRead=false, readPage=23}
- inoutBook()
12-01 17:43:54.412 7536-7536/com.martin.aidlclientblog E/客户端: now:
客户端 书籍信息 :Book{name='自动获取的名字', price=91, nowRead=true, readPage=23}
12-01 17:43:54.412 7536-7536/com.martin.aidlclientblog E/客户端: now:
服务端 返回书籍信息:Book{name='自动获取的名字', price=91, nowRead=true, readPage=23}
- get()
12-01 17:47:27.979 12139-12139/com.martin.aidlclientblog E/客户端: get: 书籍数量 3
我们打印一下获取的书籍信息:
12-01 17:47:27.976 12139-12139/com.martin.aidlclientblog E/客户端: get:
这是原本的书籍 Book{name='自动获取的名字', price=66, nowRead=false, readPage=35}
现在结合上面写过的,定向Tag的特点以及图标,三个定向符的含义就更加清晰了。
源码地址
CSDN源码最低设置2分,如果有需要可以留下邮箱,我看到会即使回的。