크로스 프로세스에 대한 관찰자 모드

관찰자 모드는 우리가 평소에 많이 사용하는데 간단한 것 같지만 사실 깊이 파낼 수 있는 것이 많다.예를 들어 관찰자 모델은 크로스 라인, 크로스 프로세스, 크로스 장치를 할 때 어떻게 실현해야 합니까?
먼저 크로스 라인을 살펴보자. 같은 프로세스 안에 있기 때문에 리셋을 등록하면 된다. 관찰 대상이 변동할 때 리셋을 호출하여 관찰자에게 알리면 되지만 라인의 동기화 문제에 주의해야 한다.
다시 한 번 크로스 프로세스를 살펴보자. 같은 프로세스 안에 없기 때문에 등록된 리셋은 크로스 프로세스를 전송해야 한다. 여기서 쉽게 떠오르는 것은 Binder이다. 그러나 존재하는 문제는 관찰자가 전달하는 리셋과 관찰자가 받는 리셋은 두 가지 다른 대상이다. 하나는 Binder이고 다른 하나는 Binder의 Proxy이다.같은 리셋이 여러 프로세스를 통해 전송되고 수신자는 매번 다른 Proxy 대상을 생성하기 때문에 관찰자는 이전에 등록된 리셋을 취소할 수 없다.이 문제를 어떻게 해결할 것인가에 대해 우리는 다음 문장에서 토론할 것이다.
다시 한 번 크로스 기기의 관찰자 모델을 살펴보자. 전형적인 것은 우리가 온도 센서를 통해 집안의 온도를 주목하고 휴대전화의 앱에 실시간으로 반영해야 한다는 것이다.센서가 온도를 서버에 보고하고 서버가 휴대전화 앱에 전달한다. 전제 조건은 휴대전화 앱이 센서에 주목해야 한다는 것이다.서버를 중개로 하고 설비는 서버와 하나의 긴 연결만 유지하지만 데이터는 서버에 의해 천만 개의 휴대전화 클라이언트에게 동시에 전송될 수 있다. 누가 이 센서 데이터를 주목하면 서버에 설정하면 된다.이를 통해 알 수 있듯이 여기는 리셋을 등록한 것이 아니라 협의를 정의한 것이다.
다음에 우리는 코드를 통해 크로스 프로세스의 관찰자 모드의 실현을 알 수 있다. 여기서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();
        }
    }

}

다음은 이 Remote Callback List에 대해 중점적으로 말하자면, 우리는 그것의 명칭에 현혹되어서는 안 된다. 그것은 단지 범형류일 뿐,List와 관계가 없다.왜 일반적인 리스트로 관찰자가 등록한 메아리를 저장하지 않습니까?우리는 관찰자가 같은 리셋을 피관찰자로 중복 등록했는데 피관찰자가 받은 리셋은 같은 대상이 아니다.즉, 크로스 프로세스에서 Binder를 전달하고, 수신자는 매번 새로 만든 Proxy를 받는다.이 경우 일반적인 List에서는 이 Proxy를 로그아웃할 대상과 연결할 수 없습니다. RemoteCallbackList에서는 이 Proxy의 실현을 살펴봅시다.
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를 키로 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 대상을 봉하여 되돌려줍니다.그럼 이제 문제가 생겼습니다. 만약에 여러 번 프로세스를 뛰어넘어 같은 스틸을 전달한다면 수신자의 mRemote는 같은 것입니까?Remote CallbackList의 실현을 보면 답은 긍정적이다.
원본 코드를 읽은 결과 자바 층의 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 대상을 포지셔닝하는 데 있다. 업무 대상은 여러 개가 있을 수 있지만 물리층 대상은 하나면 충분하다.

좋은 웹페이지 즐겨찾기