プロセス間のオブザーバモードについて


観察者モードは私たちが普段使っているものが多く、簡単そうですが、実は深く掘ることができるものがたくさんあります.例えば、観察者モードは、スレッド間、プロセス間、デバイス間でどのように実現されますか?
まずクロススレッドを見てみましょう.同じプロセス内なのでコールバックを登録すればいいのですが、観察対象が変動した場合はコールバックを呼び出して観察者に通知すればいいのですが、スレッド同期の問題に注意してください.
また,プロセス間を見てみると,同一プロセス内ではないため,登録されたコールバックはプロセス間で伝送されるが,ここではBinderが考えやすいが,観察者が伝達するコールバックと,観察者が受信したコールバックは2つの異なるオブジェクトであり,1つはBinder,もう1つはBinderのProxyであるという問題がある.同じコールバックがプロセス間で何度も転送されると、受信者は毎回異なるProxyオブジェクトを生成し、観察者は以前に登録したコールバックをログアウトできなくなります.この問題をどのように解決するかは後述する.
また、デバイスをまたぐ観察者モードを見てみると、典型的には温度センサーを通じて家の温度に注目し、携帯電話のアプリにリアルタイムで反映しなければならない.センサーはサーバーに温度を報告し、サーバーは携帯アプリにプッシュし、携帯アプリがセンサーに注目するように配置されていることを前提としている.サーバーを仲介して、設備はサーバーと1つの長い接続を維持するだけで、しかしデータはサーバーに同時に千万人の携帯電話のクライアントに送ることができて、誰がこのセンサーのデータに関心を持ってサーバーに配置するだけでいいです.ここでは、コールバックを登録するのではなく、プロトコルを定義していることがわかります.
次に,MainActivityは観察者であり,ボタンをクリックすると観察者TestActivityにジャンプし,Bundleを介してインタフェースのBinderを渡し,TestActivityは別のプロセスで動作する.
プロセス間呼び出しでは、エンティティ・エンド(サービス・エンド)は通常Binderスレッド・プールで実行されるため、スレッド同期の問題に注意する必要があります.
public class MainActivity extends Activity implements View.OnClickListener {

    private List<Book> mBooks = new ArrayList<Book>();
    private RemoteCallbackList<IBookListener> mListeners = new RemoteCallbackList<IBookListener>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn).setOnClickListener(this);
    }

    private void callListener(Book book) {
        int n = mListeners.beginBroadcast();

        for (int i = 0; i < n; i++) {
            IBookListener l = mListeners.getBroadcastItem(i);

            try {
                l.onBookArrived(book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        mListeners.finishBroadcast();
    }

    private IBookManager.Stub mManager = new IBookManager.Stub() {

        @Override
        public List<Book> getBooks() throws RemoteException {
            return mBooks;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBooks.add(book);
            callListener(book);
        }

        @Override
        public void registerListener(IBookListener l) throws RemoteException {
            mListeners.register(l);
        }

        @Override
        public void unregisterListener(IBookListener l) throws RemoteException {
            mListeners.unregister(l);
        }
    };

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch (v.getId()) {
        case R.id.btn:
            Intent intent = new Intent();

            Bundle bundle = new Bundle();
            bundle.putBinder("binder", mManager);
            intent.putExtra("data", bundle);

            intent.setClass(this, TestActivity.class);

            startActivity(intent);
            break;
        }

    }

}

public class TestActivity extends Activity {

    private IBookManager mManager;

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

        Intent intent = getIntent();
        Bundle data = (Bundle) intent.getParcelableExtra("data");
        mManager = (IBookManager) IBookManager.Stub.asInterface(data.getBinder("binder"));

        try {
            mManager.registerListener(mListener);
            mManager.addBook(new Book("one"));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private final IBookListener.Stub mListener = new IBookListener.Stub() {

        @Override
        public void onBookArrived(Book book) throws RemoteException {
            // TODO Auto-generated method stub
        }
    };

    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();

        try {
            mManager.unregisterListener(mListener);
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

次にこのRemoteCallbackListに重点を置いて、私たちはその名前に惑わされてはいけません.それは汎用クラスで、Listとは関係ありません.観察者登録のコールバックを通常のリストで保存しないのはなぜですか?観察者が同じコールバックを被観察者に繰り返し登録し,被観察者が受け取ったコールバックは同じオブジェクトではない実験を行うことができる.すなわち,Binderはプロセス間で伝達され,受信者が受信するたびに新しく作成されたProxyである.この場合、通常のリストではこのProxyをログアウトするオブジェクトに関連付けることはできませんが、RemoteCallbackリストでは、その実装を見てみましょう.
public class RemoteCallbackList<E extends IInterface> {
    ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
    private Object[] mActiveBroadcast;
    private int mBroadcastCount = -1;

    public boolean register(E callback) {
        return register(callback, null);
    }

    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }

    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                return true;
            }
            return false;
        }
    }
}

レジスターを重点的に見てProxyを通じてasBinderは対応するmRemoteを取得し,このmRemoteをkeyとしてArrayMapに追加する.これはProxyが複数あることを示していますが、mRemoteは1つしかありません.このmRemoteはどこで設定されているのか、まずコールバックAIDLで生成されたクラスを見てみましょう.
public interface IBookListener extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.example.testmultiprocess.IBookListener {
        private static final java.lang.String DESCRIPTOR = "com.example.testmultiprocess.IBookListener";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static com.example.testmultiprocess.IBookListener asInterface(
                android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.example.testmultiprocess.IBookListener))) {
                return ((com.example.testmultiprocess.IBookListener) iin);
            }
            return new com.example.testmultiprocess.IBookListener.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_onBookArrived: {
                data.enforceInterface(DESCRIPTOR);
                com.example.testmultiprocess.Book _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.example.testmultiprocess.Book.CREATOR
                            .createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.onBookArrived(_arg0);
                reply.writeNoException();
                return true;
            }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.example.testmultiprocess.IBookListener {
            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;
            }

            @Override
            public void onBookArrived(com.example.testmultiprocess.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_onBookArrived, _data,
                            _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_onBookArrived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public void onBookArrived(com.example.testmultiprocess.Book book)
            throws android.os.RemoteException;
}

コードから分かるように、mRemoteはProxyのコンストラクション関数に入力されていますが、このProxyのコンストラクションはStubのasInterfaceにあり、StubのasInterfaceは誰に呼び出されますか?IBookManagerですStubのonTransact里.ここまで流れ全体がはっきりしていて、観察者が被観察者にコールバックを登録すると、プロセスにまたがってコールバックのStubが渡され、被観察者が受け取ったのはmRemoteで、これはProxyです.これからStubに異動します.asInterfaceはビジネスインタフェースクラスを取得し、実際にはmRemoteでビジネス層をカプセル化したProxyオブジェクトが返されます.では、今問題が来ています.もし何度もプロセスをまたいで同じStubを渡したら、受信者のmRemoteは同じですか?RemoteCallbackListの実装から見ると答えは肯定的である.
ソースコードを読むと、JavaレイヤのBinderとProxyはNativeレイヤでそれぞれ1つのオブジェクトに対応しており、彼らの間には1対1の関係があることがわかります.すなわち、mRemoteの一意性を保証するには、NativeレイヤのProxyオブジェクトが一意であることを保証すればよいが、このNativeレイヤのProxyオブジェクトはBinderドライバレイヤのBinderリファレンスと一対一の関係にあり、BinderドライバはプロセスごとにBinderリファレンスの配列を維持し、NativeレイヤのProxyオブジェクトはインデックスと配列のBinderリファレンスに対応する.Binderドライバは、各プロセスが同じBinderエンティティに対して1つのBinder参照しか存在しないことを保証するかどうか、次の関数を見てみましょう.この関数は、Binderドライバで指定したプロセスでbinderエンティティのbinder参照を検索するために使用されます.ここでは、各プロセスに対してbinderリファレンスの赤と黒のツリーが維持され、対応するbinderリファレンスが検索されると、毎回newではなく直接戻ります.
static struct binder_ref *binder_get_ref_for_node(struct binder_proc *proc,
                          struct binder_node *node) {
    struct rb_node *n;
    struct rb_node **p = &proc->refs_by_node.rb_node;
    struct rb_node *parent = NULL;
    struct binder_ref *ref, *new_ref;
    while (*p) {
        parent = *p;
        ref = rb_entry(parent, struct binder_ref, rb_node_node);
        if (node < ref->node)
            p = &(*p)->rb_left;
        else if (node > ref->node)
            p = &(*p)->rb_right;
        else
            return ref;
    }
    new_ref = kzalloc(sizeof(*ref), GFP_KERNEL);
    if (new_ref == NULL)
        return NULL;
    binder_stats_created(BINDER_STAT_REF);
    new_ref->debug_id = ++binder_last_id;
    new_ref->proc = proc;
    new_ref->node = node;
    rb_link_node(&new_ref->rb_node_node, parent, p);
    rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node);
    ..........
    return new_ref;
}

要約すると,プロセス間オブザーバモデルの鍵は,トラフィック層Proxyを介して元のBinderオブジェクトに位置決めすることであり,トラフィックオブジェクトは複数あるが物理層オブジェクトは1つだけで十分である.