【Android】Android Binderのプロセス間通信AIDL例とソースコード解析

序文

ご存じのとおり、Android のプロセス間通信では Binder メカニズムが使用されます。Binder は Android システム独自のプロセス間通信方式で、mmp 関数を使用してプロセスのユーザー空間をカーネル空間のメモリ領域にマッピングし、データのコピーを不要にします。 Linux 上の IPC は、より効率的で安全です。この記事では、Binder の理解を深めるために、AIDL 関数と bindService 関数を組み合わせて、Android システムのアプリケーション層とフレームワーク層での Binder 通信を分析します。

AIDL

AIDL は Android インターフェース記述言語であり、プロセス間通信用のコードを自動的に生成して多くの作業を節約できるツールでもあります。道具なので特に必要ありません。著者は、AIDL によって生成されたコードを分析し、AIDL によって生成されたコードが IPC 通信をどのように処理するかを分析します。

AIDL の例

サーバ

ファイルを作成PersonController.aidl:

srcディレクトリにあるパッケージ名をcom.devnn.libservice右クリックしてAIDLファイルを作成し、フルネームを設定するPersonControllerと、同名のaidlファイルがaidlディレクトリに作成されます。

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

仮パラメーターの inout 変更は、データをサーバー プロセスに送信できることを示します。サーバー プロセスによるデータの変更は、クライアント プロセスにも同期されます。他に出入りする 2 つの方法があり、これは一方向の伝送を表します。in を使用すると、クライアントはサーバーによる人物オブジェクトの変更を認識できません。out を使用する場合、クライアントから送信されたフィールドは空であり、返されるデータはサーバーからのものです。

ファイルを作成Person.aidl:


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

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

parcelable Person;

ファイルを作成するにはPerson.java、Person クラスで次のParcelableインターフェイスを実装する必要があります。

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

Service クラスから継承する MyService クラスを作成します。

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

独立したプロセスで実行するにはMyService、マニフェスト宣言で新しいプロセスであることを示す必要があります。

<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" は、独立したプロセスであることを意味します。コロンで始まる場合は、子プロセスまたはプライベート プロセスであることを意味します。コロンで始まらない場合は、それを意味します。それは独立したプロセスです。com.devnn.xxx:MyService のように、プロセス名の途中でコロンを使用できないことに注意してください。

上記のコードは、次のように構造化された別の にlibservice module記述されています。

ここに画像の説明を挿入

クライアント

appという名前のモジュールでクライアント コードを作成しますClientActivityボタンは 3 つだけで、ID はそれぞれbtn1、関数はそれぞれ 、、に対応します。コードは次のとおりです。btn2btn3bindServicegetPersonsaddPersons

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

    }
}

操作インターフェースは次のとおりです。
ここに画像の説明を挿入
クライアント プロセスとサーバー プロセスの aidl ファイルは一貫している必要があり、サーバーの aidl はクライアントにコピーできます。クライアントが別のモジュールにあり、サービスが配置されているモジュールに依存している場合、aidl をコピーする必要はありません。

実行ログ

実行後、bindService ボタンをクリックして以下のようにログを出力します:
ここに画像の説明を挿入
Logcat でプロセス一覧を表示すると 2:
ここに画像の説明を挿入
getPersons ボタンをクリックすると以下のようなログが出力されます:
ここに画像の説明を挿入
クライアント プロセスが正常にデータを取得できていることがわかります。サーバープロセス。

addPerson ボタンをクリックすると、以下のようなログが出力されます。
ここに画像の説明を挿入
サーバープロセスは、クライアントプロセスから送信されたデータを正常に受信できることがわかります。

上記は AIDL の使用例であり、以下に AIDI 通信のプロセスを分析します。

AIDL コミュニケーション プロセス分析

プロジェクトがビルドされると、対応する Java コードがビルド ディレクトリに自動的に生成されます。

ここに画像の説明を挿入
PersonController.java コードは次のとおりです。

/*
 * 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;
}

asInterface注意深く観察すると、このインターフェイスにはメソッド、onTransactメソッドを持つスタブ静的抽象内部クラスがあり、Proxy静的内部クラスに注意する必要があることがわかります。

クライアント プロセス呼び出しPersonController.Stub.asInterface(service)このコードは、実際にはプロキシ オブジェクト Proxy を返します. getPersons メソッドと addPerson メソッドを呼び出す場合、Proxy プロキシ クラスの対応するメソッドを呼び出すことも同じです。

メソッドを例にとるとaddPerson、このメソッドは実際に JavaBean オブジェクトを Pacel 型のオブジェクトに変換してから、IBinder の transact メソッドを呼び出してプロセス間通信を開始します。

addPerson メソッドのプロセス間通信呼び出しの方法は以下の通りです。
mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply, 0);

mRemoteこれは、binderService を通じて取得されたサーバー プロセスの IBinder オブジェクトです。
最初のパラメーターは、Stub.TRANSACTION_getPersons呼び出すメソッド ID を示します。
2 番目のパラメータ_dataはサーバーに送信されるデータで
、3 番目のパラメータ_replyはサーバーからクライアントに返されるデータです。
4 番目のパラメーターは、0同期であり、サーバーからデータが返されるまで待機する必要があることを示します。1 は、非同期であり、サーバーからデータが返されるまで待機する必要がないことを示します。

全体として、Proxyこのクラスはクライアントが使用するコードです。

Stubこのクラスは、サーバーによって使用されるコードです。

注: 通信を開始するのがクライアントであり、通信を受信するのがサーバーです。アプリと ams の間の通信など、プロセスが同時にクライアントとサーバーとして機能する場合があり、アプリはクライアントとサーバーの両方として機能します。

StubクラスのメソッドはonTransact、サーバーが呼び出されたときのコールバック関数です。
boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

最初のパラメーターcodeは呼び出されたメソッド ID を示し、
2 番目のパラメーターはdata呼び出し元によって送信されたデータを示し、
3 番目のパラメーターはreplyサーバーが返す必要があるデータを示します。4
番目のパラメーター flags は、それが同期か非同期かを示します。

このonTransactメソッドは、バインダー サーバーのバインダー スレッド プール、つまり子スレッドで呼び出されることに注意してください。MyServiceしたがって、上記のメソッドは子スレッドで実行されます。メイン スレッドと通信する必要がある場合は、Handler を使用する必要がありますgetPersonsaddPersons

上記の分析の結果、プロセス間通信は、実際には IBinder のトランザクション メソッドを呼び出して、相手の IBinder 参照を取得した後に Parcel 型のデータを送受信することによって通信していることがわかります。実際、AIDL は Binder 呼び出しのプロセスを簡素化し、通信コードを自動的に生成するのに役立ちます。必要に応じて、関連するコード通信を自分で作成することもできます。

では、クライアント コールbindServcieメソッドはどのようにしてサーバー プロセスの IBinder オブジェクトを取得するのでしょうか? この部分には Android フレームワーク レイヤーが含まれます。ここでは、その一般的なフロー分析を示します。

bindService プロセス分析

bindService メソッドが呼び出されると、ContextWrapper の bindService メソッドが実際に呼び出され、Activity は ContextWrapper から継承されます。以下はAndroid 10のソースコードを元に、コールチェーンをフローチャートで表したものです。

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对象,可以通信了

全体として、クライアント プロセスはサーバー プロセスと通信する必要があり、まずサーバーのバインダー オブジェクトを取得する必要があり、途中で AMS (Activity Manager Service) サービスを仲介として使用する必要があります。最初に AMS への要求を開始し (bindService、ServiceConnection オブジェクトを運ぶ)、AMS はサーバー プロセスと通信し、サーバー プロセスはバインダーを AMS に送信し、AMS は ServiceConnection の onServiceConnected コールバックを介してクライアント プロセスにバインダーを送信します。クライアントはバインダーを取得した後、transact メソッドを呼び出してデータを送信できます。

以上で、AIDL と Binder のプロセス間通信についての紹介を終わります。

おすすめ

転載: blog.csdn.net/devnn/article/details/127028386