AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
AIDL的语法和Java是一样的,只是在一些细微处有些许差别:
-
文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。
-
数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包。默认支持的数据类型包括:Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。String 类型。CharSequence类型。
List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。List可以使用泛型。
Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。 -
定向tag:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。注意:不要滥用定向 tag ,而是要根据需要选取合适的,全都用 inout ,等工程大了系统的开销就会大很多。
-
两种AIDL文件:AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。
数据类
首先要定义两个进程间通信的数据类,由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域。所以我们必须将要传输的数据转化为能够在内存之间流通的形式。这个转化的过程就叫做序列化与反序列化。选择的序列化方式是实现 Parcelable 接口。
@Parcelize
data class Book(var name: String? = null, var price: Int = 0) : Parcelable {
fun readFromParcel(dest: Parcel) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
name = dest.readString()
price = dest.readInt()
}
}
在kotlin中,中实现 Parcelable 非常简单首先,在所属模块的 build.gradle 文件中应用 kotlin-parcelize 插件,然后在定义的实体类添加 @Parcelize 注解,并实现 Parcelable 接口即可。注意:添加 @Parcelize 注解生成器就会自动创建writeToParcel()/ createFromParcel()方法,但不会生成readFromParcel()方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
}
书写AIDL文件
鼠标移到app上面去,点击右键,然后 new->AIDL->AIDL File
Book.aidl
// Book.aidl package com.matt.myaidltest.ipc; //注意parcelable是小写 parcelable Book;
BookManager.aidl
// BookManager.aidl
package com.matt.myaidltest.ipc;
import com.matt.myaidltest.ipc.Book;
interface BookManager {
//所有的返回值前都不需要加任何东西,不管是什么数据类型
List getBooks();
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
void addBook(in Book book);
}
注意:Book.kt 的包名要与 Book.aidl 一致,不然会会报 Symbol not found 的错误
如果包名路径不一致的话,修改 build.gradle 文件:在 android{} 中间加上下面的内容:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
在写完AIDL文件后,需要Rebuild Project一下项目,生成相应文件,然后再编写Server
编写服务端代码
class AIDLService : Service() {
val TAG = this.javaClass.simpleName
//包含Book对象的list
private var list: MutableList = mutableListOf()
//由AIDL文件生成的BookManager
private val mBookManager: BookManager.Stub = object : BookManager.Stub() {
override fun getBooks(): MutableList {
synchronized(this) {
Log.i(TAG, "客户端获取书本列表: $list")
return list
}
}
override fun addBook(book: Book) {
synchronized(this) {
list.add(book)
Log.i(TAG, "客户端添加新书: $book")
}
}
}
override fun onCreate() {
super.onCreate()
for (i in 0..4) {
val book = Book("第" + i + "本书", i)
list.add(book)
}
}
override fun onBind(intent: Intent): IBinder {
Log.d(TAG, String.format("on bind,intent = %s", intent.toString()))
return mBookManager
}
}
上述代码主要在创建时分为三块:第一块是初始化,在 onCreate() 方法里面我进行了一些数据的初始化操作。第二块是重写 BookManager.Stub 中的方法。在这里面提供AIDL里面定义的方法接口的具体实现逻辑。第三块是重写 onBind() 方法,在里面返回写好的 BookManager.Stub 。
接下来在 Manefest 文件里面注册这个我们写好的 Service
2.2 客户端实现
移植相关文件
把服务端的整个 aidl 文件夹复制到客户端,还要单独将数据类文件放到 java 文件夹里去。
在客户端我们要完成的工作主要是调用服务端的方法,但是在那之前,我们首先要连接上服务端,完整的客户端代码是这样的:
class MainActivity : AppCompatActivity() {
val TAG = javaClass.simpleName
//由AIDL文件生成的Java类
private var mBookManager: BookManager? = null
//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
private var mBound = false
//包含Book对象的list
private var list: List? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById
当我们调用addBook()方法时,客户端打印信息
服务端打印信息
当我们调用getBook()方法时,客户端打印信息
服务端打印信息
所有的非基本参数都需要一个定向tag来指出数据流通的方式,不管是 in , out , 还是 inout 。基本参数的定向tag默认是并且只能是 in 。其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。
修改BookManager.aidl
// BookManager.aidl
package com.matt.myaidltest.ipc;
import com.matt.myaidltest.ipc.Book;
interface BookManager {
//所有的返回值前都不需要加任何东西,不管是什么数据类型
List getBooks();
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
void addBook(in Book book);
//通过三种定位tag做对比试验,观察输出的结果
void addBookIn(in Book book);
void addBookOut(out Book book);
void addBookInout(inout Book book);
}
修改服务器端
分别将接收到的 in、out、 inout 定向tag的形参后,修改其价格
private val mBookManager: BookManager.Stub = object : BookManager.Stub() {
override fun getBooks(): MutableList {
synchronized(this) {
Log.i(TAG, "客户端获取书本列表: $list")
return list
}
}
override fun addBook(book: Book) {
synchronized(this) {
list.add(book)
Log.i(TAG, "客户端添加新书: $book")
}
}
override fun addBookIn(book: Book) {
synchronized(this) {
list.add(book)
Log.i(TAG, "addBookIn: $book")
book.price = 61
}
}
override fun addBookOut(book: Book) {
synchronized(this) {
list.add(book)
Log.i(TAG, "addBookOut: $book")
book.price = 62
}
}
override fun addBookInout(book: Book) {
synchronized(this) {
list.add(book)
Log.i(TAG, "addBookInout: $book")
book.price = 63
}
}
}
修改客户端
fun addBook() {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!mBound) {
attemptToBindService()
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show()
return
}
if (mBookManager == null) return
try {
val book1 = Book("APP开发1", 31)
mBookManager!!.addBookIn(book1)
Log.e(TAG, "addBookIn 添加新书$book1")
val book2 = Book("APP开发2", 32)
mBookManager!!.addBookOut(book2)
Log.e(TAG, "addBookOut 添加新书$book2")
val book3 = Book("APP开发3", 33)
mBookManager!!.addBookInout(book3)
Log.e(TAG, "addBookInout 添加新书$book3")
} catch (e: RemoteException) {
e.printStackTrace()
}
}
看一下客户端的打印结果
服务端的打印结果
可以很容易理解,
- in 表示数据只能由客户端流向服务端,服务端能够正常的接收到客户端传过来的数据,但是服务端修改此参数后,不会影响客户端的对象。
- out 表示数据只能由服务端流向客户端,服务端收到的参数是空对象,并且服务端修改对象后客户端会同步变动。
- inout 则表示数据可在服务端与客户端之间双向流通,服务端能接收到客户端传来的完整对象,并且服务端修改对象后客户端会同步变动。



