序文
ご存じのとおり、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
、、関数はそれぞれ 、、に対応します。コードは次のとおりです。btn2
btn3
bindService
getPersons
addPersons
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 を使用する必要がありますgetPersons
。addPersons
上記の分析の結果、プロセス間通信は、実際には IBinder のトランザクション メソッドを呼び出して、相手の IBinder 参照を取得した後に Parcel 型のデータを送受信することによって通信していることがわかります。実際、AIDL は Binder 呼び出しのプロセスを簡素化し、通信コードを自動的に生成するのに役立ちます。必要に応じて、関連するコード通信を自分で作成することもできます。
では、クライアント コールbindServcie
メソッドはどのようにしてサーバー プロセスの IBinder オブジェクトを取得するのでしょうか? この部分には Android フレームワーク レイヤーが含まれます。ここでは、その一般的なフロー分析を示します。
bindService プロセス分析
bindService メソッドが呼び出されると、ContextWrapper の bindService メソッドが実際に呼び出され、Activity は ContextWrapper から継承されます。以下はAndroid 10のソースコードを元に、コールチェーンをフローチャートで表したものです。
全体として、クライアント プロセスはサーバー プロセスと通信する必要があり、まずサーバーのバインダー オブジェクトを取得する必要があり、途中で AMS (Activity Manager Service) サービスを仲介として使用する必要があります。最初に AMS への要求を開始し (bindService、ServiceConnection オブジェクトを運ぶ)、AMS はサーバー プロセスと通信し、サーバー プロセスはバインダーを AMS に送信し、AMS は ServiceConnection の onServiceConnected コールバックを介してクライアント プロセスにバインダーを送信します。クライアントはバインダーを取得した後、transact メソッドを呼び出してデータを送信できます。
以上で、AIDL と Binder のプロセス間通信についての紹介を終わります。