Android Binder机制概述

一、 是什么

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
Binder1
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 创建绑定服务,请执行以下步骤:

  1. 创建 .aidl 文件
    此文件定义带有方法签名的编程接口。

  2. 实现接口
    Android SDK 工具会基于您的 .aidl 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现这些方法。

  3. 向客户端公开接口
    实现 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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值