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<Book> 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
编写服务端代码
/**
* 服务端的AIDLService
*
*/
class AIDLService : Service() {
val TAG = this.javaClass.simpleName
//包含Book对象的list
private var list: MutableList<Book> = mutableListOf()
//由AIDL文件生成的BookManager
private val mBookManager: BookManager.Stub = object : BookManager.Stub() {
override fun getBooks(): MutableList<Book> {
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
<service
android:name=".ipc.AIDLService"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BookService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
移植相关文件
把服务端的整个 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<Book>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.button).setOnClickListener {
addBook()
}
findViewById<Button>(R.id.button2).setOnClickListener {
getBook()
}
}
/**
* 按钮的点击事件,点击之后调用服务端的addBookIn方法
*
* @param view
*/
fun addBook() {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!mBound) {
attemptToBindService()
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show()
return
}
if (mBookManager == null) return
val book = Book("APP研发录In", 30)
try {
mBookManager!!.addBook(book)
Log.e(TAG, "添加新书$book")
} catch (e: RemoteException) {
e.printStackTrace()
}
}
fun getBook() {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!mBound) {
attemptToBindService()
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show()
return
}
if (mBookManager == null) return
Log.e(TAG, "服务器返回:${mBookManager!!.books}")
}
/**
* 尝试与服务端建立连接
*/
private fun attemptToBindService() {
val intent = Intent()
intent.action = "android.intent.action.BookService"
intent.setPackage("com.example.mynativeapplication")
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
}
override fun onStart() {
super.onStart()
if (!mBound) {
attemptToBindService()
}
}
override fun onStop() {
super.onStop()
if (mBound) {
unbindService(mServiceConnection)
mBound = false
}
}
private val mServiceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
Log.d(TAG, "service connected")
mBookManager = BookManager.Stub.asInterface(service)
mBound = true
}
override fun onServiceDisconnected(name: ComponentName) {
Log.e(TAG, "service disconnected")
mBound = false
}
}
}
当我们调用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<Book> 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<Book> {
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()
}
}