foreword
As we all know, the Android inter-process communication uses the Binder mechanism. Binder is a unique inter-process communication method of the Android system. It uses the mmp function to map the user space of the process with a memory area of the kernel space, eliminating the need for a data copy. Compared with the traditional IPC on Linux, it is more efficient and safe. The advantages. This article combines AIDL and bindService functions to analyze the Binder communication in the application layer and Framework layer of the Android system in order to deepen the understanding of Binder.
AIDL
AIDL is an Android interface description language. It is also a tool that can help us automatically generate code for inter-process communication, saving a lot of work. Since it's a tool, it's not really necessary. The author analyzes the code generated by AIDL and analyzes how the code generated by it processes IPC communication.
AIDL example
Server
Create PersonController.aidl
file:
com.devnn.libservice
Right-click on the package name in the src directory to create an AIDL file and set the full namePersonController
, and an aidl file with the same name will be created in the aidl directory.
// PersonController.aidl
package com.devnn.libservice;
import com.devnn.libservice.Person;
// Declare any non-default types here with import statements
interface PersonController {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List<Person> getPersons();
void addPerson(inout Person person);
}
The inout modification on the formal parameter indicates that the data can be sent to the server process, and the modification of the data by the server process will also be synchronized to the client process. There are two other ways in and out, which represent one-way transmission. When in is used, the client cannot perceive the modification of the person object by the server. When using out, the field sent by the client is empty, and the returned data is from the server.
Create Person.aidl
file:
// Person.aidl
package com.devnn.libservice;
// Declare any non-default types here with import statements
parcelable Person;
To create Person.java
a file, the Person class needs to implement Parcelable
the interface:
package com.devnn.libservice
import android.os.Parcel
import android.os.Parcelable
class Person(var name: String?, var age: Int) : Parcelable {
constructor(parcel: Parcel) : this(parcel.readString(), parcel.readInt()) {
}
/**
* 字段写入顺序和读取顺序要保持一致
*/
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeInt(age)
}
/**
* readFromParcel不是必需的,在aidl文件中函数参数类型是inout时需要。
*/
fun readFromParcel(parcel: Parcel) {
name = parcel.readString()
age = parcel.readInt()
}
/**
* 默认即可
*/
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Person> {
override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}
override fun newArray(size: Int): Array<Person?> {
return arrayOfNulls(size)
}
}
}
Create a MyService class that inherits from the Service class:
package com.devnn.libservice
import android.app.Application
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.os.Build
import android.util.Log
import java.util.ArrayList
class MyService : Service() {
override fun onCreate() {
super.onCreate()
Log.d("MyService", "onCreate")
/**
* 为了证明是在新进程中运行,将进程名打印出来
* 在android 33设备上运行的。
*/
if (Build.VERSION.SDK_INT >= 28) {
val processName = Application.getProcessName()
Log.d("MyService", "processName=$processName")
}
}
override fun onBind(intent: Intent): IBinder? {
Log.d("MyService", "onBind")
return binder
}
private val binder: Binder = object : PersonController.Stub() {
override fun getPersons(): List<Person> {
Log.d("MyService", "getPersons")
val list: MutableList<Person> = ArrayList()
list.add(Person("张三", 20))
list.add(Person("李四", 21))
return list
}
override fun addPerson(person: Person) {
Log.d("MyService", "addPerson")
Log.d("MyService", "name=${
person.name},age=${
person.age}")
}
}
}
In order to MyService
run in an independent process, it is necessary to indicate that it is a new process in the Manifest declaration:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.devnn.libservice">
<application>
<service
android:name=".MyService"
android:process="com.devnn.libservice.MyService"></service>
</application>
</manifest>
android:process="com.devnn.libservice.MyService" means it is an independent process. If it starts with a colon, it means it is a child process or a private process. If it does not start with a colon, it means it is an independent process. Note that a colon cannot be used in the middle of the process name, such as this: com.devnn.xxx:MyService.
libservice module
The above code is written in a separate , structured as follows:
client
app
Create the client code in the module, named as , ClientActivity
there are only three buttons, the ids are btn1
, , btn2
respectively btn3
, and the functions correspond to bindService
, getPersons
, respectively addPersons
. The code is as follows:
package com.devnn.demo
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.devnn.libservice.Person
import com.devnn.libservice.PersonController
import com.devnn.demo.databinding.ActivityClientBinding
class ClientActivity : AppCompatActivity() {
private val binding by lazy {
ActivityClientBinding.inflate(this.layoutInflater)
}
private lateinit var personController: PersonController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
//bindService
binding.btn1.setOnClickListener {
val intent = Intent().apply {
component =
ComponentName(this@ClientActivity.packageName, "com.devnn.libservice.MyService")
}
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
//getPersons
binding.btn2.setOnClickListener {
personController?.let {
val list = it.persons;
list?.map {
Log.i("ClientActivity", "person:name=${
it.name},age=${
it.age}")
}
}
}
//addPerson
binding.btn3.setOnClickListener {
personController?.let {
val person = Person("王五", 22)
it.addPerson(person)
}
}
}
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i("ClientActivity", "onServiceConnected")
personController = PersonController.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.i("ClientActivity", "onServiceDisconnected")
}
}
}
The operation interface is as follows:
the aidl files of the client and server processes must be consistent, and the aidl of the server can be copied to the client. If the client is in a separate module and depends on the module where the service is located, it is not necessary to copy aidl.
run log
After running, click the bindService button to output the log as follows:
View the process list in Logcat and there are 2:
click the getPersons button, the output log is as follows:
You can see that the client process can normally obtain the data of the server process.
Click the addPerson button, and the output log is as follows:
You can see that the server process can normally receive the data sent by the client process.
The above is an example of the use of AIDL, and the process of AIDI communication is analyzed below.
AIDL communication process analysis
After the project is built, the corresponding Java code will be automatically generated in the build directory:
PersonController.java code is as follows:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.devnn.libservice;
// Declare any non-default types here with import statements
public interface PersonController extends android.os.IInterface
{
/** Default implementation for PersonController. */
public static class Default implements com.devnn.libservice.PersonController
{
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException
{
return null;
}
@Override public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.devnn.libservice.PersonController
{
private static final java.lang.String DESCRIPTOR = "com.devnn.libservice.PersonController";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.devnn.libservice.PersonController interface,
* generating a proxy if needed.
*/
public static com.devnn.libservice.PersonController asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.devnn.libservice.PersonController))) {
return ((com.devnn.libservice.PersonController)iin);
}
return new com.devnn.libservice.PersonController.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getPersons:
{
data.enforceInterface(descriptor);
java.util.List<com.devnn.libservice.Person> _result = this.getPersons();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addPerson:
{
data.enforceInterface(descriptor);
com.devnn.libservice.Person _arg0;
if ((0!=data.readInt())) {
_arg0 = com.devnn.libservice.Person.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
if ((_arg0!=null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.devnn.libservice.PersonController
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.devnn.libservice.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getPersons();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.devnn.libservice.Person.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((person!=null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addPerson(person);
return;
}
_reply.readException();
if ((0!=_reply.readInt())) {
person.readFromParcel(_reply);
}
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.devnn.libservice.PersonController sDefaultImpl;
}
static final int TRANSACTION_getPersons = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.devnn.libservice.PersonController impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.devnn.libservice.PersonController getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException;
public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException;
}
If you observe carefully, you will find that this interface has a Stub static abstract inner class, which has a asInterface
method, onTransact
a method, and Proxy
the static inner class is what we need to pay attention to.
Client process call PersonController.Stub.asInterface(service)
This code actually returns the proxy object Proxy. When calling the getPersons and addPerson methods, it is also equivalent to calling the corresponding method in the Proxy proxy class.
Take addPerson
the method as an example, this method actually converts the JavaBean object into an object of Pacel type, and then calls the transact method of IBinder to start inter-process communication.
The method of interprocess communication call of addPerson method is as follows:
mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply, 0);
mRemote
It is the IBinder object of the server process obtained through binderService.
The first parameter Stub.TRANSACTION_getPersons
indicates the method ID to call.
The second parameter _data
is the data to be sent to the server
and the third parameter _reply
is the data returned by the server to the client.
The fourth parameter 0
indicates that it is synchronous and needs to wait for the server to return data, and 1 indicates that it is asynchronous and does not need to wait for the server to return data.
Overall, Proxy
this class is the code used by the client.
Stub
This class is the code used by the server.
Note: Whoever initiates the communication is the client, and the one who receives the communication is the server. Sometimes a process acts as a client and a server at the same time, such as the communication between an app and ams, and the app acts as both a client and a server.
Stub
The method of the class onTransact
is the callback function when the server is called.
boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
The first parameter code
indicates the called method ID,
the second parameter data
indicates the data sent by the caller,
and the third parameter reply
indicates the data that the server needs to return.
The fourth parameter flags indicates whether it is synchronous or asynchronous.
Note that this
onTransact
method is called in the binder thread pool of the binder server, that is, in the child thread.MyService
So the above methodsgetPersons
areaddPersons
run in the child thread, if you need to communicate with the main thread, you have to use Handler.
After the above analysis, it can be found that inter-process communication actually communicates by calling the transact method of IBinder to send and receive Parcel type data after getting the IBinder reference of the other party. AIDL actually simplifies the process of Binder calling and helps us automatically generate communication code. We can also write relevant code communication ourselves as needed.
So how does the client call bindServcie
method get the IBinder object of the server process? This part involves the android framewok layer, here is a general flow analysis of it.
bindService process analysis
When the bindService method is called, the bindService method of the ContextWrapper is actually called, and the Activity is inherited from the ContextWrapper. The following is based on the source code of Android 10, and the call chain is represented by a flow chart.
On the whole, the client process needs to communicate with the server process. First, it needs to obtain the binder object of the server. In the middle, the AMS (Activity Manager Service) service needs to be used as an intermediary. First initiate a request to AMS (bindService, carrying a ServiceConnection object), AMS then communicates with the server process, the server process sends the binder to AMS, and AMS sends the binder to the client process through the ServiceConnection's onServiceConnected callback. After the client obtains the binder, it can call the transact method to send data.
OK, this is the end of the introduction about AIDL and Binder inter-process communication.