AIDL是Android中IPC(Inter-Process Communication,进程通信)方式中的一种,AIDL是Android Interface definition language的缩写。对于小白来说,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。
实践
AIDL用于进程之间通信,我们可以在Android Studio中建立两个APP实现两个进程。当然我们也可以在AndroidManifest.xml中声明任一四大组件运行在独立进程中,形如下文,声明了一个运行在名为AKMessagerService进程中的服务:
<service
android:name="com.akotiner.service.MyService"
android:enabled="true"
android:exported="true"
android:process=":AKMessagerService"/>
普适度的原因,本文仅使用第一种方式进行实践,我们使用AIDL经典案例中的图书馆里作为本文的DEMO。
服务端
首先我们新建一个项目作为服务端,然后新建aidl文件夹用于存储aidl文件,项目的目录形如下图:
需要注意的是aidl和java内的文件的包名需要一致。在aidl目录下,我们可以看到有两个aidl文,其中Book是自定义的类,IBookManger是自定义接口,个人认为aidl可以根据上述分两类。
类的传递
AIDL可以传递默认类型有Int、Float等基本数据类型以及String、含有基本数据类型的Map和List。如果我们想要传递自定义类型,如上图中的Book类,我们需要在java目录下定义一个实现Parcelable接口的Book类,并在aidl目录对应的包下声明该类,Code如下:
在java文件夹实现自定义类型:
package com.akotiner.aidlserver
import android.os.Parcel
import android.os.Parcelableclass Book(val bookId: Int, val bookName: String?) : Parcelable {
constructor(parcel: Parcel) : this( parcel.readInt(), parcel.readString() )
override fun toString(): String {
return "Book(bookId=$bookId, bookName='$bookName')"
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(bookId) parcel.writeString(bookName)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator
{ override fun createFromParcel(parcel: Parcel): Book { return Book(parcel) }
override fun newArray(size: Int): Array<Book?> {
return arrayOfNulls(size) } }
}
在aidl中声明自定义类型:
// Book.aidl
package com.akotiner.aidlserver;// Declare any non-default types here with import statements
parcelable Book;
接口传递
aidl通信的本质是Binder的传递,通过获取远程服务提供的代理Binder对象,我们可以进行跨进程通信。
在IBookManager.aidl定义方法:
// IBookManager.aidl
package com.akotiner.aidlserver;// Declare any non-default types here with import statements
import com.akotiner.aidlserver.Book;
interface IBookManager {//in表示数据的流向是流入,还有out表示流出。 String addBook(in Book book); String deleteBook(in Book book); //还有关键字oneway,表示异步调用 //如下述方法,表示删除一本书,但是不必等待返回值,而是仅负责将book这个参数传递下去 //oneway void deleteBook(in Book book);
}
- build项目后会生成IBookManaer.stub文件,该文件继承了系统的Binder类并实现了我们自定义的接口。 在Java中实现IBookManaer.aidl的方法:
package com.akotiner.aidlserver
//构造方法中传入一个HashMap用于存储书籍
class BookManager(private val bookList: HashMap<Int, String>) : IBookManager.Stub() {
override fun addBook(book: Book?): String {
if (book != null) {
return if (bookList.containsKey(book.bookId)) {
"Has already added"
} else {
bookList[book.bookId] = book.bookName.toString()
"Success"
}
}
return "Invalid Book"
}
override fun deleteBook(book: Book?): String {
if (book != null) {
if (bookList.containsKey(book.bookId) && bookList.containsValue(book.bookName)) {
bookList.remove(book.bookId, book.bookName)
return "Success"
}
}
return "Invalid Book"
}
}
远程服务
Service是可以通过Intent进行跨进程绑定,然后返回Binder 对象,通过客户端对Binder对象的操作实现跨进程操作。在这里我们建立一个服务用于被绑定:
package com.akotiner.aidlserver
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
class BookService : Service() {
private val bookList = HashMap<Int, String>()
private val bookManagerBinder = BookManager(bookList)
override fun onCreate() {
Log.e(TAG, "onCreate")
super.onCreate()
}
override fun onBind(intent: Intent): IBinder {
Log.e(TAG, "onBind")
return bookManagerBinder
}
companion object {
private val TAG = "BookService"
}
}
需要注意的是,Android新版本不允许通过绑定启动服务,我们需要在Acticity中手动启动服务后才能被绑定。同时我们需要设置Service的action用于隐式的绑定,在AndroidManifest,xml中声明服务的部分内容如下即可(服务的名字和aciton可以不同):
<service
android:name=".BookService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.AIDL_SERVER"/>
</intent-filter>
</service>
以上,服务端的准备就完成了。我们可以将该app安装至测试机器可以看到log打印以下内容,说明服务启动成功了:
2022-02-09 15:46:13.599 14879-14879/com.akotiner.aidlserver E/BookService: onCreate
客户端
为了添加书籍,客户端APP的界面加了两个按钮和两个文本输入框。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:layout_marginTop="200dp"
android:id="@+id/book_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/book_id"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<EditText
android:id="@+id/book_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/book_name"
android:inputType="text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="50dp"
app:layout_constraintTop_toBottomOf="@id/book_id"/>
<Button
android:id="@+id/add"
android:layout_marginTop="50dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_book"
app:layout_constraintTop_toBottomOf="@id/book_name"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<Button
android:id="@+id/del"
android:layout_marginTop="50dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/delete_book"
app:layout_constraintTop_toBottomOf="@id/add"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
引入远程aidl文件
为了调用远程的aidl文件,我们可以将服务端的代码打成jar包,也可以将服务端的aidl文件夹复制到客户端(请注意包名)。
然后我们Build一下客户端的代码,接下来进行服务绑定并获取binder对象就可以进行跨进程通信了:
package com.akotiner.aidlclient
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var bookService: IBookManager
private var isBoundService = false
private lateinit var bookIdText: EditText
private lateinit var bookNameText: EditText
private lateinit var addBook: Button
private lateinit var delBook: Button
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
bookService = IBookManager.Stub.asInterface(service)
isBoundService = true
}
override fun onServiceDisconnected(name: ComponentName?) {
isBoundService = false
}
}
private val mIntent = Intent()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bookIdText = findViewById(R.id.book_id)
bookNameText = findViewById(R.id.book_name)
addBook = findViewById(R.id.add)
delBook = findViewById(R.id.del)
addBook.setOnClickListener(this)
}
override fun onResume() {
super.onResume()
mIntent.action = "android.intent.action.AIDL_SERVER"
mIntent.`package` = "com.akotiner.aidlserver"
//隐式绑定远程服务
bindService(mIntent, serviceConnection, BIND_AUTO_CREATE)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.add -> {
val res = bookService.addBook(
Book(
bookIdText.text.toString().toInt(),
bookNameText.text.toString()
)
)
Toast.makeText(this, res, Toast.LENGTH_SHORT).show()
}
R.id.del -> {
Toast.makeText(this, "Waiting", Toast.LENGTH_SHORT).show()
}
}
}
}
上述只实现了一个添加书籍的方法,添加有效内容后会弹出Success的Toast。
源代码
理论
也可以参考这篇博客Android中AIDL的使用详解
Messenger
Messenger是对于AIDL的封装,相对于AIDL更轻量、简洁。但是不支持RPC和同步执行任务,所以使用AIDL还是Messenger要因地制宜。
实践
相对于AIDL来说Messenger的使用就简单很多了。
建立跨进程服务
这里我们使用下面的方式定义一个跨进程的服务
<service
android:name="com.akotiner.service.MyService"
android:enabled="true"
android:exported="true"
android:process=":AKMessagerService"/>
服务的代码如下:
package com.akotiner.service
import android.app.Service
import android.content.Intent
import android.os.*
import android.util.Log
class MyService : Service() {
//定义一个Handler用于构建Messenger对象
internal class ReceiveHandler : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
Log.e(TAG, "ReceiveHandler: ${msg.what}")
val message = Message.obtain()
message.what = 1
//从Client传递来的Messenger对象,其中包含了一个Handler,发送消息实现异步
msg.replyTo.send(msg)
}
}
//建立Messenger对象
private val myMessenger: Messenger = Messenger(ReceiveHandler())
//返回Binder
override fun onBind(intent: Intent): IBinder {
return myMessenger.binder
}
companion object {
private const val TAG = "MyService"
}
}
绑定服务
老生常谈,没什么好说的绑就完了:
package com.akotiner.akmessager
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.akotiner.service.MyService
class MainActivity : AppCompatActivity() {
private lateinit var mButton: Button
private lateinit var mSend: Button
private lateinit var mMessenger: Messenger
private var isBound = false
private val mServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mMessenger = Messenger(service)
isBound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
isBound = false
}
}
//定义Handler用于处理远程服务的回调
internal class CallBackHandler : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
Log.e(TAG, "CallBackHandler: ${msg.what}")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mButton = findViewById(R.id.button)
mSend = findViewById(R.id.send)
//这个按钮用于绑定服务
mButton.setOnClickListener {
if (!isBound) {
bindMyService()
} else {
Toast.makeText(this, "Connection has already established", Toast.LENGTH_SHORT)
.show()
}
}
//这个按钮用于发送消息实现跨进程通信
mSend.setOnClickListener {
//创建一个Messenger对象,用于传递Handler
val messenger = Messenger(CallBackHandler())
val message = Message.obtain()
//将Messenger对象作为参数传递给Server,相当于我们把Handler也传递了过去
message.replyTo = messenger
message.what = 1
//发送消息
mMessenger.send(message)
}
}
private fun bindMyService() {
bindService(
Intent(applicationContext, MyService::class.java),
mServiceConnection,
BIND_AUTO_CREATE
)
}
companion object {
private const val TAG = "MainActivity"
}
}
运行程序点击按钮,通过log我们可以发现实现可跨进程的消息传递和异步的回传
源代码
Binder
此部分暂略
参考博客
Android中的IPC方式
评论 (0)