关于跨进程的观察者模式

观察者模式我们平时用的很多,好像很简单,其实还是有很多东西可以深挖的。比如观察者模式在跨线程,跨进程,跨设备时该如何实现呢?

先来看看跨线程,由于是在同一个进程内,所以注册回调就好了,当观察对象发生变动时,调用回调通知观察者即可,不过要注意线程同步的问题。

再来看看跨进程,由于不在同一个进程内,所以注册的回调要跨进程传输,这里容易想到的是Binder,不过会存在的问题是观察者传递的回调和被观察者接收到的回调是两个不同的对象,一个是Binder,另一个是Binder的Proxy。同一个回调跨进程传输若干次,接收方每次都会产生不同的Proxy对象,这样观察者就无法注销之前注册的回调了。如何解决这个问题我们会在下文探讨。

再来看看跨设备的观察者模式,典型的有我们通过温度传感器关注家里的温度,要实时反映在手机的APP上。传感器上报温度到服务器,服务器再推送给手机APP,前提是手机APP配置了要关注传感器。用服务器作中介,设备只用与服务器维持一个长连接,但是数据可以被服务器同时推送给千万个手机客户端,谁要是关注这个传感器数据只需配置到服务器即可。可见这里就不是注册回调了,而是定义一套协议。

接下来我们通过代码来了解跨进程的观察者模式的实现,这里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没有关系。为什么不用普通的List来保存观察者注册的回调呢?我们可以做个实验,观察者重复注册同一个回调到被观察者,被观察者拿到的回调却不是同一个对象。也就是说跨进程传递Binder,接收方每次收到的都是新创建的Proxy。这种情况下普通的List无法将这个Proxy和要注销的对象相关联,而RemoteCallbackList可以做到,我们看看它的实现:

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;
        }
    }
}

我们重点看register,这里通过Proxy.asBinder拿到对应的mRemote,然后以这个mRemote为key添加到ArrayMap中。这说明Proxy可以有很多个,但是mRemote只有一个。这个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层都分别对应着一个对象,他们之间是一对一的关系。也就是说要保证mRemote的唯一性,只要确保Native层的Proxy对象是唯一的就行了,而这个Native层的Proxy对象又和Binder驱动层中的Binder引用是一对一的关系,Binder驱动会为每个进程维护一个Binder引用的数组,Native层的Proxy对象是通过索引和数组中的Binder引用对应上的。Binder驱动是否会保证每个进程对于同一个Binder实体只能存在一个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对象,业务对象可以有很多个,但是物理层对象只有一个就够了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章