Android中AIDL實現程式通訊(附原始碼下載)

孫群發表於2016-07-10

AIDL概述

之前的部落格《Android中通過Messenger與Service實現程式間雙向通訊》演示瞭如何通過Messenger實現與Service進行跨程式通訊,即IPC。但用Messenger實現的IPC存在一點不足:Service內部維護著一個Messenger,Messenger內部又維護著一個Hanlder,當多個client向該Service傳送Message時,這些Message需要依次進入Hanlder的訊息佇列中,Hanlder只能處理完一個Message之後,再從訊息佇列中取出下一個進行處理,這樣Service相當於只能對客戶端傳來的Message進行序列執行,而不能並行執行。如果想讓Service跨程式併發處理客戶端發來的請求,那麼就需要使用AIDL。

AIDL的全稱是Android Interface Definition Language,直譯過來就是Android介面定義語言。在Android中,正常情況下,不同程式是不能互相訪問對方的記憶體資料的。為了實現程式間記憶體訪問,可以將一個程式中的記憶體資料分解成Android認識的原子型別,然後在另一個程式中將原子型別的資料重新組合成物件。編寫將原子型別的資料重新組合成物件的程式碼比較費時,Android通過AIDL可以實現該功能。

其實Messenger和AIDL並不是對立的,Messenger內部也是基於AIDL實現的。當使用Messenger的時候,Service會用一個Handler序列執行客戶端發來的多個請求,即單執行緒處理;當直接使用AIDL的時候,Service會維護一個執行緒池,用該執行緒池併發處理客戶端發來的多個請求。

再次強調一下,如果你想實現跨程式通訊,但是並不關注多執行緒併發處理,那麼你只需要使用Messenger即可,無需編寫AIDL程式碼,只有當你想讓Service併發處理客戶端的請求的時候,才需要編寫AIDL程式碼。


AIDL之Service端實現

使用AIDL的典型案例就是一個App的Activity啟動了一個Service,並且讓該Service執行在一個新的程式中,這種在新程式中的Service我們可以叫做遠端Service,該Service會從伺服器拿到最新的資料,客戶端可以呼叫Service相應的方法獲得最新資料,更近一步的話,Service拿到最新資料之後可以主動向客戶端傳送。

要想使用AIDL,要同時在Service和client都編寫相應的程式碼。

在Service端,首先要建立.aidl檔案,然後需要實現.aidl中定義的各種方法。具體如下:

  1. 首先要建立.aidl檔案

    在Android Studio中右鍵建立AIDL檔案,該檔案其實就是定義一個介面Interface,和定義一般的Java介面差不多,需要在該介面中定義一些方法,這些方法就是允許客戶端跨程式呼叫的方法,我建立了IRemoteService.aidl檔案,如下所示:

    // IRemoteInterface.aidl
    package com.ispring.aidldemo;
    
    import com.ispring.aidldemo.IRemoteInterfaceCallback;
    
    interface IRemoteInterface {
        //獲取Service執行的程式ID
        int getPid();
    
        //從Service中獲取最新的資料
        int getData();
    
        //通過向Service中註冊回撥,可以實現Service主動向客戶端推送資料
        void registerCallback(IRemoteInterfaceCallback cb);
    
        //刪除註冊的回撥
        void unregisterCallback(IRemoteInterfaceCallback cb);
    }

    我們在上面的.aidl檔案中,定義了四個方法供客戶端呼叫,通過getPid()方法,可以獲取Service執行的程式ID;通過getData()方法,可以讓客戶端從Service獲取最新的資料;registerCallback和unregisterCallback是用來進行註冊、反註冊客戶端回撥的,後面會詳細解釋。

    AIDL中定義的方法可以接受如下的引數型別作為形參:

    1. 所有的基本型別,例如int, long, char, boolean等等

    2. String、CharSequence、List、Map

    在編寫完上述IRemoteInterface.aidl檔案後,我們編譯一下Project,Android Studio會自動幫我們生成一個IRemoteInterface.java檔案,如下所示(對於下面的程式碼,我們無需關注具體實現,大家瞭解一下即可):

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: D:\\iWork\\AndroidStudioProjects\\AidlDemo\\app\\src\\main\\aidl\\com\\ispring\\aidldemo\\IRemoteInterface.aidl
     */
    package com.ispring.aidldemo;
    public interface IRemoteInterface extends android.os.IInterface {
        /** Local-side IPC implementation stub class. */
        public static abstract class Stub extends android.os.Binder implements com.ispring.aidldemo.IRemoteInterface {
            private static final java.lang.String DESCRIPTOR = "com.ispring.aidldemo.IRemoteInterface";
            /** Construct the stub at attach it to the interface. */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
            /**
             * Cast an IBinder object into an com.ispring.aidldemo.IRemoteInterface interface,
             * generating a proxy if needed.
             */
            public static com.ispring.aidldemo.IRemoteInterface asInterface(android.os.IBinder obj) {
                if ((obj==null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin!=null)&&(iin instanceof com.ispring.aidldemo.IRemoteInterface))) {
                    return ((com.ispring.aidldemo.IRemoteInterface)iin);
                }
                return new com.ispring.aidldemo.IRemoteInterface.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_getPid:
                        {
                            data.enforceInterface(DESCRIPTOR);
                            int _result = this.getPid();
                            reply.writeNoException();
                            reply.writeInt(_result);
                            return true;
                        }
                    case TRANSACTION_getData:
                        {
                            data.enforceInterface(DESCRIPTOR);
                            int _result = this.getData();
                            reply.writeNoException();
                            reply.writeInt(_result);
                            return true;
                        }
                    case TRANSACTION_registerCallback:
                        {
                            data.enforceInterface(DESCRIPTOR);
                            com.ispring.aidldemo.IRemoteInterfaceCallback _arg0;
                            _arg0 = com.ispring.aidldemo.IRemoteInterfaceCallback.Stub.asInterface(data.readStrongBinder());
                            this.registerCallback(_arg0);
                            reply.writeNoException();
                            return true;
                        }
                    case TRANSACTION_unregisterCallback:
                        {
                            data.enforceInterface(DESCRIPTOR);
                            com.ispring.aidldemo.IRemoteInterfaceCallback _arg0;
                            _arg0 = com.ispring.aidldemo.IRemoteInterfaceCallback.Stub.asInterface(data.readStrongBinder());
                            this.unregisterCallback(_arg0);
                            reply.writeNoException();
                            return true;
                        }
                }
    
                return super.onTransact(code, data, reply, flags);
            }
    
            private static class Proxy implements com.ispring.aidldemo.IRemoteInterface {
                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 int getPid() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    int _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.readInt();
                    }finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
    
                @Override public int getData() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    int _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_getData, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.readInt();
                    }finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
    
                @Override public void registerCallback(com.ispring.aidldemo.IRemoteInterfaceCallback cb) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeStrongBinder((((cb!=null))?(cb.asBinder()):(null)));
                        mRemote.transact(Stub.TRANSACTION_registerCallback, _data, _reply, 0);
                        _reply.readException();
                    }finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
    
    
                @Override public void unregisterCallback(com.ispring.aidldemo.IRemoteInterfaceCallback cb) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeStrongBinder((((cb!=null))?(cb.asBinder()):(null)));
                        mRemote.transact(Stub.TRANSACTION_unregisterCallback, _data, _reply, 0);
                        _reply.readException();
                    }finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
            }
    
            static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
            static final int TRANSACTION_getData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
            static final int TRANSACTION_registerCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
            static final int TRANSACTION_unregisterCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
        }
    
        public int getPid() throws android.os.RemoteException;
    
        public int getData() throws android.os.RemoteException;
    
        public void registerCallback(com.ispring.aidldemo.IRemoteInterfaceCallback cb) throws android.os.RemoteException;
    
        public void unregisterCallback(com.ispring.aidldemo.IRemoteInterfaceCallback cb) throws android.os.RemoteException;
    }
    

    對於上面的程式碼,我們無需關注具體實現,我們只需要知道以下幾點即可:

    • IRemoteInterface.java中,定義了名為IRemoteInterface的interface,其對應著IRemoteInterface.aidl定義的介面,囊括了IRemoteInterface.aidl中定義的方法。IRemoteInterface繼承自android.os.IInterface,如下所示:

      public interface IRemoteInterface extends android.os.IInterface
    • IRemoteInterface介面中有一個靜態內部類StubStub是個抽象類,繼承自android.os.Binder,且實現了IRemoteInterface介面,如下所示:

      public static abstract class Stub extends android.os.Binder implements com.ispring.aidldemo.IRemoteInterface
    • Sub類中有一個asInterface方法,該方法非常重要,其接收一個android.os.IBinder型別的物件,返回IRemoteInterface型別,其方法簽名如下定義:

      public static com.ispring.aidldemo.IRemoteInterface asInterface(android.os.IBinder obj)
  2. 實現AIDL所定義的介面

    我們定義了一個RemoteService類,其繼承自Service,該類表示客戶端要呼叫的遠端服務,我們在AndroidManifest.xml檔案中對該Service進行如下配置:

    <service
            android:name=".RemoteService"
            android:enabled="true"
            android:process=":remote" />

    我們通過android:process=":remote",讓Service執行在一個新的程式中,而不是原有App的程式,其新程式的名字是remote,當然也可以是其他的名稱。remote前面的冒號表示該程式被當前的App獨享,是該App的私有程式;如果去掉remote前面的冒號,那麼則表示該程式是被多個App所共享的。

    RemoteService原始碼如下所示:

    package com.ispring.aidldemo;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.*;
    import android.util.Log;
    
    import java.util.Random;
    
    public class RemoteService extends Service {
    
        private Random random = new Random();
    
        //callbacks儲存了所有註冊過的客戶端回撥
        private final RemoteCallbackList<IRemoteInterfaceCallback> callbacks = new RemoteCallbackList<IRemoteInterfaceCallback>();
    
        private static final int MSG_REPORT_DATA = 0;
    
        //該handler用來每隔一秒主動向所有註冊過回撥的客戶端傳送資訊
        private Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case MSG_REPORT_DATA:
                        //開始廣播,獲取客戶端的數量
                        final int n = callbacks.beginBroadcast();
                        int data = random.nextInt(100);
                        for(int i = 0; i < n; i++){
                            try{
                                //遍歷客戶單回撥
                                IRemoteInterfaceCallback callback = callbacks.getBroadcastItem(i);
                                //執行我們自定義的dataChanged方法,客戶端會受到資訊
                                Log.i("DemoLog", "RemoteService: handleMessage -> callback.dataChanged(data), PID=" + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
                                callback.dataChanged(data);
                            }catch (RemoteException e){
                                e.printStackTrace();
                            }
                        }
                        //結束廣播
                        callbacks.finishBroadcast();
                        //構建新的Message,延遲1秒傳送,這樣handler每隔一秒都會受到Message
                        Message pendingMsg = obtainMessage(MSG_REPORT_DATA);
                        sendMessageDelayed(pendingMsg, 1000);
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
    
    
        //我們要實現
        IRemoteInterface.Stub binder = new IRemoteInterface.Stub(){
            @Override
            public int getPid() throws RemoteException {
                return android.os.Process.myPid();
            }
    
            @Override
            public int getData() throws RemoteException {
                Log.i("DemoLog", "RemoteService: binder -> getData, PID=" + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
                return random.nextInt(100);
            }
    
            @Override
            public void registerCallback(IRemoteInterfaceCallback cb) throws RemoteException {
                Log.i("DemoLog", "RemoteService: binder -> registerCallback, PID=" + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
                if(cb != null){
                    //註冊客戶端回撥
                    callbacks.register(cb);
                }
            }
    
            @Override
            public void unregisterCallback(IRemoteInterfaceCallback cb) throws RemoteException {
                Log.i("DemoLog", "RemoteService: binder -> unregisterCallback, PID=" + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
                if(cb != null){
                    //反註冊客戶端回撥
                    callbacks.unregister(cb);
                }
            }
        };
    
        public RemoteService() {
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            handler.sendEmptyMessage(MSG_REPORT_DATA);
            Log.i("DemoLog", "RemoteService -> onCreate, PID=" + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return binder;
        }
    
        @Override
        public void onDestroy() {
            Log.i("DemoLog", "RemoteService -> onDestroy, PID=" + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
    
            //反註冊所有的客戶端回撥,並且不再接收新的客戶端回撥
            callbacks.kill();
    
            //移除pedding message,停止message迴圈,防止記憶體洩露
            handler.removeMessages(MSG_REPORT_DATA);
    
            super.onDestroy();
        }
    }

    我們對上述部分程式碼進行說明:

    • 首先我們通過IRemoteInterface.Stub binder = new IRemoteInterface.Stub()例項化了一個IRemoteInterface.Stub物件。之前我們提到IRemoteInterface.Stub繼承自android.os.Binder,所以該例項也就是一個Binder,並且其實現了介面IRemoteInterface定義的抽象方法。

    • 然後在Service的onBind介面中將上述binder物件返回,這樣才能讓客戶端通過bindService方法呼叫Service。

    我們在後面會解釋handler、客戶端回撥相關的程式碼片段。


AIDL之客戶端實現

通常呼叫Service的客戶端是Activity,我在MainActivity中呼叫RemoteService,其介面如下所示:
這裡寫圖片描述

客戶端MainActivity原始碼如下所示:

package com.ispring.aidldemo;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.*;
import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements Button.OnClickListener {

    private Button btnBindService;
    private Button btnGetData;
    private Button btnRegister;
    private Button btnUnregister;
    private Button btnUnbindService;
    private Button btnKillProcess;
    private TextView textView;
    private boolean isRegistered = false;
    private IRemoteInterface remoteInterface;
    private static final int MSG_GET_DATA = 0;
    private static final int MSG_DATA_CHANGED = 1;

    //handler用於在主執行緒中更新UI
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MSG_GET_DATA:
                    //通過遠端服務的getData方法獲取資料
                    Toast.makeText(MainActivity.this, "Data: " + msg.arg1, Toast.LENGTH_LONG).show();
                    break;
                case MSG_DATA_CHANGED:
                    //遠端服務通過客戶端回撥向客戶端推送資料
                    textView.setText("Receive data from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };


    private ServiceConnection sc = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Log.i("DemoLog", "MainActivity: ServiceConnection -> onServiceConnected");
            remoteInterface = IRemoteInterface.Stub.asInterface(binder);

            //更新UI狀態
            btnBindService.setEnabled(false);
            btnGetData.setEnabled(true);
            btnRegister.setEnabled(true);
            btnUnregister.setEnabled(false);
            btnUnbindService.setEnabled(true);
            btnKillProcess.setEnabled(true);
            textView.setText("已連線到RemoteService");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i("DemoLog", "MainActivity: ServiceConnection -> onServiceDisconnected");
            remoteInterface = null;

            //更新UI狀態
            btnBindService.setEnabled(true);
            btnGetData.setEnabled(false);
            btnRegister.setEnabled(false);
            btnUnregister.setEnabled(false);
            btnUnbindService.setEnabled(false);
            btnKillProcess.setEnabled(false);
            textView.setText("與RemoteService斷開連線");
        }
    };

    //callback為客戶端向RemoteService註冊的回撥介面
    private IRemoteInterfaceCallback callback = new IRemoteInterfaceCallback.Stub(){
        @Override
        public void dataChanged(int data) throws RemoteException {
            Log.i("DemoLog", "MainActivity: callback -> dataChanged, data: " + data + ", PID=" +  + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
            Message msg = Message.obtain();
            msg.what = MSG_DATA_CHANGED;
            msg.arg1 = data;
            handler.sendMessage(msg);
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnBindService = (Button)findViewById(R.id.btnBindService);
        btnGetData = (Button)findViewById(R.id.btnGetData);
        btnRegister = (Button)findViewById(R.id.btnRegister);
        btnUnregister = (Button)findViewById(R.id.btnUnregister);
        btnUnbindService = (Button)findViewById(R.id.btnUnbindService);
        btnKillProcess = (Button)findViewById(R.id.btnKillProcess);
        textView = (TextView)findViewById(R.id.textView);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btnBindService:
                bindService();
                break;
            case R.id.btnGetData:
                getData();
                break;
            case R.id.btnRegister:
                registerCallback();
                break;
            case R.id.btnUnregister:
                unregisterCallback();
                break;
            case R.id.btnUnbindService:
                unbindService();
                break;
            case R.id.btnKillProcess:
                killServiceProcess();
                break;
        }
    }

    private void bindService(){
        if(remoteInterface != null){
            return;
        }
        Intent intent = new Intent(MainActivity.this, RemoteService.class);
        bindService(intent, sc, BIND_AUTO_CREATE);
    }

    private void getData(){
        if(remoteInterface == null){
            return;
        }

        try{
            Log.i("DemoLog", "MainActivity -> getData");
            int data = remoteInterface.getData();
            Message msg = Message.obtain();
            msg.what = MainActivity.MSG_GET_DATA;
            msg.arg1 = data;
            handler.sendMessage(msg);
        }catch (RemoteException e){
            e.printStackTrace();
        }
    }

    private void registerCallback(){
        if(remoteInterface == null || isRegistered){
            return;
        }
        try{
            Log.i("DemoLog", "MainActivity -> registerCallback");
            //客戶端向遠端服務註冊客戶端回撥
            remoteInterface.registerCallback(callback);
            isRegistered = true;

            //更新UI
            btnRegister.setEnabled(false);
            btnUnregister.setEnabled(true);
            Toast.makeText(this, "已向Service註冊Callback", Toast.LENGTH_LONG).show();
        }catch (RemoteException e){
            e.printStackTrace();
        }
    }

    private void unregisterCallback(){
        if(remoteInterface == null || !isRegistered){
            return;
        }
        try{
            Log.i("DemoLog", "MainActivity -> unregisterCallback");
            //遠端服務反註冊客戶端回撥
            remoteInterface.unregisterCallback(callback);
            isRegistered = false;

            //更新UI
            btnRegister.setEnabled(true);
            btnUnregister.setEnabled(false);
        }catch (RemoteException e){
            e.printStackTrace();
        }
    }

    private void unbindService(){
        if(remoteInterface == null){
            return;
        }

        unregisterCallback();
        unbindService(sc);
        remoteInterface = null;

        //更新UI狀態
        btnBindService.setEnabled(true);
        btnGetData.setEnabled(false);
        btnRegister.setEnabled(false);
        btnUnregister.setEnabled(false);
        btnUnbindService.setEnabled(false);
        btnKillProcess.setEnabled(false);
        textView.setText("與RemoteService斷開連線");
    }

    private void killServiceProcess(){
        if(remoteInterface == null){
            return;
        }

        try{
            Log.i("DemoLog", "MainActivity -> killServiceProcess");
            //獲取遠端服務的程式ID,並殺死遠端服務
            int pid = remoteInterface.getPid();
            android.os.Process.killProcess(pid);
            remoteInterface = null;
            //Service程式被殺死後,會觸發onServiceDisconnected的執行
        }catch (RemoteException e){
            e.printStackTrace();
        }
    }
}
  • bindService

    與正常呼叫bindService一樣,首先我們例項化了一個ServiceConnection物件sc,然後將其傳遞給了bindService方法,即bindService(intent, sc, BIND_AUTO_CREATE)

    點選bindService按鈕後,輸出如下所示:
    這裡寫圖片描述

    通過上圖可以看到,我們的MainActivity是執行在程式com.ispring.aidldemo中,其程式ID為4986。在呼叫了bindService之後,RemoteService被例項化,RemoteService執行在名為com.ispring.aidldemo:remote的程式中,其程式ID是6106。

    RemoteService建立成功後,會通過其onBind方法返回IBinder物件,將其傳遞給MainActivity中定義的ServiceConnection物件的onServiceConnected方法中,該方法是在客戶端的主執行緒中執行的。在該方法中通過IRemoteInterface.StubasInterface(binder)方法將IBinder物件轉換成遠端介面IRemoteInterface.Stub,這樣我們就相當於得到了遠端介面的例項,並將其通過欄位remoteInterface進行儲存,可以通過該例項呼叫介面中定義的方法。該遠端介面相當於是遠端服務RemoteService的代言人,所以下面我們不區分遠端介面和遠端服務的概念,可以把二者看成一回事,這樣可以簡化描述。

  • getData
    點選getData按鈕,可以通過int data = remoteInterface.getData()執行遠端服務的getData方法,可以從遠端服務中獲取最新的資料。我們在完成單擊之後過一段時間再次單擊getData按鈕,這兩次單擊的輸出如下所示:
    這裡寫圖片描述

    我們可以看到,兩次單擊導致RemoteService中binder物件的getData方法被呼叫了兩次,且都是在RemoteService的程式(程式ID為6106)中執行的,不是在客戶端程式執行。不過第一次是在Binder_1執行緒中執行,第二次是在Binder_2執行緒中執行。

    當客戶端呼叫遠端服務AIDL的方法時,這些遠端服務的方法是在遠端服務的程式中執行的,但是在哪個執行緒中是不確定的。遠端服務內部會維護一個執行緒池,從執行緒池中隨機取出一個執行緒執行客戶端呼叫的方法。

    當遠端服務的getData方法執行完畢後,客戶端會得到返回的結果,然後客戶端可以根據該值做相應處理,在本例中,我們將得到的data通過Message的形式傳送到handler,然後在該handler中更新UI。

  • registerCallback
    通過上面的getData方法可以讓客戶端從遠端服務獲取最新的資料,但是很多情況下我們希望遠端服務的最新資料發生變化後直接推送給我們客戶端,即客戶端和遠端服務通過觀察者模式進行資料更新的通訊,此時我們就需要客戶端回撥。

    我們在IRemoteInterface中定義了與回撥相關的兩個方法,registerCallback和unregisterCallback。registerCallback用於向遠端服務中註冊回撥,可以實現Service主動向客戶端推送資料。unregisterCallback用於刪除註冊的回撥。二者的方法簽名如下所示:

    //通過向Service中註冊回撥,可以實現Service主動向客戶端推送資料
    void registerCallback(IRemoteInterfaceCallback cb);
    
    //刪除註冊的回撥
    void unregisterCallback(IRemoteInterfaceCallback cb);

    這兩個方法都接收一個IRemoteInterfaceCallback引數,IRemoteInterfaceCallback就是一個回撥介面,該介面中定義的方法可以被遠端服務呼叫。IRemoteInterfaceCallback也是由AIDL定義的,IRemoteInterfaceCallback.aidl檔案如下所示:

    package com.ispring.aidldemo;
    
    //關鍵字oneway表示該介面下面的所有方法不會造成客戶端阻塞等待服務端方法執行完成
    oneway interface IRemoteInterfaceCallback {
        void dataChanged(int data);
    }

    IRemoteInterfaceCallback.aidl被編譯後,會產生IRemoteInterfaceCallback.java檔案,其定義了IRemoteInterfaceCallback介面和抽象類IRemoteInterfaceCallback.Stub,IRemoteInterfaceCallback.Stub繼承自IBinder,且實現了IRemoteInterfaceCallback介面。通過我們程式中的這兩個aidl檔案的編譯,我們就會發現每個aidl檔案編譯後,Android Studio都會預設幫我們新增一個其對應的Stub類,我們要和這個Stub類進行操作。

    我們在客戶單MainActivity通過如下程式碼實現了IRemoteInterfaceCallback.Stub的一個例項:

    //callback為客戶端向RemoteService註冊的回撥介面
    private IRemoteInterfaceCallback callback = new IRemoteInterfaceCallback.Stub(){
        @Override
        public void dataChanged(int data) throws RemoteException {
            Log.i("DemoLog", "MainActivity: callback -> dataChanged, data: " + data + ", PID=" +  + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
            Message msg = Message.obtain();
            msg.what = MSG_DATA_CHANGED;
            msg.arg1 = data;
            handler.sendMessage(msg);
        }
    };

    當我們點選registerCallback按鈕後,執行了程式碼remoteInterface.registerCallback(callback),輸出如下所示:
    這裡寫圖片描述

    我們對以上過程進行一下講解:

    1. 我們將callback作為引數傳遞給遠端服務的registerCallback方法。

    2. RemoteService會執行binder的registerCallback方法,程式碼如下所示:

      @Override
              public void registerCallback(IRemoteInterfaceCallback cb) throws RemoteException {
                  Log.i("DemoLog", "RemoteService: binder -> registerCallback, PID=" + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
                  if(cb != null){
                      //註冊客戶端回撥
                      callbacks.register(cb);
                  }
              }

      在RemoteService中,我們定義了一個RemoteCallbackList<IRemoteInterfaceCallback>型別的callbacks引數,用它儲存所有註冊過的客戶端回撥。通過callbacks.register(cb),將客戶端傳遞過來的回撥引數註冊到callbacks中。

    3. 我們在RemoteService中定義了一個handler,每隔一秒傳送一個Message,模擬資料發生了變化,然後向所有的客戶端主動傳送更新後的資料。handler中主要的相關程式碼如下所示:

      //開始廣播,獲取客戶端的數量
      final int n = callbacks.beginBroadcast();
      int data = random.nextInt(100);
      for(int i = 0; i < n; i++){
          try{
              //遍歷客戶單回撥
              IRemoteInterfaceCallback callback = callbacks.getBroadcastItem(i);
              //執行我們自定義的dataChanged方法,客戶端會受到資訊
              Log.i("DemoLog", "RemoteService: handleMessage -> callback.dataChanged(data), PID=" + android.os.Process.myPid() + ", Thread=" + Thread.currentThread().getName());
              callback.dataChanged(data);
          }catch (RemoteException e){
              e.printStackTrace();
          }
      }
      //結束廣播
      callbacks.finishBroadcast();
      //構建新的Message,延遲1秒傳送,這樣handler每隔一秒都會受到Message
      Message pendingMsg = obtainMessage(MSG_REPORT_DATA);
      sendMessageDelayed(pendingMsg, 1000);

      通過callbacks的beginBroadcast()方法獲取註冊的客戶端的數量,然後通過callbacks的getBroadcastItem()方法遍歷所有的客戶端回撥,通過呼叫客戶端回撥dataChanged方法,向客戶端主動傳送資料,客戶端MainActivity中的callback的dataChanged會回撥被執行,且接收到遠端服務傳入的資料。向所有客戶端推送資料完成後,呼叫callbacks的finishBroadcast()方法。

    4. 由於我們每隔一秒就傳送一條Message資料,所以每隔一秒RemoteService就會向所有的客戶端主動傳送一遍資料,所以每隔一秒MainActivity中callback的dataChanged方法就被執行一次。我們通過上面的控制檯輸出可以發現,MainActivity中callback的dataChanged方法內部的程式碼是在MainActivity所在的程式(程式ID為4986)中執行的,但是在哪個執行緒中執行是不確定的,有時候是執行緒Binder_3,有時候是執行緒Binder_2,有時候是執行緒Binder_1。

    5. 客戶端的AIDL回撥方法內的程式碼是在客戶端的程式中執行的,客戶端中也維護了一個執行緒池,從該執行緒池中隨機取出一個執行緒執行客戶端回撥方法,所以不要在AIDL的客戶端回撥方法中更新UI,需要通過hanlder更新UI。

  • unregisterCallback
    通過單擊unregisterCallback按鈕,可以反註冊客戶端回撥,這樣客戶端就不會再收到遠端服務傳送的資料,單擊unregisterCallback按鈕後控制檯輸出如下:

    這裡寫圖片描述

    在遠端服務的程式中執行binder的unregisterCallback方法,通過callbacks的unregister()方法將傳入的客戶端回撥從callbacks中刪除。

  • unbindService
    在客戶端與遠端服務處於繫結的情況下,點選unbindService按鈕之後,遠端服務執行onDestroy方法,遠端服務銷燬,控制檯輸出如下所示:

    com.ispring.aidldemo:remote I/DemoLog: RemoteService -> onDestroy, PID=6106, Thread=main

    需要注意的是,單純呼叫unbindService方法沒有觸發ServiceConnection的onServiceDisconnected()方法的執行。

  • Kill Service Process
    在客戶端與遠端服務處於繫結的情況下,點選Kill Service Process按鈕,可以殺死遠端服務的程式,控制檯輸出如下所示:

    這裡寫圖片描述

    首先通過我們在IRemoteService.aidl中定義的getPid()方法獲取遠端服務的程式ID,然後通過android.os.Process.killProcess(pid)殺死遠端服務程式。需要注意的是,android.os.Process.killProcess()方法並不是可以殺死任意的程式,你只能殺死你自己App的程式以及在你的App中所建立的新的程式(比如此例中的遠端服務的程式就是在App自身的程式中建立的)。


oneway關鍵字

當客戶端呼叫AIDL中的方法時,預設情況下客戶端會阻塞式地等待遠端服務執行完畢,然後客戶端才能繼續執行程式碼。比如在IRemoteService.aidl中定義的getData方法,如果該方法在遠端服務中執行了較長時間才返回了資料,那麼客戶端也要等待該時間。很多時候,AIDL中的方法不需要返回具體的資料,這種情況下為了避免客戶端一直等待遠端方法執行完成,我們就可以將aidl介面宣告為oneway,宣告為oneway的AIDL介面中的所有方法在呼叫時都不會阻塞,具體來說,呼叫了遠端方法後,不用等著遠端方法執行完畢,會立即返回繼續執行後面的程式碼,所以正因為此特性,oneway介面下面的方法都必須是返回void型別,不能返回其他型別的資料。大部分情況下,我們一般將客戶端的回撥介面AIDL定義為oneway的,這樣遠端服務呼叫回撥介面中的方法時不會阻塞遠端服務後面程式碼的執行。


總結

  1. 如果你想實現跨程式通訊,但是並不關注多執行緒併發處理,那麼你只需要使用Messenger即可,無需編寫AIDL程式碼,只有當你想讓Service併發處理客戶端的請求的時候,才需要編寫AIDL程式碼。

  2. 當客戶端呼叫遠端服務AIDL的方法時,這些遠端服務的方法是在遠端服務的程式中執行的,但是在哪個執行緒中是不確定的。遠端服務內部會維護一個執行緒池,從執行緒池中隨機取出一個執行緒執行客戶端呼叫的方法。

  3. 通過定義客戶端回撥AIDL介面,可以在客戶端和遠端服務之間建立觀察者模式,讓遠端服務主動向客戶端傳送資料。並且應該將客戶端AIDL介面宣告為oneway ,這樣不會阻塞遠端服務程式碼的執行。

  4. 客戶端的AIDL回撥方法內的程式碼是在客戶端的程式中執行的,客戶端中也維護了一個執行緒池,從該執行緒池中隨機取出一個執行緒執行客戶端回撥方法,所以不要在AIDL的客戶端回撥方法中更新UI,需要通過hanlder更新UI。

本文中的示例程式碼可以點此下載。

希望本文對大家理解和使用AIDL有所幫助!

參考:
Google官方的《Android Interface Definition Language (AIDL)》
Google在GitHub上的ApiDemo

相關閱讀:
我的Android博文整理彙總
Android中通過Messenger與Service實現程式間雙向通訊

相關文章