Overview of Android Binder mechanism

1, What is it

Binder is an inter process communication mechanism of Android system. It is mainly composed of the following parts: virtual binder device (/ dev/binder), binder driver (binder. C in the kernel), ServiceManager, Service providing Service and Client invoking Service.

Binder device and binder driver realize the protocol of process communication and the details of data exchange. ServiceManager is responsible for the unified management of service and the authentication of Client. Service provides specific services, and Client requests services to meet its own needs.

2, Comparison with other cross process communication mechanisms

Android considers using Binder as the main cross process communication mechanism instead of Socket communication mechanism, which is based on the comprehensive consideration of performance, stability, security and complexity.

2.1 performance

In terms of speed / performance, shared memory is undoubtedly the best. Socket, etc. at least two memory copies are required (process A user space - > kernel space - > process B user space). Binder needs A memory copy (process A user space - > kernel space), and its performance is between the two.

IPCNumber of data copies
Shared memory0
Binder1
Socket / pipe / message queue2 or more

2.2 ease of use and stability

Binder is based on C/S architecture, with clear responsibilities and no interference between processes; Shared memory is a bit fast, but it needs to rely on other synchronization tools to access critical resources, which is relatively complex to use; In addition, using Socket, the communication mechanism similar to binder's C/S architecture can also be realized, but the disadvantage is that multiple memory copies are required.

2.3 safety

Design and implementation of Android Binder: first, the receiver of traditional IPC cannot obtain the reliable UID/PID (user ID / process ID) of the other party's process, so it is unable to identify the other party's identity. Android assigns its own UID to each installed application, so the UID of the process is an important sign to identify the process. Using traditional IPC, users can only fill in UID/PID in the data packet, but it is unreliable and easy to be used by malicious programs. Reliable identity tags can only be added in the kernel by the IPC mechanism itself. Secondly, the traditional IPC access point is open and cannot establish a private channel. For example, the name of the named pipeline, the key value of system V, and the ip address or file name of the socket are all open. As long as you know these access points, the program can establish a connection with the opposite end. In any case, it can not prevent the malicious program from obtaining the connection by guessing the address of the receiver.

Based on the above reasons, Android needs to establish a new IPC mechanism to meet the system's requirements for communication mode, transmission performance and security, which is binder. Binder is based on the client server communication mode. Only one copy is needed in the transmission process. UID/PID identity is added for sending. It supports both real name binder and anonymous binder with high security.

2.4 others

According to this Know how to answer The use of Binder also separates the considerations of GPL protocol and object-oriented design.

3, Why only need to copy once

Its core principle is very simple, that is, memory mapping. To understand memory mapping, you need to understand the concepts of physical address and virtual address, user space and kernel space. There are many excellent articles on the Internet, which are analyzed very clearly and will not be repeated here.
Borrow a widely circulated picture to analyze a copy:

4, Using Messenger and AIDL

AIDL is the abbreviation of Android Interface Description Language, that is, Android interface definition language. As we all know, AIDL is implemented based on Binder mechanism, while Messager is based on AIDL.

4.1 use of Messager

Messager is a relatively simple process communication. The example is as follows (a little modification is made based on the official example):
Service end:

/** 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 side:

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 use of Aidl

According to the official document, if you want to create a binding service using AIDL, please perform the following steps:

  1. establish. aidl file
    This file defines the programming interface with the method signature.

  2. Implementation interface
    Android SDK tools will be based on your AIDL file, using Java programming language to generate interface. This interface has an internal abstract class named Stub, which is used to extend Binder class and implement methods in AIDL interface. You must extend the Stub class and implement these methods.

  3. Expose interfaces to clients
    Implement Service and override onBind() to return the implementation of the Stub class.

Let's have a try. First look at the project directory:

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}")
            // Simulate time-consuming operations
            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}")
            // Simulate time-consuming operations
            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 precautions for Aidl use

4.3.1 thread problems (synchronous / asynchronous)

By default, when the Client calls a remote method, the current thread will be blocked until the method returns, such as the getBookById method in the above example. Note that even if the return value type of this method is void, the blocking will not be changed.
The oneway keyword is used to declare asynchronous invocation and serialization of remote methods. Asynchronous calls do not need to be parsed. Serialization means that for a server AIDL interface, all oneway methods will not be executed at the same time. The binder driver will serialize them and queue up calls one by one.
It should be noted that if oneway is used for local calls, it will not have any impact. The calls are still synchronous calls.

4.3.2 in, out and inout of parameters

Understanding of AIDL oneway and in, out and inout parameters:
The in parameter enables the parameter to be transferred to the service side smoothly, but any change of the service side to the parameter will not reflect the callback to the user.
The out parameter makes the argument not really transmitted to the service party, but only the initial value of an argument (here, the argument is only used as the return value, so that in addition to the return value of return, other things can be returned). However, any change made by the service party to the argument will respond to the callback user after the call is completed.
The inout parameter is the combination of the above two. The actual parameter will be successfully transmitted to the service party, and any change of the service party to the actual parameter will respond to the callback user after the call is completed.
In fact, inout is relative to the service provider. The in parameter makes the actual parameter pass to the service party, so in enters the service party; The out parameter causes the argument to be passed back from the service side to the caller after the call, so it is out from the service side.

4.3.3 local call and remote call

The Stub internal class of the java file generated by the aidl file has the following static methods:

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);
}

It can be seen that the return value type of asInterface method is different between local call and remote call, and the latter is a proxy object.

4.3.4 serialization

In addition to the basic data types, other data types that need to be used as parameters need to implement the Parcelable interface.

4.3.5 data volume limit

There is a size limit for Binder transmission data, and the size of Binder transaction buffer is limited to 1MB. When the amount of data is too large, appropriate auxiliary measures shall be taken.

5, Benefits of multiple processes

  • Break through the process memory limit: for example, the library occupies too many resources.
  • Functional stability: independent communication processes maintain the stability of long connections.
  • Avoid system memory leakage: an independent WebView process blocks the problems caused by memory leakage.
  • Isolation risk: put unstable functions into independent processes to avoid causing the main process to crash.

6, Reference articles

  • Android Binder design and Implementation - Design: https://blog.csdn.net/universus/article/details/6211589
  • Carefully analyze mmap: what, why and how to use it: https://www.cnblogs.com/huxiao-tee/p/4660352.html
  • Program memory address = = > virtual memory = = > physical memory: https://github.com/Durant35/durant35.github.io/issues/24
  • Why does Android adopt Binder as the IPC mechanism https://www.zhihu.com/question/39440766/answer/89210950
  • Binding service overview: https://developer.android.com/guide/components/bound-services
  • According to your resume, you are familiar with AIDL and say oneway: https://juejin.cn/post/6844904147947356173
  • Understanding of AIDL oneway and in, out and inout parameters: https://blog.csdn.net/anlian523/article/details/98476033
  • android aidl oneway usage: https://blog.csdn.net/u010164190/article/details/73292012
  • Binder mechanism interview questions: https://www.jianshu.com/p/4878e9834d1b

Keywords: Android aidl

Added by cosmos33 on Thu, 17 Feb 2022 16:29:05 +0200