一、 是什么
Binder是Android系统的一个进程间通信的机制。它主要由以下几个部分组成:虚拟Binder设备(/dev/binder)、Binder驱动(内核中的binder.c)、ServiceManager、提供服务的Service、调用服务的Client。
Binder设备和Binder驱动实现了进程通信的协议和数据交换的细节,ServiceManager则负责Service的统一管理、Client的鉴权等,Service提供具体的服务,Client请求服务完成自身的需求。
二、与其他跨进程通信机制的比较
Android考虑使用Binder作为主要的跨进程通信机制而不是采取Socket之类的通信机制,是出于性能、稳定、安全和使用复杂程度等方面的综合考虑。
2.1 性能
单论从速度/性能方面来说,共享内存无疑是最优的。Socket等,需要至少两次内存拷贝(进程A用户空间 -> 内核空间 -> 进程B用户空间)。Binder需要一次内存拷贝(进程A用户空间 -> 内核空间),性能介于两者之间。
IPC | 数据拷贝次数 |
---|---|
共享内存 | 0 |
Binder | 1 |
Socket/管道/消息队列 | 2或以上 |
2.2 易用性和稳定性
Binder基于C/S架构,职责清晰,进程间互不干扰;共享内存的有点是快,但它需要依靠其他同步工具来实现临界资源的访问,使用相对复杂;另外利用Socket,也可以实现类似Binder的C/S架构的通信机制,但是缺点是需要多次内存拷贝。
2.3 安全
《Android Binder设计与实现 - 设计篇》:首先传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份。Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。使用传统IPC只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标记只有由IPC机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。
基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder。Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。
2.4 其他
根据这个知乎回答的说法,使用Binder还有隔断GPL协议和面向对象设计方面的考虑 。
三、为什么只需要拷贝一次
其核心原理很简单,就是内存映射。要理解内存映射,需要先了解物理地址和虚拟地址、用户空间和内核空间的概念。网上有非常多优秀的文章,解析得很清楚,这里不赘述。
借用一张流传广泛的图片来解析一次拷贝:
四、使用Messenger和AIDL
AIDL是Android Interface Description Language的缩写,也就是Android接口定义语言。众所周知,AIDL是基于Binder机制实现的,而Messager是基于AIDL的。
4.1 Messager的使用
Messager是比较简单的进程通信了,示例如下(基于官方示例做了一点点小修改):
Service端:
/** Command to the service to display a message */
const val MSG_SAY_HELLO = 0
const val MSG_REPLY_HELLO = 1
const val KEY_HELLO = "hello"
class MessengerService : Service() {
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
private lateinit var mMessenger: Messenger
/**
* Handler of incoming messages from clients.
*/
internal class IncomingHandler() : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO -> {
val hello = msg.data.getString(KEY_HELLO)
Log.d("Service", "$hello, thread: ${Thread.currentThread().name}")
val replyTo = msg.replyTo
val reply = Message.obtain(null, MSG_REPLY_HELLO, null)
reply.data.putString(KEY_HELLO, "Hello from Service")
replyTo.send(reply)
}
else -> super.handleMessage(msg)
}
}
}
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
override fun onBind(intent: Intent): IBinder? {
Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
mMessenger = Messenger(IncomingHandler())
return mMessenger.binder
}
}
Client端:
class MainActivity : AppCompatActivity() {
/** Messenger for communicating with the service. */
private var service: Messenger? = null
private var client : Messenger? = null
/** Flag indicating whether we have called bind on the service. */
var bound = false
/**
* Class for interacting with the main interface of the service.
*/
private val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
this@MainActivity.service = Messenger(service)
client = Messenger(IncomingHandler(applicationContext))
bound = true
}
override fun onServiceDisconnected(className: ComponentName) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
service = null
bound = false
}
}
fun sayHello(v: View?) {
if (!bound) return
// Create and send a message to the service, using a supported 'what' value
val msg: Message = Message.obtain(null, MSG_SAY_HELLO, null)
msg.data.putString(KEY_HELLO, "Hello from Client")
msg.replyTo = client
try {
service!!.send(msg)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onStart() {
super.onStart()
// Bind to the service
bindService(Intent(this, MessengerService::class.java), connection,
Context.BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
// Unbind from the service
if (bound) {
unbindService(connection)
bound = false
}
}
internal class IncomingHandler(private val context: Context) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_REPLY_HELLO -> {
val hello = msg.data.getString(KEY_HELLO)
Log.d("Client", "$hello, thread: ${Thread.currentThread().name}")
}
else -> super.handleMessage(msg)
}
}
}
}
manifest
<application>
...
<service android:name=".service.MessengerService" android:process=":messenger" />
...
</application>
4.2 AIDL的使用
官方文档介绍,如要使用 AIDL 创建绑定服务,请执行以下步骤:
-
创建 .aidl 文件
此文件定义带有方法签名的编程接口。 -
实现接口
Android SDK 工具会基于您的 .aidl 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现这些方法。 -
向客户端公开接口
实现 Service 并重写 onBind(),从而返回 Stub 类的实现。
来动手尝试一下,先看工程目录:
Book.aidl
package com.sahooz.aidlsample.entity;
parcelable Book;
IBookService.aidl
// IBookService.aidl
package com.sahooz.aidlsample.service;
import com.sahooz.aidlsample.entity.Book;
import com.sahooz.aidlsample.service.IInsertCallback;
// Declare any non-default types here with import statements
interface IBookService {
Book getBookById(int id);
oneway void insert(in Book book, IInsertCallback callback);
}
IInsertCallback.aidl
// IInsertCallback.aidl
package com.sahooz.aidlsample.service;
// Declare any non-default types here with import statements
interface IInsertCallback {
void onResult(int code);
}
Book.kt
package com.sahooz.aidlsample.entity
import android.os.Parcel
import android.os.Parcelable
class Book(
val id: Int,
val name: String
) : Parcelable {
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(id)
dest.writeString(name)
}
override fun toString(): String {
return "Book(id=$id, name='$name')"
}
protected constructor(p: Parcel) : this(p.readInt(), p.readString()!!) {
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<Book> = object : Parcelable.Creator<Book> {
override fun createFromParcel(source: Parcel): Book {
return Book(source)
}
override fun newArray(size: Int): Array<Book?> {
return arrayOfNulls(size)
}
}
}
}
BookService.kt
package com.sahooz.aidlsample.service
import android.app.Service
import android.content.Intent
import android.util.Log
import com.sahooz.aidlsample.entity.Book
class BookService : Service() {
override fun onBind(intent: Intent?) = BookBinder()
class BookBinder : IBookService.Stub() {
override fun getBookById(id: Int): Book {
Log.d("AIDL", "BookService fun[getBookById] execute on ${Thread.currentThread().name}")
// 模拟耗时操作
Thread.sleep(1000)
return Book(id, "Book $id")
}
override fun insert(book: Book, callback: IInsertCallback?) {
Log.d("AIDL", "BookService fun[insert] execute on ${Thread.currentThread().name}")
// 模拟耗时操作
Thread.sleep(1000)
callback?.onResult(0)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var service: IBookService? = null
private val conn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
this@MainActivity.service = IBookService.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bindService(Intent(applicationContext, BookService::class.java), conn, BIND_AUTO_CREATE)
}
override fun onDestroy() {
super.onDestroy()
if(service != null) {
unbindService(conn)
}
}
fun transact(v: View) {
val s = service ?: return
if(v.id == R.id.btnGet) {
thread {
Log.d("AIDL", "Call getBookById at: ${System.currentTimeMillis()}")
val book = s.getBookById(0)
Log.d("AIDL", "Done getBookById call at: ${System.currentTimeMillis()}")
}
} else {
Log.d("AIDL", "Call insert at: ${System.currentTimeMillis()}")
val book = s.insert(Book(0, "Book"), object : IInsertCallback.Stub() {
override fun onResult(code: Int) {
Log.d("AIDL", "Insert call back at: ${System.currentTimeMillis()}")
}
})
Log.d("AIDL", "Done insert call at: ${System.currentTimeMillis()}")
}
}
}
activity_main.xml
<?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">
<Button
android:id="@+id/btnGet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GET"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.366"
android:clickable="true"
android:focusable="true"
android:onClick="transact"/>
<Button
android:id="@+id/btnInsert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="INSERT"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.509"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.516"
android:clickable="true"
android:focusable="true"
android:onClick="transact"/>
</androidx.constraintlayout.widget.ConstraintLayout>
manifest
<application>
...
<service android:name=".service.BookService" android:process=":bookservice" />
...
</application>
Log
05-07 23:04:00.484 12090-12361/com.sahooz.aidlsample D/AIDL: Call getBookById at: 1620399840484
05-07 23:04:00.488 12153-12174/com.sahooz.aidlsample D/AIDL: BookService fun[getBookById] execute on Binder_1
05-07 23:04:01.492 12090-12361/com.sahooz.aidlsample D/AIDL: Done getBookById call at: 1620399841492
05-07 23:04:07.926 12090-12090/com.sahooz.aidlsample D/AIDL: Call insert at: 1620399847926
05-07 23:04:07.932 12153-12175/com.sahooz.aidlsample D/AIDL: BookService fun[insert] execute on Binder_2
05-07 23:04:07.933 12090-12090/com.sahooz.aidlsample D/AIDL: Done insert call at: 1620399847932
05-07 23:04:08.932 12090-12111/com.sahooz.aidlsample D/AIDL: Insert call back at: 1620399848932
4.3 AIDL使用注意事项
4.3.1 线程问题(同步/异步)
默认情况下,Client调用远程方法的时候,会阻塞当前线程直至方法返回,如上述例子中的getBookById方法。注意,即使该方法返回值类型是void也不会改变阻塞。
oneway关键字用来声明远程方法的异步调用和串行化处理。异步调用不需要解析了,串行化处理是指对于一个服务端的 AIDL 接口而言,所有的 oneway 方法不会同时执行,binder 驱动会将他们串行化处理,排队一个一个调用。
需要注意的是,如果 oneway 用于本地调用,则不会有任何影响,调用仍是同步调用。
4.3.2 参数的in、out、inout
《AIDL oneway 以及in、out、inout参数的理解》:
in参数使得实参顺利传到服务方,但服务方对实参的任何改变,不会反应回调用方。
out参数使得实参不会真正传到服务方,只是传一个实参的初始值过去(这里实参只是作为返回值来使用的,这样除了return那里的返回值,还可以返回另外的东西),但服务方对实参的任何改变,在调用结束后会反应回调用方。
inout参数则是上面二者的结合,实参会顺利传到服务方,且服务方对实参的任何改变,在调用结束后会反应回调用方。
其实inout,都是相对于服务方。in参数使得实参传到了服务方,所以是in进入了服务方;out参数使得实参在调用结束后从服务方传回给调用方,所以是out从服务方出来。
4.3.3 本地调用和远程调用
aidl文件生成的java文件的Stub内部类有如下静态方法:
public static com.sahooz.aidlsample.service.IBookService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.sahooz.aidlsample.service.IBookService))) {
return ((com.sahooz.aidlsample.service.IBookService)iin);
}
return new com.sahooz.aidlsample.service.IBookService.Stub.Proxy(obj);
}
可以看到,本地调用和远程调用,asInterface方法返回值类型是不一样的,后者为代理对象。
4.3.4 序列化
除了基本数据类型,其他需要用作传参的数据类型需要实现Parcelable接口。
4.3.5 数据量限制
Binder传输数据是有大小限制的,Binder事务缓冲区的大小限定为1MB。在数据量过大的情况下,采用适当的辅助措施。
五、多进程的好处
- 突破进程内存限制:如图库占用资源过多。
- 功能稳定性:独立的通信进程保持长连接的稳定性。
- 规避系统内存泄漏:独立的WebView进程,阻隔内存泄漏导致的问题。
- 隔离风险:对于不稳定的功能放到独立进程,避免导致主进程崩溃。
六、 参考文章
- Android Binder设计与实现 - 设计篇:https://blog.csdn.net/universus/article/details/6211589
- 认真分析mmap:是什么 为什么 怎么用:https://www.cnblogs.com/huxiao-tee/p/4660352.html
- 程序内存地址==>虚拟内存==>物理内存:https://github.com/Durant35/durant35.github.io/issues/24
- 为什么 Android 要采用 Binder 作为 IPC 机制?:https://www.zhihu.com/question/39440766/answer/89210950
- 绑定服务概览:https://developer.android.com/guide/components/bound-services
- 看你简历上写熟悉 AIDL,说一说 oneway 吧:https://juejin.cn/post/6844904147947356173
- AIDL oneway 以及in、out、inout参数的理解:https://blog.csdn.net/anlian523/article/details/98476033
- android aidl oneway用法:https://blog.csdn.net/u010164190/article/details/73292012
- Binder机制面试题:https://www.jianshu.com/p/4878e9834d1b