プロセス間のオブザーバモードについて
観察者モードは私たちが普段使っているものが多く、簡単そうですが、実は深く掘ることができるものがたくさんあります.例えば、観察者モードは、スレッド間、プロセス間、デバイス間でどのように実現されますか?
まずクロススレッドを見てみましょう.同じプロセス内なのでコールバックを登録すればいいのですが、観察対象が変動した場合はコールバックを呼び出して観察者に通知すればいいのですが、スレッド同期の問題に注意してください.
また,プロセス間を見てみると,同一プロセス内ではないため,登録されたコールバックはプロセス間で伝送されるが,ここではBinderが考えやすいが,観察者が伝達するコールバックと,観察者が受信したコールバックは2つの異なるオブジェクトであり,1つはBinder,もう1つはBinderのProxyであるという問題がある.同じコールバックがプロセス間で何度も転送されると、受信者は毎回異なるProxyオブジェクトを生成し、観察者は以前に登録したコールバックをログアウトできなくなります.この問題をどのように解決するかは後述する.
また、デバイスをまたぐ観察者モードを見てみると、典型的には温度センサーを通じて家の温度に注目し、携帯電話のアプリにリアルタイムで反映しなければならない.センサーはサーバーに温度を報告し、サーバーは携帯アプリにプッシュし、携帯アプリがセンサーに注目するように配置されていることを前提としている.サーバーを仲介して、設備はサーバーと1つの長い接続を維持するだけで、しかしデータはサーバーに同時に千万人の携帯電話のクライアントに送ることができて、誰がこのセンサーのデータに関心を持ってサーバーに配置するだけでいいです.ここでは、コールバックを登録するのではなく、プロトコルを定義していることがわかります.
次に,MainActivityは観察者であり,ボタンをクリックすると観察者TestActivityにジャンプし,Bundleを介してインタフェースのBinderを渡し,TestActivityは別のプロセスで動作する.
プロセス間呼び出しでは、エンティティ・エンド(サービス・エンド)は通常Binderスレッド・プールで実行されるため、スレッド同期の問題に注意する必要があります.
次にこのRemoteCallbackListに重点を置いて、私たちはその名前に惑わされてはいけません.それは汎用クラスで、Listとは関係ありません.観察者登録のコールバックを通常のリストで保存しないのはなぜですか?観察者が同じコールバックを被観察者に繰り返し登録し,被観察者が受け取ったコールバックは同じオブジェクトではない実験を行うことができる.すなわち,Binderはプロセス間で伝達され,受信者が受信するたびに新しく作成されたProxyである.この場合、通常のリストではこのProxyをログアウトするオブジェクトに関連付けることはできませんが、RemoteCallbackリストでは、その実装を見てみましょう.
レジスターを重点的に見てProxyを通じてasBinderは対応するmRemoteを取得し,このmRemoteをkeyとしてArrayMapに追加する.これはProxyが複数あることを示していますが、mRemoteは1つしかありません.このmRemoteはどこで設定されているのか、まずコールバックAIDLで生成されたクラスを見てみましょう.
コードから分かるように、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ではなく直接戻ります.
要約すると,プロセス間オブザーバモデルの鍵は,トラフィック層Proxyを介して元のBinderオブジェクトに位置決めすることであり,トラフィックオブジェクトは複数あるが物理層オブジェクトは1つだけで十分である.
まずクロススレッドを見てみましょう.同じプロセス内なのでコールバックを登録すればいいのですが、観察対象が変動した場合はコールバックを呼び出して観察者に通知すればいいのですが、スレッド同期の問題に注意してください.
また,プロセス間を見てみると,同一プロセス内ではないため,登録されたコールバックはプロセス間で伝送されるが,ここでは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つだけで十分である.