關於跨進程的觀察者模式

觀察者模式我們平時用的很多,好像很簡單,其實還是有很多東西可以深挖的。比如觀察者模式在跨線程,跨進程,跨設備時該如何實現呢?

先來看看跨線程,由於是在同一個進程內,所以註冊回調就好了,當觀察對象發生變動時,調用回調通知觀察者即可,不過要注意線程同步的問題。

再來看看跨進程,由於不在同一個進程內,所以註冊的回調要跨進程傳輸,這裏容易想到的是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對象,業務對象可以有很多個,但是物理層對象只有一個就夠了。

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