Androidにおけるプロセス間通信の実現-リモートコールプロセスとaidl

16181 ワード

Androidは設計理念でコンポーネント化を強調し,コンポーネント間の依存性は小さい.私たちは往々にして1つのintent要求を出して別のアプリケーションのactivityを起動することができて、あるいはあなたがどのプロセスのサービスを知らないで、あるいは1つのブロードキャストを登録することができて、この事件が発生すればあなたはすべて受け取ることができて、あるいは1つのcontentProviderを検索してあなたの望むデータを得ることができて、これは実はすべてプロセスをまたいで通信するサポートが必要です.ただandroidはそれをパッケージ化するのがこんなに簡単で、アプリケーション開発者はそれが私と同じプロセスにあるかどうかさえ全く気にしなくてもいいです.セキュリティの問題を考えたことがありますか.このように簡単にプロセス間でアクセスできます.セキュリティの問題はどのように保証されますか.もともとプロセスごとに孤島だったが、ipcを通じて、この孤島は世界と通信できるようになった.ここではandroidにおけるセキュリティメカニズムを簡単に紹介します.Androidのセキュリティメカニズムは3つに分かれています.最も基礎的な層で、androidはデータをsystemとdataの2つの領域に分けます.システムは読み取り専用で、dataはアプリケーション自身のデータを格納するために使用され、システムデータが勝手に書き換えられないことを保証します.第2のレイヤは、アプリケーション間のデータを互いに独立させるために使用される.各アプリケーションにはuser idとgroup idがあり、同じuser idで同じ作成者からのみデータにアクセスできます.著者はapkに署名することによって自分を識別する.署名とuidは二重の保証を構成している.3つ目の階層は権限体系であり、これは言うまでもない.本題に戻りますが、androidはどのようにipcを実現しているのでしょうか.答えはbinderです.私はandroidのbinderメカニズムを2編で紹介するつもりです.この1編はどのように使用するかに重点を置いて、プロセス間呼び出しのプロセスとaidlを紹介します.もう一つのbinder実現メカニズムに重点を置いている.Binderはandroidが最初に使用し始めたわけではありません.BeとPalm以前のOpenBinderに発祥し、Dianne Hackbornが開発をリードしています.Hackbornは今googleにいます.android frameworkのエンジニアです.https://lkml.org/lkml/2009/6/25/3から見て、Hackbornがbinderをどのように説明しているかを見ることができます.一言まとめ:In the Android platform,the binder is used for nearly everything thathappens across processes in the core platform.しかしandroidはbinderをほとんどカプセル化して見えず、階層がどうなっているかを見てみましょう.最下位はandroidのashmen(Anonymous shared memoryy)メカニズムで、メモリの割り当てとプロセス間で必要なメモリ共有を支援します.AIDL(android interface definition language)はBinderの使用をカプセル化し,開発者が容易に方法のリモートコールを行うことができ,後で詳細に説明する.Intentは最高レベルの抽象であり、開発者がよく使うプロセス間呼び出しを容易にする.プロセスをまたいでactivityやserviceを起動する方法などについては、androidで非常に基礎的な内容ではありません.ここでは、リモートのメソッド呼び出しを実現する方法について説明します.メソッドのリモートコールはandroidでどこにでもあり、framework/baseのパッケージを勝手に開くと、aidlファイルがたくさん見つかります.AIDLはandroidが開発者のリモートメソッド呼び出しを容易にするために定義した言語である.aidlを使用してリモートメソッド呼び出しを完了するには、3つのステップしか必要ありません:1.呼び出されるメソッドインタフェースをaidlで定義します.2.これらの方法を実装する.3.これらのメソッドを呼び出します.私たちはApiDemoの例を勉強します.アプリパッケージの下にISecondaryがあります.aidl
interface  {  
    /**
     * Request the PID of this service, to do evil things with it.
     */
    int getPid();  

    /**
     * This demonstrates the basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,  
            double aDouble, String aString);  
}

Javaと変わらないように見えます.2つのインタフェースメソッドが定義されていることがわかります.ここからAIDL(androidインタフェース定義言語の由来)がわかります.Androidはaidlをjavaファイルに生成します(eclipseを使用すると自動的に生成されます.genディレクトリの下にあります).生成されたコードは次のとおりです.
/*
* This file is auto-generated.  DO NOT MODIFY.
* Original file: /home/dd/workspace/ApiDemos/src/com/example/android/apis/app/ISecondary.aidl
*/
package com.example.android.apis.app;  
/**
* Example of a secondary interface associated with a service.  (Note that
* the interface itself doesn't impact, it is just a matter of how you
* retrieve it from the service.)
*/
public
interface ISecondary extends android.os.IInterface  
{  
/** Local-side IPC implementation stub class. */
public
static
abstract
class Stub extends android.os.Binder implements com.example.android.apis.app.ISecondary  
{  
private
static
final java.lang.String DESCRIPTOR = "com.example.android.apis.app.ISecondary";  
/** Construct the stub at attach it to the interface. */
public Stub()  
{  
this.attachInterface(this, DESCRIPTOR);  
}  
/**
* Cast an IBinder object into an com.example.android.apis.app.ISecondary interface,
* generating a proxy if needed.
*/
public
static com.example.android.apis.app.ISecondary asInterface(android.os.IBinder obj)  
{  
if ((obj==null)) {  
return
null;  
}  
android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);  
if (((iin!=null)&&(iin instanceof com.example.android.apis.app.ISecondary))) {  
return ((com.example.android.apis.app.ISecondary)iin);  
}  
return
new com.example.android.apis.app.ISecondary.Stub.Proxy(obj);  
}  
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_getPid:  
{  
data.enforceInterface(DESCRIPTOR);  
int _result = this.getPid();  
reply.writeNoException();  
reply.writeInt(_result);  
return
true;  
}  
case TRANSACTION_basicTypes:  
{  
data.enforceInterface(DESCRIPTOR);  
int _arg0;  
_arg0 = data.readInt();  
long _arg1;  
_arg1 = data.readLong();  
boolean _arg2;  
_arg2 = (0!=data.readInt());  
float _arg3;  
_arg3 = data.readFloat();  
double _arg4;  
_arg4 = data.readDouble();  
java.lang.String _arg5;  
_arg5 = data.readString();  
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);  
reply.writeNoException();  
return
true;  
}  
}  
return
super.onTransact(code, data, reply, flags);  
}  
private
static
class Proxy implements com.example.android.apis.app.ISecondary  
{  
private android.os.IBinder mRemote;  
Proxy(android.os.IBinder remote)  
{  
mRemote = remote;  
}  
public android.os.IBinder asBinder()  
{  
return mRemote;  
}  
public java.lang.String getInterfaceDescriptor()  
{  
return DESCRIPTOR;  
}  
/**
     * Request the PID of this service, to do evil things with it.
     */
public
int getPid() throws android.os.RemoteException  
{  
android.os.Parcel _data = android.os.Parcel.obtain();  
android.os.Parcel _reply = android.os.Parcel.obtain();  
int _result;  
try {  
_data.writeInterfaceToken(DESCRIPTOR);  
mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);  
_reply.readException();  
_result = _reply.readInt();  
}  
finally {  
_reply.recycle();  
_data.recycle();  
}  
return _result;  
}  
/**
     * This demonstrates the basic types that you can use as parameters
     * and return values in AIDL.
     */
public
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException  
{  
android.os.Parcel _data = android.os.Parcel.obtain();  
android.os.Parcel _reply = android.os.Parcel.obtain();  
try {  
_data.writeInterfaceToken(DESCRIPTOR);  
_data.writeInt(anInt);  
_data.writeLong(aLong);  
_data.writeInt(((aBoolean)?(1):(0)));  
_data.writeFloat(aFloat);  
_data.writeDouble(aDouble);  
_data.writeString(aString);  
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);  
_reply.readException();  
}  
finally {  
_reply.recycle();  
_data.recycle();  
}  
}  
}  
static
final
int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);  
static
final
int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);  
}  
/**
     * Request the PID of this service, to do evil things with it.
     */
public
int getPid() throws android.os.RemoteException;  
/**
     * This demonstrates the basic types that you can use as parameters
     * and return values in AIDL.
     */
public
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;  
}  

私たちはandroidツールが私たちが書いたaidlファイルをどのようなファイルを生成し、どのような仕事をしているのかを分析します.まずこのインタフェースはandroidを継承します.os.IInterface.aidlファイルによって生成されるすべてのベースクラスです.インタフェースにはBinderから継承され、生成されたjavaインタフェースISecondaryを実現する内部クラスStubがあります.しかし、定義したインタフェースメソッドは実装されていません.これらのインタフェースの方法は、実際には私たちに実装されています.ApiDemoでは、RemoteServiceクラスがこれらの方法を実装しています.
/**
     * A secondary interface to the service.
     */
    private
final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() {  
        public
int getPid() {  
            return Process.myPid();  
        }  
        public
void basicTypes(int anInt, long aLong, boolean aBoolean,  
                float aFloat, double aDouble, String aString) {  
        }  
    };  

これが私たちがしなければならない第2の操作で、これらの方法を実現します.このインタフェースクラスを見続けます.stubで重要な方法asInterface(android.os.IBinder obj)を実現した.このメソッドでは、ISecondaryのインスタンスがあるかどうかをクエリーします.これは、同じアプリケーションで呼び出すかどうかをクエリーします.では、リモートコールを実行せずに、直接ローカルコールすればいいです.ローカルインタフェースでない場合、Proxyオブジェクトが返されます.ProxyクラスはStubの内部クラスであり、ISecondaryインタフェースも同様に実現されている.しかし、これらのインタフェースメソッドは実装されています.これは、リモートコールを行う場合、stubクラスのasInterfaceメソッドによって取得されるProxyクラスのインスタンスを取得する必要があることを意味します.ApiDemoでこのインスタンスを取得する方法を見てみましょう.
/**
* Class for interacting with the secondary interface of the service.
*/
private ServiceConnection mSecondaryConnection = new ServiceConnection() {  
    public
void onServiceConnected(ComponentName className,  
            IBinder service) {  
        // Connecting to a secondary interface is the same as any
        // other interface.
        mSecondaryService = ISecondary.Stub.asInterface(service);  
        mKillButton.setEnabled(true);  
    }  

    public
void onServiceDisconnected(ComponentName className) {  
        mSecondaryService = null;  
        mKillButton.setEnabled(false);  
    }  
};  

このリモートインスタンスはonServiceConnectedで取得されていることがわかりますが、具体的にはどのように入手できますか?
ServiceConnection              service    bindService     。
bindService(new Intent(ISecondary.class.getName()),  
                        mSecondaryConnection, Context.BIND_AUTO_CREATE);

Activity Management ServiceはbindServiceの場合、Activity Threadメソッドを呼び出し、Binderリファレンスを渡します.Activity ThreadはServiceConnectionのOnServiceConnectedメソッドをコールバックし、このBinderオブジェクト、すなわちanInterfaceメソッドのサービスに転送します.これにより、プロセス全体が完了するとリモート・インスタンスが得られ、後でリモート・メソッドを呼び出すためにグローバル変数に保存されます.このとき、メソッド呼び出しを行うには、3番目のステップを実行することができます.
int pid = mSecondaryService.getPid();  

実はこの時、リモートコールが完了し、pidの値を取得しました.しかし、私たちは見続けてもいいです.別の方法basicTypeを見て、apidemoは使用していませんが、別の方法はパラメータに転送され、より代表的な意味を持っています.basicType方法を実現し、Proxyを通じてリモートコールを行います(コードは貼り付けません).この呼び出しはproxyオブジェクトによってpacelでパッケージできるベースデータ型に変換され、パラメータもシーケンス化されてパケットに書き込まれます.Binderではintタイプのみが渡されるため、メソッド名を識別するために使用されるユーザ定義int型codeがtransactionに割り当てられます.これには、クライアントとリモート・サービス・エンドの約束が必要です.方法は以下のように実現される.
public
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException  
{  
android.os.Parcel _data = android.os.Parcel.obtain();  
android.os.Parcel _reply = android.os.Parcel.obtain();  
try {  
_data.writeInterfaceToken(DESCRIPTOR);  
_data.writeInt(anInt);  
_data.writeLong(aLong);  
_data.writeInt(((aBoolean)?(1):(0)));  
_data.writeFloat(aFloat);  
_data.writeDouble(aDouble);  
_data.writeString(aString);  
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);  
_reply.readException();  
}  
finally {  
_reply.recycle();  
_data.recycle();  
}  

メソッドは、まずobtainメソッドによって2つのParcelオブジェクトを取得します.writeInterfaceTokenメソッドを呼び出して、サービス側が認識できるように識別します.次にパラメータを書き込み、この書き込み順序と取り出し順序が一致しなければならないことに注意します.次にProxyに渡されるbinderオブジェクトに対してtransactメソッドが呼び出され,このメソッドではcodeがパラメータとして渡される.Pacelオブジェクトはjniインタフェースを介してBinderのC++空間に渡され,最終的にBinder駆動に渡される.binderドライバは、クライアント・プロセスをスリープさせ、送信されたpacelデータをクライアント・プロセスからサービス・エンド・プロセスにマッピングします.その後,逆方向の伝達はbinder駆動からC++中間層に伝達され,JNIを介してjava層に伝達される.Stubのontransactメソッドが呼び出されます.方法は次のとおりです.
@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_getPid:  
{  
data.enforceInterface(DESCRIPTOR);  
int _result = this.getPid();  
reply.writeNoException();  
reply.writeInt(_result);  
return
true;  
}  
case TRANSACTION_basicTypes:  
{  
data.enforceInterface(DESCRIPTOR);  
int _arg0;  
_arg0 = data.readInt();  
long _arg1;  
_arg1 = data.readLong();  
boolean _arg2;  
_arg2 = (0!=data.readInt());  
float _arg3;  
_arg3 = data.readFloat();  
double _arg4;  
_arg4 = data.readDouble();  
java.lang.String _arg5;  
_arg5 = data.readString();  
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);  
reply.writeNoException();  
return
true;  
}  
}  
return
super.onTransact(code, data, reply, flags);  
}  

まずcodeの判断により,対応方法の内容を実行し,データに対して順次パケットを解き,パラメータを読み出す.最終的にメソッドを呼び出し、戻り値をparcelに書き込みbinderドライバに渡します.binderドライバは、クライアントプロセスを再起動し、戻り値をproxyオブジェクトに渡し、最後にパケットを解除してproxyメソッドの戻り値とする.このプロセスから、aidlは主にデータの包装とパケットの解除のプロセスを完了し、transactプロセスを呼び出すのに役立つことがわかります.伝達するためのパケットはparcelと呼ばれています.parcelについては、公式ドキュメントの説明を直接見てみましょう.Container for a message (data and object references) that can be sent through an IBinder. A Parcel can contain both flattened data that will be unflattened on the other side of the IPC (using the various methods here for writing specific types, or the general Parcelable interface), and references to live IBinder objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel.伝達するパラメータがベースタイプでない場合は、parcelableのインスタンスとしてパッケージする必要があります.次のようになります.
public
class MyParcelable implements Parcelable {  
    private
int mData;  

    public
int describeContents() {  
        return
0;  
    }  

    public
void writeToParcel(Parcel out, int flags) {  
        out.writeInt(mData);  
    }  

    public
static
final Parcelable.Creator<MyParcelable> CREATOR  
            = new Parcelable.Creator<MyParcelable>() {  
        public MyParcelable createFromParcel(Parcel in) {  
            return
new MyParcelable(in);  
        }  

        public MyParcelable[] newArray(int size) {  
            return
new MyParcelable[size];  
        }  
    };  

    private MyParcelable(Parcel in) {  
        mData = in.readInt();  
    }  
}

最後にこの図を見てみましょう.はっきりしたのではないでしょうか.aidlを使わずに手動でリモートコールを書いても完成すると思います.androidは非常によく設計されており、aidlもipcを使う必要があるときに開発者に非常に友好的だと言わざるを得ない.Androidにおけるipc通信の使用とプロセスはほぼ同じである.
回転元:http://bbs.51cto.com/thread-978666-1.html