IPCメカニズムのAIDLはクライアントコールバックを実現する(四)

15668 ワード

IPCメカニズムのAIDL伝達基礎タイプデータ(一)IPCメカニズムのMessenger例(二)IPCメカニズムのAIDL伝達Parcelable(三)
序文
前の3つの文章はマルチプロセスの通信を実現したが、いずれもクライアントがサービス側を呼び出す方法やクライアントがメッセージを送信し、サービス側と通信する方法しか完成していない.以下ではAIDLにおけるオブザーバーモードを紹介し、クライアントがサービス側の成功犬をバインドし、クライアント側をコールバックする方法を紹介する.
以上のコードはいずれもIPCメカニズムのAIDL伝達Parcelable文章に基づいて実現されているので,異なるコードのみを示し,後で完全なコードを添付する.
Server#IOnNewBookArrivedListener.aidl
package com.aidd.sample;

import com.aidd.sample.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

Server#IBookManager.aidl
package com.aidd.sample;

import com.aidd.sample.Book;
import com.aidd.sample.IOnNewBookArrivedListener;

interface IBookManager {

    List getBookList();

    void addBook(in Book book);

    void registerListener(IOnNewBookArrivedListener listener);

    void unregisterListener(IOnNewBookArrivedListener listener);
}

Server#BookManagerService.java
package com.aidd.sample;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

public class BookManagerService extends Service {

    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
    private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>();

    private RemoteCallbackList mListenerList = new RemoteCallbackList<>();

    private String TAG = "info";

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "iOS"));
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List getBookList() throws RemoteException {
            return mBookList;
        }

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

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
            int count = mListenerList.beginBroadcast();
            Log.i(TAG, "registerListener success, count-->>" + count);
            mListenerList.finishBroadcast();
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
            int count = mListenerList.beginBroadcast();
            Log.i(TAG, "unregisterListener success, count-->>" + count);
            mListenerList.finishBroadcast();
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        Log.i(TAG, "onNewBookArrived, notify booklist-->>" + mBookList.size());
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener arrivedListener = mListenerList.getBroadcastItem(i);
            if (arrivedListener != null)
                arrivedListener.onNewBookArrived(book);
        }
        mListenerList.finishBroadcast();
    }

    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestroyed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size() + 1;
                Book newBook = new Book(bookId, "new book#" + bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

RemoteCallback Listはキー値ペアで、KeyはBinder、valueはコールバックのcallbackであり、コールバックを登録したクライアントを記録することを目的としている.
Client#MainActivity.java
package sample.aidl.com.client;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.aidd.sample.Book;
import com.aidd.sample.IBookManager;
import com.aidd.sample.IOnNewBookArrivedListener;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private IBookManager iBookManager;

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

    public void startIPC(View view) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.aidd.sample", "com.aidd.sample.BookManagerService"));
        this.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBookManager = IBookManager.Stub.asInterface(service);
            try {
                List bookList = iBookManager.getBookList();
                Log.i("info", "    " + bookList.getClass().getCanonicalName());
                Log.i("info", "      " + bookList.toString());
                iBookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iBookManager = null;
        }
    };

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    Log.i("info", "recive new book");
                    break;
            }
        }
    };

    private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            handler.obtainMessage(1, newBook).sendToTarget();
        }
    };

    @Override
    protected void onDestroy() {
        if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
            try {
                iBookManager.unregisterListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }
}

Q: Client#MainActivity.JAvaではなぜIOnNewBookArrivedListenerコールバックメソッドがHandleを使用するのですか?A:クライアントのコールバックメソッドは、クライアントのBinderスレッドプール、すなわちサブスレッドで実行されるため、UIを直接更新することはできませんが、onServiceConnectedとonServiceDisconnectedはUIスレッドで実行されます.
Q: Server#BookManagerService.JAvaのmBinderメソッドはどこで実行されますか?A:クライアントがサービス側を呼び出す方法もBinderスレッドプールで実行されますが、サービス側のBinderスレッドプールで実行されるだけなので、この方法は非UIスレッドです.
Q:サービス側binderが提供する方法は、時間のかかる操作を実行できますか?A:BinderのメソッドはBinderスレッドプールにあるので、時間のかかる操作を実行できますが、クライアントがUIスレッドで呼び出された場合、クライアントが長時間コールバックを受け取れずに詰まってしまい、ANRが発生します.同様に、サービス側がプライマリ・スレッドでクライアントのコールバック・メソッドを呼び出す場合、クライアントのコールバックが時間のかかる操作である場合、サービス側にもANRが表示されます.
ソースのダウンロード