Android 程式間通訊

吳小龍同學發表於2018-02-23

要講 Android 程式通訊的話,就不得不先講講 Service. Service 是 Android 的四大元件之一,它主要的作用是後臺執行操作,Activity 屬於帶有 UI 介面跟使用者進行互動,而 Service 則沒有 UI 介面,所有的操作都是基於後臺執行完成。並且 Service 跟 Activity 一樣也是可以由其它的應用程式呼叫啟動的,而且就算使用者切換了應用程式,Service 依舊保持執行。一個元件如果與 Service 進行了繫結( bind ), 就可以跟 Service 進行資料的互動,並且也可以跟不同的程式之間進行互動 (IPC)。通常會使用到 Service 的情況有進行網路請求,音樂的操控,檔案的 I/O 操作等。

Service

宣告

在 Manifest 裡宣告 Service, 類似於 Activity,所有的 Service 都要在 Manifest 裡面進行宣告,如下:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>複製程式碼

檢視 service 標籤 的官方文件來獲取更多資訊

啟動

Service 通常是通過以下兩種方式進行啟動

  • startService
  • bindService

Start Service

當元件(例如 activity)通過呼叫 startService() 來啟動 Service 的時候。一旦啟動後,Service 就會獨立的在後臺執行,即使呼叫的元件已經銷燬了,Service 還是可以繼續在後臺執行。一般情況下,只需要進行一次單獨的操作,不需要將操作後的結果返回給呼叫者的時候,會使用該方式啟動 Service。例如,進行上傳或者下載操作的時候,當操作完成後,Service 應該自行呼叫 stopService() 或 stopSelf() 來結束執行。

Bind Service

當元件(例如 activity)通過呼叫 bindService() 來啟動 Service 的時候。這種方式提供了 client - service 的介面,可以讓呼叫元件跟 Service 進行傳送請求及返回結果的操作,設定可以進行程式間的通訊 (IPC)。只要有一個元件對該 Service 進行了繫結,那該 Service 就不會銷燬。並且多個元件可以同時對一個 Service 進行繫結,只有在所有進行了繫結的元件都解綁的時候,Service 才會銷燬

儘管兩種方式是分開討論的,但是並不是互斥的關係,使用 startService 啟動了 Service 後,也是可以通過 bindService繫結的。

注意: 雖然 Service 是在後臺執行,但是其實還是在主執行緒裡進行所有的操作的。Service 在啟動時除非單獨進行了定義否則並沒有在單獨的執行緒或者程式了而都是在主執行緒裡。所以這表示任何能堵塞主執行緒的操作(例如音樂的播放或者網路請求)都應該單獨開闢新的執行緒來進行操作,否則很容易出現 ANR 。

如果某個元件是通過呼叫 startService() 的方式來啟動了 Service,那這個 Service 就會一直在後臺執行直到 Service 內部呼叫 stopSelf() 或某個元件呼叫 stopService() 來結束該 Service

如果某個元件是通過呼叫 bindService() 的方式來啟動了 Service,那這個 Service 就會一直在後臺執行直到該元件與其解綁。Service 在沒有任何元件繫結的時候,系統會將其銷燬

關於 Service 更多詳細的介紹可以檢視這裡

Service 生命週期

Android 程式間通訊
service 生命週期圖

AIDL (Android Interface Definition Language )

Android IPC 是通過 Binder 實現的,但是 Binder 相關的概念非常複雜,為了方便開發者 Google 就推出了 AIDL (安卓介面定義語言)。通過編寫 AIDL 檔案,Android Studio 就可以幫我們生成 Binder 通訊的相關程式碼。開發者即使不瞭解 Binder 機制也可以實現 IPC 了。

關鍵字

oneway

正常情況下 Client 呼叫 AIDL 介面方法時會阻塞,直到 Server 程式中該方法被執行完。oneway 可以修飾 AIDL 檔案裡的方法,oneway 修飾的方法在使用者請求相應功能時不需要等待響應可直接呼叫返回,非阻塞效果,該關鍵字可以用來宣告介面或者宣告方法,如果介面宣告中用到了 oneway 關鍵字,則該介面宣告的所有方法都採用 oneway 方式。(注意,如果 Client 和 Server 在同一程式中, oneway 修飾的方法還是會阻塞)

in

非基本資料型別和 String 的引數型別必須加引數修飾符, in 的意思是隻輸入,既最終 Server 端執行完後不會影響到引數物件

out

與 in 相反, out 修飾的引數只能由 Server 寫入並傳遞到 Client,而 Client 傳入的值並不會傳遞到 Server

inout

被 inout 修飾的引數,既可以從 Client 傳遞到 Server,也可以 Server 傳遞到 Client

AIDL 自動生成檔案講解

Talk is cheap, show you the code.

interface ISocketService {

    int getState();

    oneway void registerCallback(in ISocketServiceCallback callback);
    oneway void unregisterCallback(in ISocketServiceCallback callback);

    oneway void runShadowSocks(in Config config);
}複製程式碼

IDE 自動生成的程式碼如下

public interface ISocketService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.shadark.app.aidl.ISocketService {
        private static final java.lang.String DESCRIPTOR = "com.shadark.app.aidl.ISocketService";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.shadark.app.aidl.ISocketService interface,
         * generating a proxy if needed.
         */
        public static com.shadark.app.aidl.ISocketService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.shadark.app.aidl.ISocketService))) {
                return ((com.shadark.app.aidl.ISocketService) iin);
            }
            return new com.shadark.app.aidl.ISocketService.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_getState: {
                    data.enforceInterface(DESCRIPTOR);
                    int _result = this.getState();
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_registerCallback: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shadark.app.aidl.ISocketServiceCallback _arg0;
                    _arg0 = com.shadark.app.aidl.ISocketServiceCallback.Stub.asInterface(data.readStrongBinder());
                    this.registerCallback(_arg0);
                    return true;
                }
                case TRANSACTION_unregisterCallback: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shadark.app.aidl.ISocketServiceCallback _arg0;
                    _arg0 = com.shadark.app.aidl.ISocketServiceCallback.Stub.asInterface(data.readStrongBinder());
                    this.unregisterCallback(_arg0);
                    return true;
                }
                case TRANSACTION_runShadowSocks: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shadark.app.aidl.Config _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.shadark.app.aidl.Config.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.runShadowSocks(_arg0);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.shadark.app.aidl.ISocketService {
            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 getState() 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_getState, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void registerCallback(com.shadark.app.aidl.ISocketServiceCallback callback) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_registerCallback, _data, null, android.os.IBinder.FLAG_ONEWAY);
                } finally {
                    _data.recycle();
                }
            }

            @Override
            public void unregisterCallback(com.shadark.app.aidl.ISocketServiceCallback callback) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_unregisterCallback, _data, null, android.os.IBinder.FLAG_ONEWAY);
                } finally {
                    _data.recycle();
                }
            }

            @Override
            public void runShadowSocks(com.shadark.app.aidl.Config config) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((config != null)) {
                        _data.writeInt(1);
                        config.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_runShadowSocks, _data, null, android.os.IBinder.FLAG_ONEWAY);
                } finally {
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getState = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_registerCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_unregisterCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
        static final int TRANSACTION_runShadowSocks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
    }

    public int getState() throws android.os.RemoteException;

    public void registerCallback(com.shadark.app.aidl.ISocketServiceCallback callback) throws android.os.RemoteException;

    public void unregisterCallback(com.shadark.app.aidl.ISocketServiceCallback callback) throws android.os.RemoteException;

    public void runShadowSocks(com.shadark.app.aidl.Config config) throws android.os.RemoteException;
}複製程式碼

IDE 用我們編寫的 AIDL 檔案,幫我們做了如下這些事情:
1.建立了 ISocketService 的實現類 Stub 和 Stub 的子類 Proxy
2.Stub 類中實現了有 IBinder 物件轉換為 ISocketService 型別的 asInterface, asInterface 中通過 queryLocalInterface(DESCRIPTOR) 方法檢視本程式是否有 ISocketService 在 Server 端的實現類(既判斷 Server 與 Client 是否在同一程式),如果是同一程式就直接返回 Server 端的 ISocketService 實現者,如果不在同一程式就返回代理物件
3.Proxy 類中實現了 AIDL 中定義的方法,根據 oneway、in、out、inout 修飾符來生成不同的程式碼,決定是否向 binder 驅動寫入資料或者執行完後向方法引數回寫資料。注意:oneway 修飾一個方法後,該方法不阻塞 client 呼叫執行緒,但是方法沒有返回值,方法引數在執行方法執行完後也不會回寫。
4.Proxy 類中實現的方法最終通過 transact() 方法向 Binder 驅動寫入資料(執行在 Client 程式),最終 Stub 類中的 onTransact() 方法會被呼叫到(執行在 Server 程式),就這樣完成一次跨程式方法呼叫。

Binder 死亡處理

在程式間通訊過程中,很可能出現一個程式死亡的情況。如果這時活著的一方不知道另一方已經死了就會出現問題。那我們如何在 A 程式中獲取 B 程式的存活狀態呢?
Android 肯定給我們提供瞭解決方式,那就是 BinderlinkToDeathunlinkToDeath 方法, linkToDeath 方法需要傳入一個 DeathRecipient 物件, DeathRecipient 類裡面有個 binderDied 方法,當 binder 物件的所在程式死亡, binderDied 方法就會被執行,我們就可以在 binderDied 方法裡面做一些異常處理,釋放資源等操作了。

示例如下:

...

    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {

        try {
            mBinder = binder;
            binder.linkToDeath(SocketServiceManager.this, 0);
            mSocketService = ISocketService.Stub.asInterface(binder);
            registerCallback();
            mCallback.onServiceConnected();
        } catch (RemoteException e) {
            LogUtils.e(TAG, "onServiceConnected: " + e.getMessage());
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        unregisterCallback();
        mCallback.onServiceDisconnected();

        if (null != mBinder) {
            mBinder.unlinkToDeath(this, 0);
            mBinder = null;
        }
    }
...複製程式碼
private class SocketServiceManager implements IBinder.DeathRecipient {

    ...

        @Override
        public void binderDied() {
            mCallbackList.unregister(mClientCallBack);
            mClientCallBack = null;
            Logger.d(TAG,"client  is died");
        }

    ...
}複製程式碼

上面是在 Server 端對 Client 的回撥介面的 Binder 物件設定的 DeathRecipient。在 Client 死亡時,解註冊 Client 的回撥,並且置空。

Client 註冊回撥介面

之前一直說的都是 Client 向 Server 的通訊,那如果 Server 要呼叫 Client 呢?
一個比較容易想到的辦法就是通過 AIDL 在 Server 端設定一個 Client 的回撥。這樣的話就相當於 Client 端是 Server 端的 Server 了。
有註冊回撥就肯定有解註冊,但是 Client 端與 Server 不在一個程式,Server 是無法得知 Client 解註冊時傳入的回撥介面是哪一個( Client 呼叫解註冊時,是通過 Binder 傳輸到 Server 端,所以解註冊時的回撥介面是新建立的,而不是註冊時的回撥介面)。為了解決這個問題,Android 提供了 RemoteCallbackList 這個類來專門管理 remote 回撥的註冊與解註冊。

AIDL 類

interface ITaskCallback {   
    void actionPerformed(int actionId);  
}複製程式碼
interface ITaskBinder {   
    boolean isTaskRunning();   
    void stopRunningTask();   
    void registerCallback(ITaskCallback cb);   
    void unregisterCallback(ITaskCallback cb);   
}複製程式碼

Service 類

public class MyService extends Service {   
    private static final String TAG = "aidltest";  
    final RemoteCallbackList <ITaskCallback>mCallbacks = new RemoteCallbackList <ITaskCallback>(); 
    ...

    @Override  
    public IBinder onBind(Intent t) {  
        printf("service on bind");  
        return mBinder;   
    }  

    @Override  
    public boolean onUnbind(Intent intent) {   
        printf("service on unbind");  
        return super.onUnbind(intent);   
    }  

    void callback(int val) {   
        final int N = mCallbacks.beginBroadcast();  
        for (int i=0; i<N; i++) {   
            try {  
                mCallbacks.getBroadcastItem(i).actionPerformed(val);   
            }  
            catch (RemoteException e) {   
                // The RemoteCallbackList will take care of removing   
                // the dead object for us.     
            }  
        }  
        mCallbacks.finishBroadcast();  
    }  

    private final ITaskBinder.Stub mBinder = new ITaskBinder.Stub() {  

        public void stopRunningTask() {  

        }  

        public boolean isTaskRunning() {   
            return false;   
        }   

        public void registerCallback(ITaskCallback cb) {   
            if (cb != null) {   
                mCallbacks.register(cb);  
            }  
        }  

        public void unregisterCallback(ITaskCallback cb) {  
            if(cb != null) {  
                mCallbacks.unregister(cb);  
            }  
        }  
    };   

}複製程式碼

Client 類

public class MyActivity extends Activity {   

    private static final String TAG = "aidltest";  
    private Button btnOk;   
    private Button btnCancel;  

...

    ITaskBinder mService;   

    private ServiceConnection mConnection = new ServiceConnection() {   

        public void onServiceConnected(ComponentName className, IBinder service) {  
            mService = ITaskBinder.Stub.asInterface(service);   
            try {   
                mService.registerCallback(mCallback);  
            } catch (RemoteException e) {  

            }  
        }  

        public void onServiceDisconnected(ComponentName className) {   
            mService = null;  
        }   
    };   

    private ITaskCallback mCallback = new ITaskCallback.Stub() {  

        public void actionPerformed(int id) {   
            printf("callback id=" + id);  
        }   
    };   

}複製程式碼

RemoteCallbackList 可以實現正常註冊於解註冊的原因在於註冊與解註冊時雖然對應的回撥介面不是同一個,但是其對應的 Binder 物件卻是同一個。

Messenger 通訊

以下概括了Messenger的使用方法:

  1. 服務實現一個 Handler ,用於客戶端每次呼叫時接收回撥
  2. Handler 用於建立一個 Messenger 物件(它是一個對 Handler 的引用)
  3. Messenger 物件建立一個 IBinder ,服務在 onBind() 中把它返回給客戶端
  4. 客戶端用 IBinderMessenger(引用服務的 Handler)例項化,客戶端用它向服務傳送訊息物件 Message
  5. 服務接收 Handler 中的每個訊息 Message ——確切的說,是在 handleMessage() 方法中接收

Service 類

public class MessengerService extends Service {

/**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }

    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_REGISTER_CLIENT:
                    mClients.add(msg.replyTo);
                    break;
                case MSG_UNREGISTER_CLIENT:
                    mClients.remove(msg.replyTo);
                    break;
                case MSG_SET_VALUE:
                    mValue = msg.arg1;
                    for (int i=mClients.size()-1; i>=0; i--) {
                        try {
                            mClients.get(i).send(Message.obtain(null,
                                    MSG_SET_VALUE, mValue, 0));
                        } catch (RemoteException e) {
                            // The client is dead.  Remove it from the list;
                            // we are going through the list from back to front
                            // so this is safe to do inside the loop.
                            mClients.remove(i);
                        }
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    ...
}複製程式碼

Client 類

public static class Binding extends Activity {

    /**
     * Messenger for communicating with service.
     */
    Messenger mService = null;

    /**
     * Handler of incoming messages from service.
     */
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MessengerService.MSG_SET_VALUE:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = new Messenger(service);
            mCallbackText.setText("Attached.");
            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                Message msg = Message.obtain(null,
                        MessengerService.MSG_REGISTER_CLIENT);
                msg.replyTo = mMessenger;
                mService.send(msg);

                // Give it some value as an example.
                msg = Message.obtain(null,
                        MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
                mService.send(msg);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mCallbackText.setText("Disconnected.");
            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    ...
}複製程式碼

MessengerService.java(服務)和 MessengerServiceActivities.java(客戶端)例程中,可以看到如何關於 Messenger 的實用例子。

Messenger 和 AIDL 的異同

其實 Messenger 的底層也是用 AIDL 實現的,但用起來還是有些不同的,這裡總結了幾點區別:

  1. Messenger 本質也是 AIDL,只是進行了封裝,開發的時候不用再寫 .aidl 檔案

    結合自身的使用,因為不用去寫 .aidl 檔案,相比起來,Messenger 使用起來十分簡單。但前面也說了,Messenger 本質上也是 AIDL,故在底層程式間通訊這一塊,兩者的效率應該是一樣的。

  2. 在 Service 端,Messenger 處理 Client 端的請求是單執行緒的,而 AIDL 是多執行緒的

    使用 AIDL 的時候,service 端每收到一個 client 端的請求時,就在 Binder 執行緒池中取一個執行緒去執行相應的操作。而 Messenger ,service 收到的請求是放在 HandlerMessageQueue 裡面,Handler 大家都用過,它需要繫結一個 Thread,然後不斷 poll message 執行相關操作,這個過程是同步執行的。

  3. Client 的方法,使用 AIDL 獲取返回值是同步的,而 Messenger 是非同步的

    Messenger 只提供了一個方法進行程式間通訊,就是 send(Message msg) 方法,傳送的是一個 Message,沒有返回值,要拿到返回值,需要把 Client 的 Messenger 作為 msg.replyTo 引數傳遞過去,Service 端處理完之後,在呼叫客戶端的 Messenger 的 send(Message msg) 方法把返回值傳遞迴 Client,這個過程是非同步的,而 AIDL 你可以自己指定方法,指定返回值,它獲取返回值是同步的(如果沒有用 oneway 修飾方法的話)。

P.S. 該文章還配有對應的 PPT, 請點選這裡

相關文章