[Android] Android Binder inter-process communication AIDL example and source code analysis

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.aidlfile:

com.devnn.libserviceRight-click on the package name in the src directory to create an AIDL file and set the full name PersonController, 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.aidlfile:


// Person.aidl
package com.devnn.libservice;

// Declare any non-default types here with import statements

parcelable Person;

To create Person.javaa file, the Person class needs to implement Parcelablethe 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 MyServicerun 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 moduleThe above code is written in a separate , structured as follows:

insert image description here

client

appCreate the client code in the module, named as , ClientActivitythere are only three buttons, the ids are btn1, , btn2respectively 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:
insert image description here
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:
insert image description here
View the process list in Logcat and there are 2:
insert image description here
click the getPersons button, the output log is as follows:
insert image description here
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:
insert image description here
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:

insert image description here
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 asInterfacemethod, onTransacta method, and Proxythe 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 addPersonthe 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);

mRemoteIt is the IBinder object of the server process obtained through binderService.
The first parameter Stub.TRANSACTION_getPersonsindicates the method ID to call.
The second parameter _datais the data to be sent to the server
and the third parameter _replyis the data returned by the server to the client.
The fourth parameter 0indicates 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, Proxythis class is the code used by the client.

StubThis 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.

StubThe method of the class onTransactis 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 codeindicates the called method ID,
the second parameter dataindicates the data sent by the caller,
and the third parameter replyindicates the data that the server needs to return.
The fourth parameter flags indicates whether it is synchronous or asynchronous.

Note that this onTransactmethod is called in the binder thread pool of the binder server, that is, in the child thread. MyServiceSo the above methods getPersonsare addPersonsrun 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 bindServciemethod 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.

Created with Raphaël 2.3.0 activity.bindService ContextWrapper.bindService ContextImpl.bindService ContextImpl.bindServiceCommon(从ServiceManager获取AMS) ActivityManagerService.bindIsolatedService(到了AMS进程中) ActiveServices.bindServiceLocked(AMS进程中) ActiveServices.requestServiceBindingLocked(AMS进程中) ServiceRecord.ProcessRecord.IApplicationThread.scheduleBindService(这里回到了客户端进程的ActivityThread中) 注意:ApplicationThread是IApplicationThread实现类且是ActivityThread内部类 sendMessage(H.BIND_SERVICE, s); 发送handler消息给客户端主线程处理服务绑定 ActivityThread.handleBindService 获取对应service,执行service.onBind回调。ActivityThread源代码: IBinder binder = s.onBind(data.intent); ActivityManager.getService().publishService( data.token, data.intent, binder); 这里将服务端的binder又通过AMS发送了出去,AMS里将这个binder通过客户端的ServiceConnection回调了过去。 ActivityManagerService.publishService ActiveServices.publishServiceLocked 找到客户端的ServiceConnection执行onServiceConnected回调。 ServiceConnection.onServiceConnected:客户端获取到了服务端的IBinder对象,可以通信了

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.

Guess you like

Origin blog.csdn.net/devnn/article/details/127028386