Android フレームワーク - プロセス間通信 - Binder

同じプログラム内の 2 つの関数間の直接呼び出しの根本的な原因は、それらが同じメモリ空間にあることです。
たとえば、次の 2 つの関数 A と B があります。

/*Simple.c*/
void A()
{
    
     B(); }
void B()
{
    
     }

1 つのメモリ空間にあるため、仮想アドレスのマッピング規則は完全に一致しているため、関数 A と B の呼び出し関係は図に示すように非常に単純です。
ここに画像の説明を挿入
図に示すように、特定の Application1 と ActivityManagerService (それらが配置されているプロセス) などの 2 つの異なるプロセスは、メモリ アドレスを介して相互の内部関数または変数に直接アクセスする方法がありません。
ここに画像の説明を挿入
他のプロセスのメモリ空間に「直接」アクセスすることはできないため、「間接的な」方法はありますか? つまり、図 6-3 に示すように、これが Binder の機能です。
ここに画像の説明を挿入
Binder は、Android で最も広く使用されている IPC メカニズムです。バインダーには
、バインダー ドライバー、
サービス マネージャー、
バインダー クライアント、
バインダー サーバーが含まれます。
Binder のさまざまなコンポーネントを見ると、TCP/IP ネットワークと多くの類似点があることに驚かれることでしょう:
Binder ドライバー→ルーター、
Service Manager→DNS、
Binder Client→クライアント、
Binder Server→サーバー。
TCP/IP での一般的なサービス接続プロセス (クライアントがブラウザを通じて Google ホームページにアクセスするなど) を図 6-4 に示します。
ここに画像の説明を挿入
クライアントは、Google.com の IP アドレスを DNS に照会します。DNS はクエリ結果をクライアントに返します。クライアントが接続を開始します。クライアントが Google.com の IP アドレスを取得すると、それに基づいて Google サーバーへの接続を開始できます。ルーターは通信構造全体の基礎であり、その役割はユーザーが設定したターゲット IP にデータ パケットを配信することです。
まず、TCP/IP 参照モデルでは、IP 層以上のユーザーにとって、IP アドレスは相互に通信するための資格情報です。つまり、インターネット全体でユーザーの IP 識別子は一意です。
次に、ルーターは通信ネットワークを構築するための基盤であり、ユーザーが入力した宛先 IP に従ってデータ パケットを宛先に正しく送信できます。
最後に、DNS の役割は必須ではなく、人々が複雑で覚えにくい IP アドレスをより読みやすいドメイン名に関連付け、ルックアップ機能を提供するために存在します。クライアントが DNS を使用できる前提は、DNS サーバーの IP アドレスが正しく構成されていることです。
Binder のプロトタイプを図 6-5 に示します.
ここに画像の説明を挿入
Binder の本質的な目的は、プロセス 1 (クライアント) がプロセス 2 (サーバー) と通信したいという一文で表されます。ただし、クロスプロセス (クロスネットワーク) であるため、Binder ドライバー (ルーター) を使用して、要求を相手のプロセス (ネットワーク) に正しく配信する必要があります。通信に参加するプロセスは、図 6-6 に示すように、Binder が「発行する」一意の識別子 (IP アドレス) を保持する必要があります。
ここに画像の説明を挿入
TCP/IP ネットワークと同様に、Binder の「DNS」は必要ありません - クライアントがアクセスしたいプロセスの Binder フラグ (IP アドレス) を覚えていることが前提です; そして、この事実に特別な注意を払ってください。 flag は「Dynamic IP」です。これは、クライアントがこの通信プロセスでターゲット プロセスの一意の識別子を覚えていたとしても、次のアクセスを再度取得する必要があることを意味し、クライアントの難易度が高くなることは間違いありません。「DNS」の出現により、この問題は完全に解決され、Binder ロゴとより読みやすい「ドメイン名」との対応を管理し、ユーザーにクエリ機能を提供するために使用されます。
Binderの仕組みでは、DNSの役割は Service Manager です
ここに画像の説明を挿入
が、Service ManagerはDNSなので、その「IPアドレス」とは?Binder メカニズムは、このための特別な規定を作成します。Binder 通信プロセスにおける Service Manager の唯一のフラグは常に 0 です。

スマートポインター

スマート ポインターは、Android プロジェクト全体、特に Binder のソース コード実装で広く使用されています。
1.1 スマート ポインターの設計概念
Java と C/C++ の主な違いは、"ポインター" の概念がないことです。これは、Java がポインターを使用する必要がないことを意味するのではなく、この「スーパーウェポン」が隠されていることを意味します。
C/C++ プロジェクトでよくあるポインターの問題は、次のように要約できます。
ポインターが初期化されていない、新しいオブジェクトが作成された後にオブジェクトが削除されない、ワイルド ポインター
Android スマート ポインターの実装には、ストロング ポインターとストロング ポインターの 2 種類があります。ウィーク ポインター:
1.2 ストロング ポインター sp
Frameworks/native /include/utils/StrongPointer.h

template <typename T>
class sp
{
    
    
public:
 inline sp() : m_ptr(0) {
    
     }
 sp(T* other);/*常用构造函数*//*其他构造函数*/sp();/*析构函数*/
 sp& operator = (T* other);// 重载运算符“=”
 …
 inline T& operator* () const {
    
     return *m_ptr; }// 重载运算符“*”
 inline T* operator-> () const {
    
     return m_ptr; }// 重载运算符“->”
 inline T* get() const {
    
     return m_ptr; }private:
 template<typename Y> friend class sp;
 template<typename Y> friend class wp;
 void set_pointer(T* ptr);
 T* m_ptr;
};

演算子 equals の実装は次のとおりです。

template<typename T>
sp<T>& sp<T>::operator = (T* other)
{
    
    
 if (other) other->incStrong(this);/*增加引用计数*/
 if (m_ptr) m_ptr->decStrong(this);
 m_ptr = other;
 return *this;
}

デストラクタ

template<typename T>
sp<T>::sp()
{
    
    
 if (m_ptr) m_ptr->decStrong(this);/*减小引用计数*/
}

1.3 弱いポインター wp に
はシーンがあり、親オブジェクトの親が子オブジェクトの子を指し、次に子
オブジェクトが親オブジェクトを指すため、循環参照の現象が発生します。

struct CDad 
{
    
     
 CChild *myChild;
}; 
struct CChild 
{
    
    
 CDad *myDad;
};

両方のクラスに参照カウンター機能があるとします。
CDad は CChild を指しているため、後者の参照カウンターはゼロではありません。
また、CChild は CDad を指しているため、カウンターも非ゼロになります。
メモリコレクターは、両方が「必要な」状態にあることを発見し、もちろん解放できないため、悪循環が形成されます。
この矛盾を解決する効果的な方法は、「弱い参照」を使用することです。具体的な対策は以下の通りです。
CDad は強いポインターを使用して CChild を参照し、CChild は弱い参照のみを使用して親クラスを指します。両当事者は、強参照カウントが 0 の場合、弱参照が 0 であるかどうかに関係なく、自分自身を削除できると規定しています (このルールは Android システムで調整できます)。このように、一方の当事者が解放される限り、デッドロックはうまく回避できます。ワイルドポインターで問題が発生しますか? はい、確かにこれに関しては懸念があります。たとえば、CDad のストロング ポインター カウントが 0 に達したため、規則に従って、ライフ サイクルは終了しますが、この時点では、CChild はまだその親クラスの弱参照を保持しています。 CDad この時点で、致命的な問題が発生します。これを考慮して、次のように指定する必要があります。
弱いポインターは、それらが指すターゲット オブジェクトにアクセスする前に、強いポインターにアップグレードする必要があります。

template <typename T>
class wp
{
    
    
public:
 typedef typename RefBase::weakref_type weakref_type;
 inline wp() : m_ptr(0) {
    
     }
 wp(T* other);//构造函数/*其他构造函数省略*/wp(); 
 wp& operator = (T* other);//运算符重载void set_object_and_refs(T* other, weakref_type* refs);
 sp<T> promote() const;/*升级为强指针*//*其他方法省略*/
private:
 template<typename Y> friend class sp;
 template<typename Y> friend class wp;
 T* m_ptr;
 weakref_type* m_refs;
};

まとめ
1. スマートポインタは、ストロングポインタ sp とウィークポインタ wp の 2 種類に分けられます。
2. 通常、ターゲット オブジェクトの親クラスは RefBase です。この基本クラスは、強参照と弱参照を同時に制御できる、weakref_impl タイプの参照カウンターを提供します (カウントは mStrong と mWeak によって内部的に提供されます)。
3. incStrong が強参照を追加すると、弱参照も追加されます。
4. incWeak の場合のみ弱参照カウントを増やします。
5. ユーザーは、extendObjectLifetime を使用して参照カウンターのルールを設定できます。ターゲット オブジェクトを削除するタイミングは、ルールによって異なります。
6. ユーザーは、プログラムの要件に従って、適切なスマート ポインターの種類とカウンター ルールを選択できます。

工程間データ転送キャリア - 小包

プロセス間のデータ転送の場合、int 値だけの場合は、ターゲット プロセスまでコピーし続けます。しかし、オブジェクトはどうですか?同じプロセス間でのオブジェクトの転送は参照によって行われると想像できるため、基本的にはメモリ アドレスです。クロスプロセスの場合、この方法は無力です。仮想メモリ メカニズムの採用により、2 つのプロセスは独自の独立したメモリ アドレス空間を持つため、プロセス間で渡されるアドレス値は無効になります。
プロセス間のデータ転送は Binder メカニズムの重要な部分であり、Parcel は Android システムでこの重要なタスクを担当します。パーセルは、IBinder を介して送信する関連情報 (データおよびオブジェクト参照を含む) を運ぶために使用されるデータ キャリアです。プロセスAのオブジェクトが占有するメモリ関連データをパッケージ化し、プロセスBに送信して、Bが独自のプロセス空間でオブジェクトを「再現」できるようにすることは可能ですか? パーセルには、このパッケージ化および再編成機能があります。
1. パーセル設定関連
格納されるデータが多いほど、パーセルが占有するメモリ スペースが大きくなります。次の方法で関連する設定を行うことができます。
dataSize(): 現在格納されているデータ サイズを取得します。

setDataCapacity (int size): パーセルのスペース サイズを設定します。明らかに、格納されたデータはこの値より大きくすることはできません。

setDataPosition (int pos): パーセルの読み取りおよび書き込み位置を変更します。これは 0 から dataSize() の間でなければなりません。

dataAvail(): 現在のパーセルで読み取り可能なデータ サイズ。

dataCapacity(): 現在のパーセルのストレージ容量。

dataPosition(): データの現在の位置の値で、カーソルに多少似ています。

dataSize(): 現在のパーセルに含まれるデータのサイズ。
2. プリミティブ型データのプリミティブ
読み取りおよび書き込み操作。例:
writeByte(byte): バイトを書き込みます。

readByte(): バイトを読み取ります。

writeDouble(double): double を書き込みます。

readDouble(): double を読み取ります。
3. プリミティブ配列
プリミティブ データ型配列の読み取りおよび書き込み操作では、通常、最初に 4 バイトで表されるデータ サイズの値を書き込み、次にデータ自体を書き込みます。さらに、ユーザーはデータを既存の配列空間に読み込むか、Parcel に新しい配列を返させるかを選択できます。そのようなメソッドは次のとおりです。
writeBooleanArray(boolean[]): ブール配列を書き込みます。

readBooleanArray(boolean[]): ブール配列を読み取ります。

boolean[]createBooleanArray(): ブール配列を読み取って返します。

writeByteArray(byte[]): バイト配列を書き込みます。

writeByteArray(byte[], int, int): 上記とは異なり、この関数の最後の 2 つのパラメーターは、配列に書き込むデータの開始位置と書き込む量を示します。

readByteArray(byte[]): バイト配列を読み取ります。

byte[]createByteArray(): 配列を読み取って返します。
データの書き込み時にパーセルのストレージ容量を超えていることがシステムによって検出された場合、必要なメモリ領域が自動的に適用され、dataCapacity が拡張され、各書き込みは dataPosition() から開始されます。
4. Parcelables
オブジェクトは Parcelable プロトコルに従っており、Parcelable を介してアクセスできます. たとえば、開発者が頻繁に使用するバンドルは、Parcelable から継承されます. writeParcelable(Parcelable, int): この Parcelableクラス
の名前と内容を Parcel に書き込みます. 実際には、この Parcelable の writeToParcel() メソッドをコールバックしてデータを書き込みます.

readParcelable(ClassLoader): 新しい Parcelable オブジェクトを読み取って返します。

writeParcelableArray(T[], int): Parcelable オブジェクトの配列を書き込みます。

readParcelableArray(ClassLoader): Parcelable オブジェクトの配列を読み取って返します
5. バンドル
前述のように、Bundle は Parcelable を継承し、タイプセーフな特別なコンテナーです。Bundle の最大の特徴は、キーと値のペアを使用してデータを格納し、読み取り効率をある程度最適化することです。このタイプのパーセル オペレーションには以下が含まれます。
writeBundel(Bundle): バンドルをパーセルに書き込みます。

readBundle(): 新しい Bundle オブジェクトを読み取って返します。

readBundle(ClassLoader): 新しい Bundle オブジェクトを読み取って返します。対応する Parcelable オブジェクトを取得するために、Bundle に ClassLoader が使用されます。
6. Active Objects
Parcelのもう 1 つの強力な武器は、 Active Object を読み書きできることです。アクティブ オブジェクトとは 通常、Parcel に保存するのはオブジェクトのコンテンツであり、Active Objects が書き込むのはその特別なフラグ参照です。したがって、これらのオブジェクトを Parcel から読み取ると、再作成されたオブジェクト インスタンスではなく、元の書き込まれたインスタンスが表示されます。このように転送できるオブジェクトは多くないと推測できますが、現時点では主に 2 つのタイプがあります。
(1) バインダー。Binder は、Android システムにおける IPC 通信のコア メカニズムの 1 つであると同時に、オブジェクトでもあります。Parcel を使用して Binder オブジェクトを作成すると、それを読み取るときに元の Binder オブジェクト、またはその特別なプロキシ実装を取得できます (最終的な操作は元の
Binder オブジェクトのままです)。これに関連する操作は次のとおりです。
writeStrongBinder(IBinder)
writeStrongInterface(IInterface)
readStrongBinder()
...
(2) FileDescriptor。FileDescriptor は Linux のファイル記述子であり、Parcel の次のメソッドを介して渡すことができます。
writeFileDescriptor(FileDescriptor)、readFileDescriptor() は、
渡されたオブジェクトが元のオブジェクトと同じファイル ストリームに基づいて引き続き動作するため、アクティブ オブジェクトのタイプと見なすことができます。
7。型なしコンテナ
標準の読み取りと書き込みに使用される任意のタイプの Java コンテナです。含む:
writeArray(Object[])、readArray(ClassLoader)、writeList(List)、readList(List, ClassLoader) など。

Parcel は多くのタイプをサポートしており、開発者のデータ転送要求を満たすのに十分です。小包の類推を見つけたい場合は、コンテナに似ています。理由は次のとおりです。
1. 貨物の無関係とは
、輸送される商品の種類を除外しないこと、電子製品は問題ない、車は問題ない、またはスペアパーツも許容されることを意味します。
2. 商品が異なれば、梱包と荷降ろしの方法も異なります
. たとえば、壊れやすいアイテムと硬いアイテムを運ぶための梱包と荷降ろしの方法は、明らかに大きく異なります。
3. 長距離輸送と組み立て
コンテナ商品は通常、海を越えて輸送されます。これは、Parcel のクロスプロセス機能と似ています。ただし、コンテナ運送会社自体は、出荷された商品のその後の組み立てについて責任を負いません。例えば、自動車メーカーは、完成した車両を部品に分解してから積み込み、輸送する必要がありますが、運送会社は、目的地に到着した後は、組み立ての責任を負うことなく、商品をそのまま荷受人に引き渡すだけで済みます。車両全体。一方、パーセルはより専用であり、プロトコルに従って
元のデータオブジェクトを完全に復元するサービスを受信者に提供します(パッケージ化と再編成に使用されるプロトコルは一致する必要があります)。
writeString の実装原理
writeString の実装原理を詳しく説明する例と組み合わせると、例は ServiceManagerProxy の getService() メソッドでの Parcel の操作です。

Parcel data = Parcel.obtain();
…
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);

コードの最初の文は Parcel オブジェクトを取得するために使用され、最後にローカルの Parcel インスタンスが作成され、完全に初期化されます。

2 番目の文の writeInterfaceToken は IBinder インターフェイス トークンを書き込むために使用され、パラメーターは IServiceManager.descriptor="android.os.IServiceManager" などの String 型です。

3 番目の文は、writeString を介してパーセル内の ServiceManager から照会する必要があるサービス名を書き込みます。

IPC 全体での Parcel の内部転送処理は比較的煩雑で、特に Binder データを運ぶ場合は、複数回の変換が必要になるため、方向性を失いやすいです。しかし、その過程がどれほど曲がりくねっていても、変わらないことが 1 つあります。つまり、ライターとリーダーが使用するプロトコルは完全に一致している必要があります。

ライター (ServiceManagerProxy) が「コンテナ」に「パック」したものを見てみましょう。

status_t Parcel::writeInterfaceToken(const String16& interface)
{
    
    
 writeInt32(IPCThreadState::self()->getStrictModePolicy()
|STRICT_MODE_PENALTY_GATHER);
 return writeString16(interface);
}

相当:
writeInterfaceToken→writeInt32(policyvalue)+writeString16(interface)

インターフェースは「android.os.IServiceManager」です。

status_t Parcel::writeInt32(int32_t val)
{
    
    
 return writeAligned(val);
}

この関数の実装は非常に単純です。コードは 1 行しかありません。関数名から判断すると、アラインメントに従って val 値を Parcel のストレージ スペースに書き込みます。つまり、mDataPos が起動した mData にデータを書き込むことです (もちろん、現在のストレージ容量が内部で要件を満たしているかどうか、新しいメモリを申請するかどうかなどを判断する必要があります)。

status_t Parcel::writeString16(const String16& str)
{
    
    
 return writeString16(str.string(), str.size());
}
status_t Parcel::writeString16(const char16_t* str, size_t len)
{
    
    
 if (str == NULL) return writeInt32 (-1); //str不为空
 status_t err = writeInt32(len); //先写入数据长度
 if (err == NO_ERROR) {
    
    
 len *= sizeof(char16_t); //长度*单位大小=占用的空间
 uint8_t* data =
(uint8_t*)writeInplace(len+sizeof(char16_t));
 if (data) {
    
    
 memcpy(data, str, len);/*将数据复制到data所指向的位置中
*/
 *reinterpret_cast<char16_t*>(data+len) = 0;
 return NO_ERROR;
 }
 err = mError;
 }
 return err;
}

writeString16 のプロセス全体を理解するのは難しくありません: 最初に 4 バイトを占めるデータの長さを入力し、次にデータに必要なスペースを計算し、最後にデータを対応する場所にコピーします - writeInplace を使用して宛先アドレスを計算しますコピーしたデータの.
文字列を書き込む手順 (writeString16):
writeInt32(len);
memcpy;
パディング (場合によっては、パディングが必要ありません。このステップは、ソース コードの実装で memcpy の前にあります)。
ServiceManagerProxy の getService に戻ります。

data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);

上記の 2 つのステートメントを分解して、ライターの作業を取得します

リーダーは Service_manager.c です

int svcmgr_handler(struct binder_state *bs, struct binder_txn
*txn,
 struct binder_io *msg, struct binder_io *reply)
{
    
    …
 uint32_t strict_policy;
 …
 strict_policy = bio_get_uint32(msg); //取得policy值
 s = bio_get_string16(msg, &len); //取得一个String16,即上面写入的interface
 if ((len != (sizeof(svcmgr_id) / 2)) ||
 memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
    
    /*判断Interface是否正确?*/
 fprintf(stderr,"invalid id %s\n", str8(s));
 return -1;
 }

上記のコード セグメントは、受信したインターフェイスが正しいかどうかを判断するために使用されます。
uint16_t svcmgr_id[] = { 'a','n','d','r','o','i','d','.','o','s',' . ', 'I','S','e','r','v','i','c','e','M','a','n','a', 'g', 'e', 'r' };は、以前の「android.os.IServiceManager」と同じです。



switch(txn->code) {
    
    
 case SVC_MGR_GET_SERVICE:
 case SVC_MGR_CHECK_SERVICE:
 s = bio_get_string16(msg, &len);//获取要查询的service name
 ptr = do_find_service(bs, s, len, txn->sender_euid);
 if (!ptr)
 break;
 bio_put_ref(reply, ptr);
 return 0;

ServiceManager によるデータの読み取りプロセスは、データの書き込みプロセスとまったく同じであることがわかります。

バインダー ドライバーとプロトコル

Android システムは Linux カーネルに基づいているため、依存する Binder ドライバーも標準の Linux ドライバーである必要があります。具体的には、バインダー ドライバーは自身をその他のデバイスとして登録し、/dev/binder ノードを上位レイヤーに提供します。バインダー ノードは実際のハードウェア デバイスに対応していないことに注意してください。Binder ドライバーはカーネル モードで実行され、open()、ioctl()、mmap() などの一般的なファイル操作を提供できます。
Linux のキャラクター デバイスは、通常、alloc_chrdev_region() や cdev_init() などの一連の操作を実行して、カーネルに自身を登録します。misc タイプのドライバーは比較的単純で、misc_register() を呼び出すことで簡単に解決できます。たとえば、Binder でのドライバー登録に関連するコードは次のとおりです。

/*drivers/staging/android/Binder.c*/
static struct miscdevice binder_miscdev = {
    
    
 .minor = MISC_DYNAMIC_MINOR, /*动态分配次设备号*/
 .name = "binder", /*驱动名称*/
 .fops = &binder_fops /*Binder驱动支持的文件操作*/
};
static int __init binder_init(void)
{
    
     …
 ret = misc_register(&binder_miscdev); /*驱动注册*/}

Binder ドライバーは、file_operations 構造体にも入力する必要があります。次のように:

/*drivers/staging/android/Binder.c*/
static const struct file_operations binder_fops = {
    
    
 .owner = THIS_MODULE,
 .poll = binder_poll,
 .unlocked_ioctl = binder_ioctl,
 .mmap = binder_mmap,
 .open = binder_open,
 .flush = binder_flush,
 .release = binder_release,
};

Binder ドライバーは、上位層アプリケーション用に合計 6 つのインターフェースを提供します。最もよく使用されるのは、binder_ioctl、binder_mmap、binder_open です。
1.1 Binder ドライバーを開く - bind_open

/*如果没有特别说明,以下的函数都在Binder.c中*/
static int binder_open(struct inode *nodp, struct file *filp)
{
    
    
 struct binder_proc *proc;
 …
 proc = kzalloc(sizeof(*proc), GFP_KERNEL);/*分配空间*/
 if (proc == NULL)
 return -ENOMEM;

Binder ドライバーはユーザー用に独自の binder_proc エンティティを作成しており、Binder デバイスでのユーザーの操作はこのオブジェクトに基づいています。
1.2 bind_mmap
mmap() は、デバイスによって指定されたメモリ ブロックをアプリケーションのメモリ空間に直接マップできます。Binder ドライバーの場合、上位ユーザーによって呼び出される mmap() は、最終的には bind_mmap() に対応します。図 6-13 に示すように、2 つのプロセス A と B があり、プロセス B が open() と mmap() を介して Binder ドライバとの接続を確立するとします。
ここに画像の説明を挿入
1. アプリケーション プログラムは、mmap() の戻り値を介してメモリ アドレス (もちろん、これは仮想アドレスです) を取得し、このアドレスは最終的に、仮想メモリ変換後の物理メモリ内の特定の場所を指します (セグメンテーション、ページング)。
2. Binder ドライバの場合、仮想メモリ アドレスを指すポインタ (binder_proc->buffer) も持っています。仮想メモリの変換後は、アプリケーションが指す物理メモリと同じ場所にあります。

この時点で、Binder とアプリケーション プログラムは、いくつかの共有物理メモリ ブロックを持っています。つまり、それぞれのメモリアドレスに対する操作は、実際には同じメモリブロックで実行されます。
プロセス A をもう一度追加してみましょう.
ここに画像の説明を挿入
Binder ドライバーの右半分は、copy_from_user() を使用して、プロセス A の特定のデータを、binder_proc->buffer が指すメモリ空間にコピーします。物理メモリ内の bind_proc->buffer の場所はプロセス B と共有されているため、プロセス B はこのデータに直接アクセスできます。つまり、バインダードライバは、プロセス A とプロセス B の間のデータ共有を 1 つのコピーだけで実現します。
1.3 bind_ioctl
Binder インタフェース関数の中で最も負荷が大きく、Binder が駆動する業務の大部分を担っています。
ここに画像の説明を挿入
ここに画像の説明を挿入

「DNS」サーバー - ServiceManager (バインダーサーバー)

ServiceManager(以下、SM)の機能は、インターネットにおける「DNS」サーバーに例えることができ、「IPアドレス」は0です。また、DNS 自体がサーバーであるように、SM も標準の Binder Server です。
1.1 ServiceManager の起動
DNS ですので、利用者が Web を閲覧する前に設定しておく必要があります。同じことが SM にも当てはまります。誰かが Binder メカニズムを使用する前に、SM が通常の動作状態であることを確認する必要があります。それで、正確にいつそれが機能したのですか?当然、init プログラムが init.rc を解析するときに開始する必要があると考えます。そして確かにそうです。次のように:

/*init.rc*/
service servicemanager /system/bin/servicemanager
 class core
 user system
 group system
 critical
 onrestart restart zygote
 onrestart restart media
 onrestart restart surfaceflinger
 onrestart restart drm

この servicemanager は C/C++ で書かれています. ソース コード パスはプロジェクトの /frameworks/native/cmds/servicemanager ディレクトリにあります. 最初にその make ファイルを見てください:

LOCAL_PATH:= $(call my-dir)
…
include $(CLEAR_VARS)
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := service_manager.c binder.c 
LOCAL_MODULE := servicemanager /*生成的可执行文件名为
servicemanager*/
include $(BUILD_EXECUTABLE)/*编译可执行文件*/

1.2 ServiceManager の構築
SM が起動後に行った作業を見てみましょう。

/*frameworks/native/cmds/servicemanager/Service_manager.c*/
int main(int argc, char **argv)
{
    
    
 struct binder_state *bs;
 void *svcmgr = BINDER_SERVICE_MANAGER;
 bs = binder_open(128*1024);
 if (binder_become_context_manager(bs)) {
    
     /*将自己设置为
Binder“大管家”,整个Android 
 系统只允许一个ServiceManager存在,因而如果后面还有人
调用这个函数就会失败*/
 ALOGE("canno t become context manager (%s)\n",
strerror(errno));
 return -1;
 }
 svcmgr_handle = svcmgr;
 binder_loop(bs, svcmgr_handler); //进入循环,等待客户的请求
 return 0;
}

main 関数は主に次のことを行います:
Binder デバイスを開いて初期化し、
自身を Binder ハウスキーパーとして設定し、
メイン ループに入ります。
では、具体的にどのような初期化を行う必要があるのでしょうか?

/*frameworks/native/cmds/servicemanager/Binder.c */
struct binder_state *binder_open(unsigned mapsize)
{
    
    
 struct binder_state *bs; /*这个结构体记录了SM中有关于Binder的所有
信息,
 如fd、map的大小等*/
 bs = malloc(sizeof(*bs));
 …
 bs->fd = open("/dev/binder", O_RDWR); //打开Binder驱动节点
 …
 bs->mapsize = mapsize; //mapsize是SM自己设的,为128*1024,即
128K
 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs-
>fd, 0);return bs;
fail_map:
 close(bs->fd); //关闭file
fail_open:
 free(bs);
 return 0;
}

上記のコード セグメントのパラメーター設定によると:
1. プロセス空間にマップされるメモリの開始アドレスはバインダー ドライバーによって決定されます;
2. マップされたブロックのサイズは 128KB です;
3. マップされた領域は読み取られます-のみ;
4. 変更されたマッピングされた領域はプライベートであり、ファイルを保存する必要はありません;
5. ファイルの開始アドレスからマッピングを開始します

main 関数の 2 番目のステップを見てみましょう。これは、servicemanager を Binder メカニズムの「大きな管理者」として登録することです。

int binder_become_context_manager(struct binder_state *bs)
{
    
    
 return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

すべての準備が整い、SM はクライアントの要求を待ち始めることができます。作業のこの部分は、SM の焦点であり困難です。SM がリクエストを処理する方法を確認するために、binder_loop() から始めましょう (セクションを読んでください)。

void binder_loop(struct binder_state *bs, binder_handler func)
{
    
    
 int res;
 struct binder_write_read bwr; /*这是执行BINDER_WRITE_READ命令
所需的数据格式*/
 unsigned readbuf[32];/*一次读取容量*/

Binder サーバーがループに入る前に、まず Binder ドライバーにこの状態変化を通知する必要があります。次のコードはその役割を果たします。

bwr.write_size = 0;//这里只是先初始化为0,下面还会再赋值
 bwr.write_consumed = 0;
 bwr.write_buffer = 0; 
 readbuf[0] = BC_ENTER_LOOPER;/*命令*/
 binder_write(bs, readbuf, sizeof(unsigned));

その後、SM はループに入ります。ループ本体で何をする必要がありますか?
1. メッセージ キューからメッセージを読み取ります。
2. メッセージが「終了コマンド」の場合は、すぐにループを終了します。メッセージが空の場合は、読み取りを続行するか、読み取り前に一定時間待機します。メッセージが空ではなく、終了コマンドでない場合は、次のように処理します。特定の状況に。
3. 終了するまでこのサイクルを繰り返します。
ただし、SM にはメッセージ キューはなく、その「メッセージ」(または「コマンド」) は Binder ドライバーから取得されます。

for (;;) {
    
    
 bwr.read_size = sizeof(readbuf);/*readbuf的大小为32个
unsigned*/
 bwr.read_consumed = 0;
 bwr.read_buffer = (unsigned) readbuf;/*读取的消息存储到
readbuf中*/
 res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); //读取“消
息”
 …
 res = binder_parse(bs, 0, readbuf, bwr.read_consumed,
func);//处理这条消息
 if (res == 0) {
    
    
 ALOGE("binder_loop: unexpected reply?!\n");
 break;
 }
 if (res < 0) {
    
    
 ALOGE("binder_loop: io error %d %s\n", res,
strerror(errno));
 break;
 }
 }
}/*binder_loop结束*/

SM が次の手順に従うことがわかります。 1. BINDER_WRITE_READ コマンドを送信して
Binder ドライバからメッセージを読み取ります。このコマンドは、bwr.write_size と bwr.read_size に応じて読み取りまたは書き込みが可能です。
write_size の初期値が 0 で、read_size が sizeof(readbuf) であるため、Binder ドライバーは読み取り操作のみを実行します。
2. メッセージを処理する
bind_parse を呼び出してメッセージを解析します。
3. ループし続け、積極的に終了しない (致命的なエラーが発生しない限り)
1.3 ServiceManager サービス設計の考え方を取得する
SM (Binder Server) のサービスにアクセスする場合、どのようなプロセスが必要ですか?
次の手順にすぎません。

  1. バインダー デバイスを開きます。
  2. mmap を実行します。
  3. Binder ドライバーを介して SM に要求を送信します (SM のハンドルは 0)。
  4. 結果を得る。

コアワークは本当にこれらだけです。ただし、次のような特定の詳細については、まだ議論する必要があります。

  1. SM への要求を開始する Binder Client は Android APK アプリケーションである可能性があるため、SM は Java レイヤー インターフェイスを提供する必要があります。
  2. 各 Binder Client が SM サービスを取得するために上記の手順を個別に実行する必要がある場合、多くの時間が無駄になると考えられます。もちろん、Android システムもこれを考慮しているため、SM 呼び出しプロセス全体をより合理的かつ実用的にするために、より優れたパッケージを提供します。
  3. アプリケーション コードが SM サービス (ま​​たは他の Binder Server サービス) を毎回使用する場合、Binder ドライバーを一度開いて mmap を実行する必要があるため、クラッシュするまでシステム リソースがますます消費されます。効果的な解決策は、各プロセスが Binder デバイスを開くことを 1 回だけ許可し、メモリ マッピングを 1 回だけ行うことです。Binder ドライバーを使用する必要があるすべてのスレッドがこのリソースを共有します。

質問は次のように変換されます: 上記の要件を満たす BinderClient を設計する場合、何をすべきか?
1. ProcessState と IPCThreadState
で最初に考えられるのは、各アプリケーション プロセスで Binder 操作を具体的に管理するためのクラスを作成することです。さらに重要なことは、Binder によって駆動される一連のコマンドの実行が上位ユーザーに対して「透過的」でなければならないことです。このクラスは ProcessState です。ProcessState だけでは十分ではありません。プロセス内の各スレッドは、Binder ドライバーと自由に通信する権利を持っている必要があります。また、Binder に基づく IPC がブロックされているため、プロセス間通信アプリケーションを実行するときに個々のスレッドがスタックしないようにすることができます。Binder ドライバーとの実際のコマンド通信は IPCThreadState です。
2. Proxy
上記の 2 つのクラスにより、アプリケーションは Binder ドライバーと通信できるようになります。原則として、SM によって提供されるサービスを段階的に取得するために、BINDER_WRITE_READ などの Binder によってサポートされるコマンドを送信することによって、引き続きそれとやり取りすることができます。SM によって提供されるサービスをカプセル化し、この SM サービスのカプセル化に ServiceManagerProxy という名前を付ける必要があります。

/*应用程序获取SM服务示例*/
//Step1. 创建ServiceManagerProxy
ServiceManagerProxy sm = new ServiceManagerProxy(new BpBinder(HANDLE));
//Step2. 通过ServiceManagerProxy获取SM的某项服务
IBinder wms_binder = sm.getService("window");

アプリケーションが ServiceManager によって提供されるサービスを取得するために必要な手順は 2 つだけです。
この目標を達成する方法:
(1) ServiceManagerProxy のインターフェイス。ServiceManagerProxy が提供できるサービスは、getService、addService など、サーバーの SM と一致している必要があります。これらのメソッドを抽出するのが ServiceManagerProxy のインターフェースです。以下に示すように、IServiceManager という名前を付けます
(最初にそのパラメーターを無視します)。

public interface IServiceManager 
{
    
    
 public IBinder getService(String name) throws
RemoteException;
 public IBinder checkService(String name) throws
RemoteException;
 public void addService(String name, IBinder service,
boolean allowIsolated)throws RemoteException;
 public String[] listServices() throws RemoteException;
}

明らかに、図に示すように、ServiceManagerProxy は IServiceManager から継承する必要があります。
ここに画像の説明を挿入
(2) インターフェースの実装。getService を例に取ると、ServiceManager のサービスを取得するには、少なくとも 2 つの部分があります。
1. Binder との関係を確立する Binder
ドライバーと明確に通信する ProcessState と IPCThreadState という 2 つのクラスがプロセス内に既に存在するため、Java 層コードは Binder ドライバーを使用して実際にそれらに基づいて完了します。これを BpBinder と呼びます。
2. Binder にコマンドを送信して、SM が提供するサービスを取得します。
次のように要約します。

  1. Binder アーキテクチャー
    本体は、ドライバー、SM、Binder Client、Binder Server で構成されます。
  2. バインダードライバー
  3. Service Manager
    SM は、Binder フレームワークのサポーターであるだけでなく、標準サーバーでもあります。

図に示すように、Binder のメカニズムをグラフにまとめることができます (SM サービス (主要コンポーネント) の取得)。
ここに画像の説明を挿入
図の Client は Binder Client を表しています。つまり、Binder メカニズムを使用してサービスを取得するクライアントです。その内部構造は、
下から ProcessState/IPCThreadState → BpBinder → Proxy → User です。Client も Service Manager も Binder Driver をベースに仕事をしています。
1.4 ServiceManagerProxy
前のセクションで「設計意図」について考えたとき、疑似コードの小さな断片による ServiceManagerProxy の実装について説明しました。Android システムでの特定の実装は、ServiceManagerProxy にもう 1 つ追加することを除いて、基本的にこれと似ています。レイヤーのカプセル化、つまり ServiceManager.java。
このように、アプリケーションで ServiceManager を使用する方が便利であり、以下に示すように、ServiceManagerProxy オブジェクトを作成する必要さえありません:
ServiceManager.getService(name);
getService の内部実装:

 public static IBinder getService(String name) {
    
    
 try {
    
    
 IBinder service = sCache.get(name);//查询缓存
 if (service != null) {
    
    
 return service;//从缓存中找到结果,直接返回
 } else {
    
    
 return
getIServiceManager().getService(name);//向SM发起查询
 }
 } catch (RemoteException e) {
    
    
 Log.e(TAG, "error in getService", e);
 }
 return null;
 }
private static IServiceManager getIServiceManager() {
    
    
 if (sServiceManager != null) {
    
    
 return sServiceManager;//返回一个IServiceManager对象
 }
 // Find the service manager
 sServiceManager
=ServiceManagerNative.asInterface(BinderInternal.getContextObjec
t());
 return sServiceManager;
 }

ServiceManagerNative

/*frameworks/base/core/java/android/os/ServiceManagerNative.java
*/
 static public IServiceManager asInterface(IBinder obj)
 {
    
    
 if (obj == null) {
    
    
 return null;
 }
 IServiceManager in =
(IServiceManager)obj.queryLocalInterface(descriptor);
 if (in != null) {
    
    
 return in;
 }
 return new ServiceManagerProxy(obj);
 }

この関数のコメントから、Binder オブジェクトを IServiceManager に変換し、必要に応じて ServiceManagerProxy を作成する役割を果たしていることがわかります。
ServiceManagerProxy は Binder ドライバーと通信する必要があるため、IBinder オブジェクトがそのコンストラクターに渡されます。

public ServiceManagerProxy(IBinder remote) {
    
    
 mRemote = remote;
}

ご覧のとおり、IBinder オブジェクトを記録するだけです。電話で料理を注文するようなもの. IBinder はレストランの電話番号です. 通常、最初に書き留めておき、必要なときにこの番号からレストランが提供するサービスを受けることができます. たとえば、 getService() インターフェース:

public IBinder getService(String name) throws
RemoteException {
    
    
 Parcel data = Parcel.obtain();
 Parcel reply = Parcel.obtain();
 data.writeInterfaceToken(IServiceManager.descriptor);
 data.writeString(name);
 mRemote.transact(GET_SERVICE_TRANSACTION, data, reply,
0);/*利用IBinder对象执行
 
命令*/
 IBinder binder = reply.readStrongBinder();
 reply.recycle();
 data.recycle();
 return binder;
 }

この関数の実装は、次の 3 つの部分に分かれています。

  1. データを準備する
  2. IBinder.transact は、
    Binder 駆動の open、mmap、および Binder プロトコルの多くの特定のコマンドに関係なく、IBinder のトランザクションを使用して要求を送信します。したがって、この IBinder は間違いなく ProcessState と IPCThreadState を内部で使用して、Binder ドライバーと通信します。
  3. 結果を得る

実際の作業は次の文のみです。

mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);

ここで、上記の GET_SERVICE_TRANSACTION のように、クライアントとサーバーが使用するビジネス コードは一貫している必要があることに注意してください。その定義は次のとおりです。

int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;

IBinder の定義によると:

int FIRST_CALL_TRANSACTION = 0x00000001;

したがって、このビジネス コードは 1 です。Service_manager.c のビジネス コードの説明を見てみましょう。

enum {
    
    
 SVC_MGR_GET_SERVICE = 1, //对应的就是上面的那个业务
 SVC_MGR_CHECK_SERVICE,
 SVC_MGR_ADD_SERVICE,
 SVC_MGR_LIST_SERVICES,
};

このようにして、クライアントとサーバーの業務コードは一致します。
1.5 IBinder と BpBinder が
ServiceManagerProxy を作成するとき、IBinder オブジェクトを渡し、そのトランザクション メソッドを使用して Binder ドライバーと便利に通信します。では、IBinder は内部でどのように実装されているのでしょうか。
Binder が提供する機能は、IBinder で一様に表現できます。少なくとも次のインターフェイス メソッドが必要です。

/*frameworks/base/core/java/android/os/IBinder.java*/
public interface IBinder {
    
    
 public IInterface queryLocalInterface(String descriptor);
 public boolean transact(int code, Parcel data, Parcel
reply, int flags)
 throws Remote Exception;}

さらに、IBinder オブジェクトを取得するためのクラス、つまり BinderInternal が必要です。提供されている対応するメソッドは次のとおりです。

/*frameworks/base/core/java/com/android/internel/os/BinderIntern
al.java*/
public class BinderInternal {
    
    
 public static final native IBinder getContextObject();}

対応するネイティブ メソッド:

/*frameworks/base/core/jni/android_util_Binder.cpp*/
static jobject
android_os_BinderInternal_getContextObject(JNIEnv* env, jobject
clazz)
{
    
    
 sp<IBinder> b = ProcessState::self()-
>getContextObject(NULL);
 return javaObjectForIBinder(env, b);
}

これは ProcessState を通じて実現され、ProcessState で作成されたオブジェクトは Java 層の IBinder オブジェクトに変換されます。
IBinder は単なるインターフェイス クラスであり、明らかにそれを継承する具体的な実装クラスが存在します。ネイティブ層では BpBinder (BpBinder.cpp)、Java 層では Binder.java の BinderProxy です。実際、 ProcessState::self() ->getContextObject(NULL) が返すのは BpBinder オブジェクトです。
ここに画像の説明を挿入
BinderProxy と BpBinder は、それぞれ Java レイヤーとネイティブ レイヤーの IBinder インターフェイスから継承します。このうち、BpBinder は ProcessState によって作成され、BinderProxy は JNI の NewObject() を介して javaObjectForIBinder() 関数によって作成されます。
ソース コード mRemote->transact を分析し、BinderProxy のトランザクション メソッドを呼び出します。実際の実装はまだ android_util_Binder.cpp にあります。最後に、BpBinder.transact を介してユーザーの Binder リクエストを処理します。

/*frameworks/native/libs/binder/BpBinder.cpp*/
status_t BpBinder::transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
    
    
 // Once a binder has died, it will never come back to life.
 if (mAlive) {
    
    
 status_t status=IPCThreadState::self()-
>transact(mHandle,code,data,reply, flags);
 if (status == DEAD_OBJECT) mAlive = 0;
 return status;
 }
 return DEAD_OBJECT;
}

最終的に、これは IPCThreadState と ProcessState によって実現されます。
1.6 ProcessState と IPCThreadState
ProcessState を実現するポイントは、

  1. ProcessState インスタンスが同じプロセスに 1 つだけ存在することを確認し、ProcessState オブジェクトの作成時に Binder デバイスのみを開いてメモリ マッピングを実行します。
  2. 上位層に IPC サービスを提供します。
  3. 分業で IPCThreadState を操作し、その職務を遂行します。

ほとんどのプログラムには IPC が必要であり、プロセス間通信自体は非常に面倒なので、Android システムは Binder メカニズムを使用して、プログラム プロセスの 2 つの実装クラス、つまり ProcessState と IPCThreadState をカプセル化します。名前からわかるように、前者はプロセス
関連、後者はスレッド関連です。ProcessState は Binder ドライバー デバイスを開き、mmap() およびその他の準備を実行する役割を担い、特定のコマンドについて Binder ドライバーと通信する方法は IPCThreadState によって行われます。

バインダー クライアント - バインダー クライアント

1. Binder とは何ですか?
これは多くのプロセス間通信の 1 つです。プロセス間通信は、すべてのオペレーティング システムが提供する必要があるものです。
2. アプリケーションと Binder
Binder の最大の「消費者」は、Java 層のアプリケーション プログラムです。図 6-21 は単純ですが、Binder の本質、つまりプロセス間の通信ブリッジを要約しています。次に、
この手がかりに沿って引き続き掘り下げる必要があります。図の工程①は一般的な参考ですが、Binderを利用するためにはどのような条件や準備が必要でしょうか?一般に、アプリケーション開発者は bindService、startActivity、sendBroadcast などの一連のインターフェイス メソッドを使用して、プログラム コードの任意の時点で他のプロセスとの対話を実現できるため、アプリケーション開発者の観点からは、これは考慮されていないように見えます。 、図 6-22 に示すように。
ここに画像の説明を挿入

Binder Driver、Service Manager、および Android システムがアプリケーション開発用に提供する Binder の強力なパッケージの努力により、アプリケーションはスムーズかつシームレスに通信することができます。4 つの主要なコンポーネントからいくつかの手掛かりを見ることができます。

  • アクティビティ

対象のプロセスは startActivity で起動できます。

  • サービス

後者がクロスプロセスであるかどうかに関係なく、任意のアプリケーションが startService または bindService を介して特定のサービスを開始できます。

  • ブロードキャスト
    ブロードキャスト ハンドラが同じプロセス内にあるかどうかに関係なく、アプリケーションは sendBroadcast を介してブロードキャストを送信できます。
  • 上記のインテントの 4 つの主要コンポーネントの操作では、ほとんどの場合、どのターゲット アプリケーションが要求に応答する必要があるかを具体的に指定しません。最初に
    「インテント」と呼ばれるオブジェクトを通じて「意志」を
    表現し、次にシステムは、関連する作業を完了するために最適な申請プロセスを見つけます。このような設計により、システムの柔軟性が大幅に向上します。

以下では、bindService を例として取り上げ、これらのインターフェイスの背後に隠されている Binder の内部原則を完全に明らかにします。
ここに画像の説明を挿入
図からわかるように、フレームワーク全体は 2 つの部分に分割されます。これらの部分は、Binder メカニズムでアプリケーションに表示される部分と隠される部分をそれぞれ表します。
誰もが「隠れた部分」全体の内部実装をより明確に見ることができるようにするために、分析のために例が選択されています。図 6-25 に示すように、Application1 のアクティビティは、
bindService(intent) を介してインテントの記述に準拠するサービス サービスを開始しようとします。最後に、Application2 のサービスが実行されます。
ここに画像の説明を挿入
システム内の他のプロセスによって提供されるサービスを開始するために、アプリケーションは bindService にどのように依存できますか? この運用上の目標を達成するには、次の手順を実行する必要があります。

  • Step1. アプリケーションは Intent を入力し、bindService を呼び出してリクエストを送信します。
  • ステップ 2. 要求を受信した bindService (この時点ではまだアプリケーションの実行スペース内にあります) は、Activity ManagerService (AMS) と通信します。AMS の Binder ハンドル値を取得するには、事前に ServiceManager.getService を呼び出す必要がありますが、これには既にプロセス間通信が含まれています。AMS のハンドル値を取得した後、プログラムは実際に AMS への要求を開始できます。
  • Step3. AMS は、特定の「最適マッチング戦略」に基づいて、内部ストレージに格納されているシステム内のすべてのサービス コンポーネントのデータから Intent に最も一致するものを見つけ、サービス バインディング リクエストを AMS に送信します (このステップは、プロセス間通信も)
    — - ターゲット プロセスが存在しない場合は、AMS がそのプロセスの起動も担当することに注意してください。
  • Step4. 「バインドされた」サービス プロセスは、バインドに応答し、特定の操作を実行し、正常に完了した後に AMS に通知する必要があります。後者は、要求を開始したアプリケーションをコールバックします (コールバック インターフェイスは ServiceConnection です)。

一見シンプルな bindService の内部に「大きな世界」があることがわかります。しかし、なぜ Application1 は、Activity で bindService を呼び出すだけで、
上記の面倒なプロセスが見られないのでしょうか?
図 6-26 に Activity アプリケーション プログラムに基づく継承関係を示します.Activity
ここに画像の説明を挿入
継承関係の「ルート」は Context です。bindService は当然 Context に含まれます。具体的には、 Context は抽象インターフェースを提供するだけで、関数は ContextWrapper に実装されています。

/*frameworks/base/core/java/android/content/ContextWrapper.java*/
 public boolean bindService(Intent service, ServiceConnection
conn,int flags) {
    
    
 return mBase.bindService(service, conn, flags); //mBase是什么?
 }

上記の変数 mBase は Context オブジェクトでもあり、最新バージョンでは ContextImpl によって実装されています (bindService は直接 bindServiceAsUser を呼び出します)。

/*frameworks/base/core/java/android/app/ContextImpl.java*/
 public boolean bindServiceAsUser(Intent
service,ServiceConnection conn,int flags,
 UserHandle user) {
    
    int res =
ActivityManagerNative.getDefault().bindService(
 mMainThread.getApplicationThread(),
getActivityToken(),
 service,
service.resolveTypeIfNeeded(getContentResolver()),
 sd, flags, userId); /*ActivityManager出现了,证明了我们猜测的第2步*/}

では、アプリケーションはどのようにして AMS との接続を見つけて確立するのでしょうか? ServiceManager と同様に、AMS は次のように ActivityManagerNative と ActivityManagerProxy も提供します。

/*frameworks/base/core/java/android/app/ActivityManagerNative.ja
va*/
 static public IActivityManager getDefault() {
    
    
 return gDefault.get(); /*得到默认的IActivityManager对象*/
 }

この gDefault.get() は何を取得しますか?

/*frameworks/base/core/java/android/app/ActivityManagerNative.ja
va*/
 private static final Singleton<IActivityManager> gDefault
=new Singleton <IActivity
 Manager>() {
    
    
 /*Singleton,即“单实例”是一种常见的设计模式,它保证某个对象只会被创建
一次。
 当调用gDefault.get()时,会先进行内部判断:如果该对象已经存在,就直接
返回它的现有值;否则才
 需要通过内部create()新建一个对象实例*/
 protected IActivityManager create() {
    
    
 IBinder b = ServiceManager.getService("activity");/*通过
ServiceManager Service
 取得ActivityManagerService的
IBinder对象*/
 …
 IActivityManager am = asInterface(b); /*创建一个可用的
ActivityManagerProxy*/return am;
 }
 };

ActivityManagerNative の機能の 1 つは、呼び出し元が ActivityManagerProxy を便利かつ迅速に取得できるようにすることです。
gDefault の単一インスタンスでは、有効な IActivityManager オブジェクトを取得するには、次の 2 つの手順が必要です。

  • IBinder(BpBinder) を取得します。
  • IBinder を IInterface (このシナリオでは IactivityManager) に変換します。

ちなみに、ActivityManagerNative のもう 1 つの役割は、ActivityManagerService の実装を容易にすることです。
よく見ると、 ActivityManagerNative には次のメソッドがあることがわかります。

public boolean onTransact(int code, Parcel data, Parcel reply,
int flags)throws Remote Exception {
    
    
 switch (code) {
    
    
 case START_ACTIVITY_TRANSACTION:
 {
    
    int result = startActivity(app, intent,
resolvedType,
 grantedUriPermissions, grantedMode,
resultTo, resultWho,
 requestCode, onlyIfNeeded, debug,
profileFile, 
 profileFd, autoStopPro filer);}

このように、AMS の ActivityManagerNative を継承する限り、ユーザーのサービス要求コードは独自の内部実装関数に接続されているので、非常に便利ではないでしょうか。ソースコードは次のとおりです。

/*frameworks/base/services/java/com/android/server/am/ActivityMa
nagerService.java*/
public final class ActivityManagerService extends
ActivityManagerNative/*果然继承了
 
ActivityManagerNative*/
 implements Watchdog.Monitor,
BatteryStatsImpl.BatteryCallback {
    
    

そのため、ActivityManagerNative(他のサービスのNativeも同じ)は、呼び出し側だけでなく、サービスの実装自体にも向いていると言えますが、このNativeの名前は曖昧になりがちです。
After the analysis of the above code, the inter-process communication between Application1 and Application2 should also be supported by ServiceManager and ActivityManagerService,
as shown in Figure 6-27
ここに画像の説明を挿入
. アプリケーションが ServiceManager を介して Binder Server にクエリを実行する必要がある場合、getService を呼び出します。方法。アプリケーションに直接直面しているのは Service
Manager.java で、呼び出し元が ServiceManager によって提供されるサービス (Service Manager.getService など) を取得するための複数の静的インターフェイスを提供します。これらの
静的関数は、内部的に getIServiceManager を介して ServiceManagerProxy オブジェクトを取得します。後者は、SM のローカル エージェントとして、IBinder を使用して JNI レイヤーを「通過」し、対応する BpBinder を呼び出してから、ProcessState および IPCThreadState の関連するインターフェイスを使用します。最後に、Binder ドライバーとの通信を完了します。
ここに画像の説明を挿入
bindService 呼び出しプロセス

Android インターフェース記述言語 - AIDL

AIDL は Android Interface Description Language の略です。名前からして言語であり、インターフェイスを記述するために特別に使用される言語です。正確には、クライアント/サーバー通信インターフェースを定義するために使用される記述言語です。
例を通じて、AIDL が Binder Server にもたらす利便性とその内部実装原則を分析します。例として WindowManagerService を取り上げます
(1) WMS は SystemServer で開始されます
(2) AIDL がインターフェースの一貫性を保証する方法を確認します。AIDL を使用するには、最初に *.aidl ファイルを記述してサーバーを記述する必要があります。例えば:

/*IWindowManager.aidl*/
interface IWindowManager
{
    
    …
IWindowSession openSession(in IInputMethodClient client,in
IInputContext inputContext);}

上記のコード セグメントは、openSession のインターフェイス メソッドのみを保持します。IWindowManager.aidl ファイルがツールによって変換されると、次のようになります。

/*IWindowManager.java*/
public interface IWindowManager extends android.os.IInterface
{
    
    
public static abstract class Stub extends android.os.Binder
//Stub表示一个“桩”
implements android.view.IWindowManager
{
    
    
public static android.view.IWindowManager
asInterface(android.os.IBinder 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 TRANSACTION_openSession:
{
    
    }}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements
android.view.IwindowManager
//Proxy就是“代理”,我们已经多次讲解
{
    
    
private android.os.IBinder mRemote;
…
@Override public android.os.IBinder asBinder()
{
    
    
return mRemote;
}
@Override public android.view.IWindowSession
 openSession(com.android.internal.view.IInputMethodClient
client,
 com.android.internal.view.IInputContext inputContext) throws
android.os.RemoteException
{
    
    }
} //Proxy结束
}//Stub结束static final int TRANSACTION_openSession =
(android.os.IBinder.FIRST_CALL_TRANSACTION + 3); 
/*自动分配业务码,大家可以和ServiceManager中的手工分配做下对比*/
}//IWindowManager结束
  • IWindowManager は
    通常、大文字の I で始まり、インターフェイスを表します。AIDL では、すべてのサービス インターフェイスが Iinterface を継承し、これに基づいてこのサーバー サービスに関連するメソッドを宣言します。たとえば、IWindowManager の 2 つのネストされたクラスに加えて、末尾には、openSession、getRealDisplaySize、hasSystemNavBar など、それが提供するインターフェイスのプロトタイプも含まれています。
  • IWindowManager.Stub
    ServiceManagerNative を覚えていますか? Stub の役割はそれに似ています。これには、ネストされたクラス (Stub.Proxy) と、一般的に使用されるさまざまなインターフェイス メソッド (asInterface、asBinder など) が含まれており、その中で最も重要なのは onTransact です。ServiceManagerNative がサーバーとクライアントの両方に対応していることはわかっており、Stub も同様です。実際の使用では、通常、Binder Server の実装クラスは Stub から継承されます。スタブは Binder から継承し、IWindowManager の実装クラス WindowManagerService など、サーバーの IXX インターフェイスを実装します。
public class WindowManagerService extends IWindowManager.Stub
  • IWindowManager.Stub.Proxy
    Proxy はプロキシであり、その機能は ServiceManager Proxy に似ています。したがって、このクラスは Binder Client 向けであり、呼び出し元が Binder Server のローカル プロキシ オブジェクトを簡単に構築できるようにします. 図 (
    AIDL に基づく Binder Server) に示されているように、
    ここに画像の説明を挿入
    aidl ファイルと Java インターフェース ファイルを分析することにより、 AIDL インターフェイスには、IwindowManager、IWindowManager.Stub、および IWindowManager.Stub.Proxy という 3 つの重要なクラスが含まれていることがわかっています。後者の 2 つは、それぞれ WMS サーバーと Binder Client ローカル エージェントの実装に向けられており、どちらも IWindowManager から継承するため、Client と Server が同じサービス インターフェイスで通信することが保証されます。
    (3) Binder ドライバーとの対話方法
    分析の結果、最初に元のシステム サービス プロセスが呼び出されたことがわかりました。
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();

これにより、プログラムは最終的に bind_loop と同様のメイン ループに入ります。したがって、このプロセスの Binder オブジェクトは、ドライバーだけと対話する必要はありませ

SM 、これは WMS が属するケースです。2 つ目は、匿名サーバーの実装です。実名サーバーの場合、addService を使用してそれ自体を SM に登録すると、Binder ドライバーを「渡し」、後者は Binder オブジェクトを proc->nodes にリンクし、target_proc->refs_by_desc および target_proc を計画どおりにリンクします。 -> refs_by_node. このシナリオでは、proc はシステム サービス プロセスであり、target_proc は SM です。つまり、SM 内に WMS を記述する bind_node への参照があります。このようにして、Binder Client が getService を介して SM へのクエリを開始すると、SM は訪問したい WMS ノードの場所を発信者に正確に通知できます。

匿名バインダー サーバー

addService を介して Service Manager に自分自身を登録します。これにより、BinderClient は SM の getService インターフェイスを介して参照を取得できます。このタイプのバインダー サーバーは、「実名」サーバーと呼ばれます。「匿名」バインダー サーバーと呼ばれる Service Manager に登録されていません。匿名性の直接的な利点は、安全率の向上です.たとえば、アプリケーションは特定のサーバー サービスを提供しますが、それを一般に公開したくありません.

おすすめ

転載: blog.csdn.net/jifashihan/article/details/129294282