ホームページ  >  記事  >  类库下载  >  Android プロセス通信メカニズム AIDL

Android プロセス通信メカニズム AIDL

高洛峰
高洛峰オリジナル
2016-10-31 11:32:301726ブラウズ

AIDLとは

AIDLとはAndroid Interface Definition Languageの略で、Android Interface description Languageのことです。とても奥が深いように思えますが、本質はプロセス間通信インターフェースを生成するための補助ツールです。これは .aidl ファイルの形式で存在します。開発者が行う必要があるのは、コンパイル時にファイル内でプロセス間通信用のインターフェイスを定義することだけです。IDE は、プロジェクト ベースで使用できる .java ファイルを生成します。 .aidl インターフェース ファイル上にあります。これは、いわゆる「糖衣構文」に似ています。

AIDL の構文は Java の構文と同じですが、パッケージのインポートには若干の違いがあります。 Java では、2 つのクラスが同じパッケージ内にある場合、パッケージをインポートする必要はありませんが、AIDL では、パッケージのインポート宣言を行う必要があります。

AIDL の詳細な説明

シナリオを考えてみましょう: CS モードを通じて実装される図書館管理システムがあります。特定の管理機能はサーバー プロセスによって実装され、クライアントは対応するインターフェイスを呼び出すだけで済みます。

次に、最初にこの管理システムの ADIL インターフェイスを定義します。

/rc に新しい Aidl パッケージを作成します。このパッケージには、Book.java、Book.aidl、および IBookManager.aidl の 3 つのファイルがあります。

package com.example.aidl book

public class Book implements Parcelable {
  int bookId;
  String bookName;

  public Book(int bookId, String bookName) {
     this.bookId = bookId;
     this.bookName = bookName;
  }

  ...
}

package com.example.aidl;

Parcelable Book;

package com.example.aidl;

import com.example.aidl.Book;

inteface IBookManager {
   List<Book> getBookList();
   void addBook(in Book book);
}

以下はこれら 3 つのファイルの説明です:

Book.java は私たちが定義したエンティティ クラスであり、Parcelable インターフェイスを実装しているため、Book クラスはプロセス間で転送されます。

Book.aidl は、AIDL におけるこのエンティティ クラスの宣言です。

IBookManager は、サーバーとクライアント間の通信用のインターフェイスです。 (AIDL インターフェースの基本型に加えて、方向をパラメーターの前に追加する必要があることに注意してください。in は入力パラメーターを表し、out は出力パラメーターを表し、inout は入力パラメーターと出力パラメーターを表します。)

コンパイラーがコンパイルされた後、androidこのプロジェクトには .java ファイルが自動的に生成されます。このファイルには IBookManager、Stub、Proxy という 3 つのクラスが含まれています。これら 3 つのクラスはすべて完全に分離できます。以下のように:

IBookManager

public interface IBookManager extends android.os.IInterface {

    public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException;

    public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException;
}

Stub

public static abstract class Stub extends android.os.Binder implements net.bingyan.library.IBookManager {
        private static final java.lang.String DESCRIPTOR = "net.bingyan.library.IBookManager";

        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an net.bingyan.library.IBookManager interface,
         * generating a proxy if needed.  http://www.manongjc.com/article/1501.html
         */
        public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) {
                return ((net.bingyan.library.IBookManager) iin);
            }
            return new net.bingyan.library.IBookManager.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 {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    net.bingyan.library.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<net.bingyan.library.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
}

Proxy

private static class Proxy implements net.bingyan.library.IBookManager {
            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.   http://www.manongjc.com/article/1500.html
             */
            @Override
            public void addBook(net.bingyan.library.Book book) 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 ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<net.bingyan.library.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
       }

生成された 3 つのクラスの説明は次のとおりです:

IBookManager このクラスは、Android Studio が親クラスを追加して使用できるようにするインターフェイスです。インターフェイス android.os.interface にはメソッド IBinder asBinder() が 1 つしかないため、IBookManager には 3 つのメソッドが実装されており、これはサーバー プロセスとクライアント プロセス間の通信用のウィンドウです。

Stub は、android.os.Binder クラスを継承し、IBookManager インターフェイスを実装する抽象クラスです。スタブでは、asBinder() インターフェイス メソッドが実装されており、それを継承して実装するサブクラス用に定義した 2 つの AIDL インターフェイス メソッドがあります。これはサーバー側で使用されるため、サーバーはこれら 2 つのメソッドを実装する必要があります。

Proxy 名前が示すように、これはクライアント上のサーバーのプロキシでもあり、IBookManager インターフェイスを実装し、IBookManager のすべてのメソッドを実装します。これはクライアント側で使用され、クライアント側のサーバーのプロキシになります。

ここで、これら 3 つのクラスを 1 つずつ分析します。

IBookManager このクラスについては何も言うことはありません。単に asInterface インターフェイスを継承しており、その機能は IBookManager を IBinder に変換することです。

プロキシ このクラスは、プロセス間通信メカニズムのカプセル化クラスであり、その内部実装メカニズムは、構築メソッドを通じて簡単に確認できます。そのコンストラクターは、明らかにサーバーを表す、remote という名前の IBinder 型パラメーターを受け入れます。このクラスのメソッド addBook() と getBookList() を見てみましょう:

@Override
public void addBook(net.bingyan.library.Book book) 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 ((book != null)) {
                _data.writeInt(1);
                book.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
            _reply.readException();
       } finally {
            _reply.recycle();
            _data.recycle();
       }
}
@Override /* http://www.manongjc.com/article/1547.html */
public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException {
       android.os.Parcel _data = android.os.Parcel.obtain();
       android.os.Parcel _reply = android.os.Parcel.obtain();
       java.util.List<net.bingyan.library.Book> _result;
       try {
             _data.writeInterfaceToken(DESCRIPTOR);
             mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
             _reply.readException();
             _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
       } finally {
            _reply.recycle();
            _data.recycle();
       }
       return _result;
}

これら 2 つのメソッドには多くの類似点があり、ここで明らかになります: これら 2 つのメソッドはクライアントのプロセスです。サーバープロセスのウィンドウを呼び出します。これら 2 つのメソッドの先頭では、両方とも 2 つの Parcel (中国語訳: パッケージ) オブジェクトを定義します。 Parcel クラスはよく知られています。はい、Book クラスの writeToParcel() と CREATOR の createFromParcel() のパラメーターは Parcel タイプです。このクラスのドキュメントは次のように説明されています。

IBinder を通じて送信できるメッセージ (データおよびオブジェクト参照) のコンテナー。パーセルには、IPC の反対側で非フラット化されるフラット化されたデータの両方を含めることができます (特定のタイプを記述するためのさまざまなメソッドを使用するか、一般的な {@link Parcelable} インターフェイス)、およびライブ {@link IBinder} オブジェクトへの参照。これにより、反対側は、Parcel 内の元の IBinder に接続されたプロキシ IBinder を受信します。

翻訳: プロキシは、 IBinder メッセージング用のコンテナー。パーセルには、IPC のもう一方の端で逆シリアル化されるシリアル化可能なデータを含めることができます。また、IBinder オブジェクトへの参照を含めることもできます。これにより、もう一方の端は、IBinder タイプのプロキシ オブジェクトを受信します。このプロキシ オブジェクトは接続されています。 Parcel 内の元の IBinder オブジェクト。

以下は画像付きの視覚的な説明です:

Android プロセス通信メカニズム AIDL

図に示されているように、サーバーがデータ パッケージとして Parcel を使用し、クライアントとの通信に Binder に依存していることが直感的にわかります。データ パッケージはシリアル化後のオブジェクトです。

前述したように、これら 2 つのメソッドは、それぞれ _data と _reply と呼ばれる 2 つの Parcel オブジェクトを定義します。比喩的に言えば、クライアントの観点から見ると、_data はクライアントからサーバーに送信されるデータ パッケージであり、_reply はサーバーのデータです。クライアントに送信されたパッケージ。

その後、これら 2 つのオブジェクトを使用してサーバーと通信し始めました。両方のメソッドに mRemote.transact() というメソッド呼び出しがあり、最初のパラメーターは次のとおりです。その意味については後で説明します。 2 番目のパラメータ _data は、インターフェース メソッドのパラメータなどのデータ パッケージをサーバーに送信する役割を果たし、3 番目のパラメータ _reply は、インターフェース メソッドの戻り値などのデータ パッケージをサーバーから受信する役割を果たします。このコード行には単純なメソッド呼び出ししかありませんが、これは AIDL 通信の中核部分であり、実際にはリモート メソッド呼び出しを行います (クライアントは、ローカル プロキシ Proxy によって公開されるインターフェイス メソッドを通じて、同じ名前のサーバー スタブ メソッドを呼び出します)。 )、時間のかかる操作であると考えることができます。

この例では:

void addBook(Book book) は、パラメーター Book:book をサーバーに送信するために _data を使用する必要があります。送信方法は、実装されている writeToParcel(Parcel out) メソッドを通じて Book を _data にパッケージ化することです。ご想像のとおり、_data は実際には Book でのこのメソッドの実装を覚えていますか? Book のフィールドを 1 つずつ Parcel にパッケージ化します。

List getBookList() は、サーバーから戻り値 List:books を受け取るために _reply を使用する必要があります。このメソッドは、Book の静的フィールド CREATOR をパラメーターとして _reply の createTypedArrayList() メソッドに渡すことです。クリエイターを予約しますか?そのとき、この静的フィールドの使い方に興味がありましたか?これですべてが明らかになりました。サーバー側でデータを逆シリアル化し、シリアル化可能なオブジェクトまたはオブジェクト配列を再生成するには、このオブジェクト (理解しやすいように「デシリアライザー」と呼ぶことができます) に依存する必要があります。明らかに、CREATOR は _reply の助けを借りて List:books を生成しました。

もちろん、これら 2 つのメソッドの _data と _reply は、オブジェクトを渡すだけでなく、いくつかの検証情報も渡します。これについては詳しく説明する必要はありませんが、Parcel のパッケージ化と解凍の順序は厳密である必要があることに注意してください。対応する。たとえば、最初のパックされた値が int:i の場合、最初のアンパックされた値もこの整数値である必要があります。つまり、パッケージ化時の最初の呼び出しが Parcel.writeInt(int) である場合、アンパック時の最初の呼び出しは Parcel.readInt() である必要があります。

この時点で、クライアントのプロキシについて説明しました。サーバーのスタブを見てみましょう。

Stub は IBookManager のメソッドの 1 つを実装します。これは非常に単純です。Stub 自体は Binder を継承し、Binder は IBinder を継承するため、問題はありません。 「実装されていないメソッドが 2 つあるのではないか?」と疑問に思うかもしれません。これら 2 つのメソッドは私たちが定義したインターフェイス メソッドであり、実装はサーバー プロセスに任せられます。つまり、サーバー プロセスでスタブ インプリメンターを定義する必要があります。以下は、スタブの 2 つの重要なメソッドの分析です:

IBookManager asInterface(IBinder obj)

public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) {
                return ((net.bingyan.library.IBookManager) iin);
            }
            return new net.bingyan.library.IBookManager.Stub.Proxy(obj);
        }

这个方法的作用是将 Stub 类转换成 IBookManager 这个接口,方法中有个判断:如果我们的服务端进程和客户端进程是同一进程,那么就直接将 Stub 类通过类型转换转成 IBookManager;如果不是同一进程,那么就通过代理类 Proxy 将 Stub 转换成 IBookManager。为什么这么做,我们知道如果服务端进程和客户端进程不是同一进程,那么它们的内存就不能共享,就不能通过一般的方式进行通信,但是我们如果自己去实现进程间通信方式,对于普通开发者来说成本太大,因此编译器帮我们生成了一个封装了了进程间通信的工具,也就是这个 Proxy,这个类对底层的进程通信机制进行了封装只同时暴露出接口方法,客户端只需要调用这两个方法实现进程间通信(其实就是方法的远程调用)而不需要了解其中的细节。

有了这个方法,我们在客户端可以借助其将一个 IBinder 类型的变量转换成我们定义的接口 IBookManager,它的使用场景我们会在后面的实例中进行讲解。

onTransact(int code, Parcel data, Parcel reply, int flags)

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
           switch (code) {
               case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
               }
               case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    net.bingyan.library.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true; /*  http://www.manongjc.com/article/1499.html */
               }
               case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<net.bingyan.library.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
           }
           return super.onTransact(code, data, reply, flags);
}

这个方法我们是不是也很熟悉呢?我们在 Proxy 中也看到一个类似得方法 transact(int, Parcel, Parcel, int),它们的参数一样,而且它们都是 Binder 中的方法,那么它们有什么联系呢?

前面说了,transact() 执行了一个远程调用,如果说 transact() 是远程调用的发起,那么 onTransact() 就是远程调用的响应。真实过程是客户端发器远程方法调用,android 系统通过底层代码对这个调用进行响应和处理,之后回调服务端的 onTransact() 方法,从数据包裹中取出方法参数,交给服务端实现的同名方法调用,最后将返回值打包返回给客户端。

需要注意的是 onTransact() 是在服务端进程的 Binder 线程池中进行的,这就意味着如果我们的要在 onTransact() 方法的中更新 UI,就必须借助 Handler。

这两个方法的第一个参数的含义是 AIDL 接口方法的标识码,在 Stub 中,定义了两个常量作为这两个方法的标示:

static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);   static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

如果 code == TRANSACTION_addBook,那么说明客户端调用的是 addBook();如果 code == TRANSACTION_getBookList,那么客户端调用的是 getBookList(),然后交由相应的服务端方法处理。 用一张图来表示整个通信过程:

Android プロセス通信メカニズム AIDL

了解了 AIDL 的整个过程,接下来就是 AIDL 在安卓程序中的应用了。

 

AIDL 的使用

相信大家应该都和清楚 Service 的使用了吧,Service 虽然称作“服务”,并且运行于后台,但是它们默认还是运行在默认进程的主线程中。其实让 Service 运行在默认进程中,有点大材小用了。android 的很多系统服务都运行于单独的进程中,供其他应用调用,比如窗口管理服务。这样做的好处是可以多个应用共享同一个服务,节约了资源,也便于集中管理各个客户端,要注意问题的就是线程安全问题。

那么接下来我们就用 AIDL 实现一个简单的 CS 架构的图书管理系统。

首先我们定义服务端:

BookManagerService

public class BookManagerService extends Service {

    private final List<Book> mLibrary = new ArrayList<>();

    private IBookManager mBookManager = new IBookManager.Stub() {
        @Override
        public void addBook(Book book) throws RemoteException {
            synchronized (mLibrary) {
                mLibrary.add(book);
                Log.d("BookManagerService", "now our library has " + mLibrary.size() + " books");
            }

        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mLibrary;
        }
    };
 
    @Override  /*  http://www.manongjc.com/article/1496.html  */
    public IBinder onBind(Intent intent) {
        return mBookManager.asBinder();
    }

}

     android:name=".BookManagerService"/>

服务端我们定义了 BookManagerService 这个类,在它里面我们创建了服务端的 Stub 对象,并且实现了需要实现的两个 AIDL 接口方法来定义服务端的图书管理策略。在 onBind() 方法中我们将 IBookManager 对象作为 IBinder 返回。我们知道,当我们绑定一个服务时,系统会调用 onBinder() 方法得到服务端的 IBinder 对象,并将其转换成客户端的 IBinder 对象传给客户端,虽然服务端的 IBinder 和 客户端的 IBinder 是两个 IBinder 对象,但他们在底层都是同一个对象。我们在 xml 中注册 Service 时给它指定了进程名,这样 Service 就能运行在单独的进程中了。

接下来看看客户端的实现:

Client

public class Client extends AppCompatActivity {

    private TextView textView;

    private IBookManager bookManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.library_book_manager_system_client);

        Intent i  = new Intent(Client.this, BookManagerService.class);
        bindService(i, conn, BIND_AUTO_CREATE);

        Button addABook = (Button) findViewById(R.id.button);
        addABook.setOnClickListener(v -> {
            if (bookManager == null) return;
            try {
                bookManager.addBook(new Book(0, "book"));
                textView.setText(getString(R.string.book_management_system_book_count, String.valueOf(bookManager.getBookList().size())));
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        });

        textView = (TextView) findViewById(R.id.textView);
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("Client -->", service.toString());

            bookManager = IBookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("Client", name.toString());
        }
    };

}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="1"
    android:gravity="center">

    <Button
        android:text="http://www.manongjc.com/article/1495.html"
        android:layout_width="111dp"
        android:layout_height="wrap_content"
        android:id="@+id/button" />

    <TextView
        android:layout_marginTop="10dp"
        android:text="@string/book_management_system_book_count"
        android:layout_width="231dp"
        android:gravity="center"
        android:layout_height="wrap_content"
        android:id="@+id/textView" />
</LinearLayout>

我们的客户端就是一个 Activity,onCreate() 中进行了服务的绑定,bindService() 方法中有一参数 ServiceConnection:conn,因为绑定服务是异步进行的,这个参数的作用就是绑定服务成功后回调的接口,它有两个回调方法:一个是连接服务成功后回调,另一个在与服务端断开连接后回调。我们现在关心的主要是 onServiceConnected() 方法,在这里我们只做了一件事:将服务端转换过来的 IBinder 对象转换成 AIDL 接口,我们定义 IBookManager:bookManager 字段来保持对其的引用。这样的话,我们就可以通过这个 bookManager 来进行方法的远程调用。我们给客户端的 Button 注册事件:每一次点击都会向服务端增加一本书,并且将图书馆现有的图书数量显示出来。

现在我们看看程序的运行效果:

Android プロセス通信メカニズム AIDL

每当我们点击按钮,我们就成功的向服务端添加了一本书,说明我们通过 AIDL 跨进程通信成功了。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。