2011年12月26日 星期一

Android - Pass Data (7) - 利用aidl和callback機制,在不同apk中讓activity和service傳遞資料

Pass Data(4)中對aidl有簡單介紹,但是那個範例並不能展現為何需要使用aidl的方式來傳遞資料。因為如果service和activity都在同一個apk,其實只要利用bindservice就可以傳遞資料了,並不用非得使用aidl。那到底aidl用在何時,通常應該是service未來會被其他apk(客戶端)使用 ,即兩者不在同一個process 運行時,這時會把xxx.aidl檔產生出的xxx.java檔,給其他apk使用。這樣2者即可進行IPC。因為xxx.java當初產生的套件名稱,和客戶端的套件名稱可能不同,所以有可能要另外開資料夾在客戶端的src裡面,這樣xxx.java才會符合套件名稱,而客戶端才能import進來。

舉例來說,比如你開發了一支一般可在Launcher執行的程式,另外又開發了一支Widget,
兩支是不同的apk,那在Launcher就可以有activity和service,而widget就去呼叫service取得資料。
本範例中是用API Demos裡面App/Service/Remote Service Binding修改而成,
總共三支apk:
(1) TestAidlServeice.apk
(2) TestAidlActivity1.apk
(3) TestAidlActivity2.apk

TestAidlActivity1利用bindService的方式和TestAidlService綁定後,
一直用Call Back的方式,取得來自TestAidlService的資料

TestAidlActivity2的程式和TestAidlActivity1相同。而且CallBack物件是跑在相同的process,
所以可以發現TestAidlService傳給TestAidlActivity1和TestAidlActivity2的值是連續的,
而不會有重覆的情形發生。

TestAidlService主要程式如下:
public class RemoteService extends Service {
   
    // 因為這支RemoteService可能會和很多支客戶端程式連接,每支客戶端如果都要用到ICallBack功能
    // 所以RemoteCallbackList<ICallBack>可以儲存很多個客戶端的ICallBack
    final RemoteCallbackList<ICallBack> mCallbacks= new RemoteCallbackList<ICallBack>();
   
    int mValue = 0;
    private static final int REPORT_MSG = 1;
   
    private final IService.Stub mBinder = new IService.Stub() {
        //增加一個CallBack
        public void registerCallback(ICallBack cb) {
            Log.d("gill","registerCallback() - service");
            if (cb != null) mCallbacks.register(cb);
        }
       
        //移除一個CallBack
        public void unregisterCallback(ICallBack cb) {
            Log.d("gill","unregisterCallback() - service");
            if (cb != null) mCallbacks.unregister(cb);
        }
    };
   
    @Override
    public void onCreate() {
        Log.d("gill","onCreate() - service");
        mHandler.sendEmptyMessage(REPORT_MSG);
    }
   
    @Override
    public void onDestroy() { 
        mCallbacks.kill();
        mHandler.removeMessages(REPORT_MSG);
    }
   
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("gill","onBind() - IService | intent.getAction() = " + intent.getAction());
        if(intent.getAction().equals("com.test.aidl.service.RemoteService")){
            return mBinder;
        }
        return null;
    }
   
    private final Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            Log.d("gill","handleMessage() - service");
            switch (msg.what) {   
                case REPORT_MSG: {
                    int value = ++mValue;
                    Log.d("gill","handleMessage()/what - service | value = " + value);
                    // Broadcast 所有的客戶端程式,因為本例有2個Activity,所以N會等於2
                    final int N = mCallbacks.beginBroadcast();
                    for (int i=0; i<N; i++) {
                        Log.d("gill","N = " +N);
                        try {
                            //執行valueChanged()的動作,而具體要做的事情,已經被寫在Activity裡面了
                            mCallbacks.getBroadcastItem(i).valueChanged(value);
                        } catch (RemoteException e) {
                        }
                    }
                    mCallbacks.finishBroadcast();
                   
                    // 每一秒就做一次改變 mValue的動作,然後再把值送到客戶端(就是2支Activity)
                    sendMessageDelayed(obtainMessage(REPORT_MSG), 1*1000);
                } break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
}

TestAidlActivity1主要程式如下:
import com.test.aidl.service.ICallBack;
import com.test.aidl.service.IService;
public class Main1 extends Activity {
  
    // 這個 mService是由RemoteService所傳過來的
    IService mService = null;
 
    TextView mCallbackText;
    private boolean mIsBound;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d("gill","onCreate() - Binding activity");
       
        setContentView(R.layout.main);

        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(mUnbindListener);
       
        mCallbackText = (TextView)findViewById(R.id.callback);
        mCallbackText.setText("Not attached.");
    }


    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            Log.d("gill","onServiceConnected() - mConnection");

            //當和RemoteService綁定時,獲得實作IService所有方法(registerCallback/registerCallback)的IBinder實體
            mService = IService.Stub.asInterface(service);
            mCallbackText.setText("Attached.");

            try {
                //mCallback由客戶端實作ICallBack裡的方法
                //把mCallback丟進RemoteService裡面的RemoteCallbackList<ICallBack> mCallbacks
                //如此一來,mCallbacks可以用"Call Back function"的機制,利用valueChanged(value)把值秀回到客戶端上
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
            }

        }

        public void onServiceDisconnected(ComponentName className) {
            Log.d("gill","onServiceDisconnected() - mConnection");
            mService = null;
            mCallbackText.setText("Disconnected.");
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            //按下bind按鈕後,就去bindService (和RemoteService綁定)
           
            Log.d("gill","onBindListener() - Binding activity | IService.class.getName() = "+IService.class.getName());

            bindService(new Intent("com.test.aidl.service.RemoteService"),
                    mConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
            mCallbackText.setText("Binding.");
        }
    };

    private OnClickListener mUnbindListener = new OnClickListener() {
        public void onClick(View v) {
            Log.d("gill","onUnbindListener() - Binding activity");
            if (mIsBound) {

                //如果之前有綁定的話,此時要取消註冊
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                    }
                }
               
                //按下unbind按鈕後,不再和RemoteService綁定
                unbindService(mConnection);
                mIsBound = false;
                mCallbackText.setText("Unbinding.");
            }
        }
    };
   
    private ICallBack mCallback = new ICallBack.Stub() {
        //因為這個mCallback會被丟到RemoteService去,此時他的運作是在RemoteService的process裡,
        //因為他不是在Main1這個Activity的main thread,所以無法更改畫面,只能透過hander機制來改變畫面
        public void valueChanged(int value) {
            Log.d("gill","valueChanged() - Binding activity | the value = "+value);
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
        }
    };
   
    private static final int BUMP_MSG = 1;
   
    private Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            Log.d("gill","handleMessage() - Binding activity");
            switch (msg.what) {
                case BUMP_MSG:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
       
    };
}

TestAidlActivity2的程式和TestAidlActivity1程式相同,不再描述。

執行畫面如下:




先 前提過用bindService的方式,如果Activity結束,Service也會結束。反之Service則會在背景繼續運作。測試方式可以先執行 TestAidlActivity1,然後跑到Received from service:10,按下home再執行TestAidlActivity2去bindServeice,會發現Received from service:10之後的數值。
但是如果先執行TestAidlActivity1,然後跑到Received from service:10,按下BackKey再執行TestAidlActivity2去bindServeice,會發現Received from service:會從新開始。

2 則留言: