進程通信之Messenger&AIDL使用詳解

1. 前言

提到的進程間通信(IPC:Inter-Process Communication),在Android系統中,一個進程是不能直接訪問另一個進程的內存的,需要提供一些機制在不同的進程之間進行通信,Android官方推出了AIDL(Android Interface Definition Language),它是基於Binder機制的,至於官方為什麼要採用Binder,查看為什麼Android要採用Binder作為IPC機制,分析很全面。

上篇Android之Service總結 提到組件在與Service通信方法有三種。

  • 實現IBinder
  • Messenger
  • AIDL

後面兩種可以跨進程通信,是基於Binder機制的通信方式。

2. 使用時機

在確定使用什麼機制之前,首先了解應用場景。Android系統中,如果組件與服務通信是在同一進程,就使用第一種方式;如果是跨進程通信,使用第二種和第三種,兩者不同在於,Messenger不能處理多線程併發請求。

3. AIDL使用

AIDL是可以處理多線程訪問的請求的,所以實現AIDL首先要保證線程安全。

  • 創建.aidl文件,定義接口
  • 在代碼中實現接口,Android SDK會根據aidl文件,生成接口,接口內部有一個名為Stub內部抽象類,這個類用於繼承了Binder類並實現aidl文件中定義的接口,我們需要拓展Stub類並實現裡面的抽象方法
  • 複寫Service的onBind(),返回Stub類的實現

3.1 創建.aidl文件

在Android Studio中工程目錄下,反鍵new -> AIDL -> AIDL FIle,可以新建aidl文件,編譯器會自動在app(殼工程)/src/main/目錄下新建aidl文件,同時也會新建文件夾,默認以工程包名作為aidl文件所在的目錄。

目錄結構如下:


圖-1 aidl文件目錄結構

也可以手動創建,aidl接口定義的包名也可以和工程包名不同,aidl文件語法和Java語法相似,aidl定義的接口名必須和文件名一致,而且支持傳遞自定義的數據類型,需要實現parcelable接口。

IRemoteService.aidl

package com.demo.aidl;

import com.demo.aidl.ParcelableData;

interface IRemoteService { /** * 獲取當前進程的pid / int getPid(); /* * 獲取當前服務名稱 / String getServiceName(); /* * 處理客戶端傳過來的數據 */ void handleData(in ParcelableData data); }

ParcelableData.aidl

package com.demo.aidl;

/**

  • 聲明支持傳遞的類 */ parcelable ParcelableData;

IRemoteServiceCallBack.aidl

package com.demo.aidl;

oneway interface IRemoteServiceCallBack { void valueChanged(int value); }

aidl文件定義的接口支持的數據類型如下:

  • Java的八種基本數據類型(byte,int,short,long,float,double,char,boolean)
  • String
  • CharSequence
  • List,List中所有的元素必須是aidl文件支持的數據類型,例如,List< String >
  • Map,Map中所有的元素必須是aidl文件支持的數據類型,
  • 其他aidl定義的接口,要手動添加import
  • 其他aidl文件中申明的類,要手動添加import

aidl文件中定義的方法接受的參數,除了Java的基本數據類型和aidl定義的接口之外,其他參數都需要標記數據的走向,in/out/inout,基本數據類型和aidl定義的接口作為參數,默認是in。

  • in表示輸入參數,客戶端把參數傳遞給服務端使用。
  • out表示輸出參數,客戶端將參數傳遞給服務端填充,然後自己使用處理。
  • inout標書輸入輸出參數,傳送相應的值並接收返回。

關鍵字oneway表示用戶請求相應功能時不需要等待響應可直接調用返回,非阻塞效果,該關鍵字可以用來聲明接口或者聲明方法,如果接口聲明中用到了oneway關鍵字,則該接口聲明的所有方法都採用oneway方式

新建完畢aidl文件後,rebuild工程或者使用gradle assembleDebug(或gradle assembleRelease)指令編譯工程,生成具體的java代碼,在殼工程/build/generated/aidl/目錄下的debug或者release文件夾下,根據build的類型而定,如圖:


圖-2 adil生成代碼目錄圖

AIDL接口首次公佈後對其的任何修改都必須保持向後兼容性,避免中斷客戶端對服務的使用,因為需要將.aidl文件複製給其他應用,才能使其他應用能夠訪問服務,所以必須保持對原始接口的支持。

3.2 實現接口

Android SDK會根據.aidl文件生成同名.java文件,生成的接口中有一個Stub的抽象子類,這個類實現(implements)aidl定義的接口,同時繼承了Binder

具體代碼如下:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    @Override
    public int getPid() throws RemoteException {
        return Process.myPid();
    }
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function">String <span class="hljs-title">getServiceName</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
    <span class="hljs-keyword">return</span> RemoteService.<span class="hljs-keyword">this</span>.getClass().getSimpleName();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">handleData</span><span class="hljs-params">(ParcelableData data)</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
    Toast.makeText(RemoteService.<span class="hljs-keyword">this</span>, <span class="hljs-string">"num is "</span> + data.num, Toast.LENGTH_SHORT).show();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">registerCallback</span><span class="hljs-params">(IRemoteServiceCallBack cb)</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
    <span class="hljs-keyword">if</span>(cb != <span class="hljs-keyword">null</span>) {
        mCallBacks.register(cb);
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">unregisterCallback</span><span class="hljs-params">(IRemoteServiceCallBack cb)</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
    <span class="hljs-keyword">if</span>(cb != <span class="hljs-keyword">null</span>) {
        mCallBacks.unregister(cb);
    }
}

};

現在mBinder是Stub類的一個實例,同時也是一個Binder,用於服務定義的RPC服務,作為onBind()方法的返回對象實例。

實現AIDL接口注意事項:

  • 因為AIDL可以處理多線程併發,在實現過程中要保證線程安全
  • 默認情況下,RPC調用是同步的,但是服務端可能有耗時操作,客戶端最好不要在主線程調用服務
  • 在服務端人工拋出的任何異常不會返回給客戶端

3.3 向客戶端暴露接口

實現接口後,需要向客戶端將接口暴露出來,以便客戶端使用。將Stub的實例化對象作為Service中onBind()方法的返回對象。

public class RemoteService extends Service {
    /**
     * 回調容器
     */
    private final RemoteCallbackList<IRemoteServiceCallBack> mCallBacks = new RemoteCallbackList<>();
    /**
     * aidl接口具體實現
     */
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        @Override
        public int getPid() throws RemoteException {
            return Process.myPid();
        }
    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function">String <span class="hljs-title">getServiceName</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
        <span class="hljs-keyword">return</span> RemoteService.<span class="hljs-keyword">this</span>.getClass().getSimpleName();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">handleData</span><span class="hljs-params">(ParcelableData data)</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
        Toast.makeText(RemoteService.<span class="hljs-keyword">this</span>, <span class="hljs-string">"num is "</span> + data.num, Toast.LENGTH_SHORT).show();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">registerCallback</span><span class="hljs-params">(IRemoteServiceCallBack cb)</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
        <span class="hljs-keyword">if</span>(cb != <span class="hljs-keyword">null</span>) {
            mCallBacks.register(cb);
        }
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">unregisterCallback</span><span class="hljs-params">(IRemoteServiceCallBack cb)</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
        <span class="hljs-keyword">if</span>(cb != <span class="hljs-keyword">null</span>) {
            mCallBacks.unregister(cb);
        }
    }
};

<span class="hljs-meta">@Nullable</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function">IBinder <span class="hljs-title">onBind</span><span class="hljs-params">(Intent intent)</span> </span>{
    <span class="hljs-keyword">return</span> mBinder;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">onDestroy</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// 註銷所有回調</span>
    mCallBacks.kill();
}

}

3.4 客戶端調用

服務提供給第三方應用使用,其他應用就必須要有接口類,在客戶端創建相同的aidl文件(可以直接拷貝過去)。

核心連接遠端服務的代碼:

/**
 * 遠端服務
 */
private IRemoteService mService;

private ServiceConnection mConnection = new ServiceConnection() { /** * 連接服務器成功回調 * * @param className * @param service */ public void onServiceConnected(ComponentName className, IBinder service) { mService = IRemoteService.Stub.asInterface(service); }

<span class="hljs-comment">/**
 * 服務器因為一場情況斷開連接時候回調
 * 
 * @param className
 */</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onServiceDisconnected</span>(<span class="hljs-params">ComponentName className</span>) </span>{
    mService = <span class="hljs-literal">null</span>;
}

};

/**

  • 綁定服務 */ private void doBindService() { isBound = true; Intent intent = new Intent(BindRemoteServiceActivity.this, RemoteService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); }

/**

  • 解除綁定 */ private void doUnbindService() { if(isBound && mService != null) { isBound = false; try { mService.unregisterCallback(mCallback); } catch (RemoteException e) { e.printStackTrace(); }

     unbindService(mConnection);
    

    } }

/**

  • 向服務端發送信息 */ private void doSendMsg() { if(!isBound || mService == null) { return; } ParcelableData data = new ParcelableData(1); try { mService.handleData(data); } catch (RemoteException e) { e.printStackTrace(); } }

/**

  • 調用服務端方法獲取信息 */ private void doGetServiceInfo() { if(!isBound || mService == null) { return; } try { String info = mService.getServiceName();

     mInfoTv.setText(<span class="hljs-string">"Service info :"</span> + info);
    

    } catch (RemoteException e) { e.printStackTrace(); } }

詳情代碼貼上來比較長,貼上工程地址,點我呀!!!

4. Messenger的使用

Messenger的使用相對於AIDL方便好多,因為Messenger是Android系統中自帶的類,服務端和客戶端都不用創建AIDL文件。

Messenger會持有一個Handler,這個Handler用於處理接受到的信息,在服務端和乘客通過Messenger實現雙方通信。

4.1 服務端

代碼實例:

public class MessengerService extends Service {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> MSG_REGISTER_CLIENT = <span class="hljs-number">0X001</span>;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> MSG_UNREGISTER_CLIENT = <span class="hljs-number">0X010</span>;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> MSG_HANDLE = <span class="hljs-number">0X100</span>;

<span class="hljs-keyword">private</span> ArrayList&lt;Messenger&gt; mClients = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();

<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Messenger mMessenger = <span class="hljs-keyword">new</span> Messenger(<span class="hljs-keyword">new</span> IncomingHandler());

<span class="hljs-meta">@Nullable</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function">IBinder <span class="hljs-title">onBind</span><span class="hljs-params">(Intent intent)</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">return</span> mMessenger.<span class="hljs-title">getBinder</span><span class="hljs-params">()</span></span>;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">onDestroy</span><span class="hljs-params">()</span> </span>{
    Toast.makeText(<span class="hljs-keyword">this</span>, <span class="hljs-string">"Remote Service Destroy"</span>, Toast.LENGTH_SHORT).show();
}

<span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IncomingHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Handler</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span><span class="hljs-params">(Message msg)</span> </span>{
        <span class="hljs-keyword">switch</span> (msg.what) {
            <span class="hljs-keyword">case</span> MSG_REGISTER_CLIENT:
                mClients.add(msg.replyTo);
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">case</span> MSG_UNREGISTER_CLIENT:
                mClients.remove(msg.replyTo);
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">case</span> MSG_HANDLE:
                <span class="hljs-keyword">for</span> (Messenger mClient : mClients) {
                    <span class="hljs-keyword">try</span> {
                        mClient.send(Message.obtain(<span class="hljs-keyword">null</span>, MSG_HANDLE, msg.arg1, <span class="hljs-number">0</span>));
                    } <span class="hljs-keyword">catch</span> (RemoteException e) {
                        e.printStackTrace();
                        mClients.remove(mClient);
                    }
                }
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">default</span>:
                <span class="hljs-keyword">super</span>.handleMessage(msg);
        }
    }
};

}

4.2 客戶端

核心代碼:

/**
 * 關聯遠端服務的messenger
 */
private Messenger mServiceWrapper;
/**
 * 用於處理服務端發送的信息
 */
final Messenger mMessenger = new Messenger(new IncomingHandler());

private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mServiceWrapper = new Messenger(service);

    mInfoTv.setText(<span class="hljs-string">"Connected Service"</span>);


    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// 添加監聽註冊</span>
        Message msg = Message.obtain(<span class="hljs-literal">null</span>, MessengerService.MSG_REGISTER_CLIENT);
        msg.replyTo = mMessenger;
        mServiceWrapper.send(msg);
    } <span class="hljs-keyword">catch</span> (RemoteException e) {
        e.printStackTrace();
    }
}

@<span class="hljs-function">Override
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onServiceDisconnected</span>(<span class="hljs-params">ComponentName name</span>) </span>{
    mServiceWrapper = <span class="hljs-literal">null</span>;
    mInfoTv.setText(<span class="hljs-string">"Disconnected"</span>);
}

};

/**

  • 綁定服務 */ private void doBindService() { if(!isBound) { bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);

     isBound = <span class="hljs-literal">true</span>;
    
     mInfoTv.setText(<span class="hljs-string">"Binding..."</span>);
    

    } }

/**

  • 移除監聽並解綁服務 */ private void doUnbindService() { if(isBound) { if(mServiceWrapper != null) { try { Message msg = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT); msg.replyTo = mMessenger; mServiceWrapper.send(msg); } catch (RemoteException e) { e.printStackTrace(); } }

     unbindService(mConnection);
     isBound = <span class="hljs-literal">false</span>;
     mInfoTv.setText(<span class="hljs-string">"Unbinding..."</span>);
    

    } }

/**

  • 向服務端發送信息 */ private void doSendMsg() { if(mServiceWrapper != null) { try { Message msg = Message.obtain(null, MessengerService.MSG_HANDLE, this.hashCode(), 0); mServiceWrapper.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } }

4.3 客戶端發送信息

使用Messenger向服務端發送信息,使用的是Messenger.send(Message)方法,這個方法具體實現如下:

public void send(Message message) throws RemoteException {
    mTarget.send(message);
}

方法內部調用mTarget.send(Message)方法,在Messenger中,mTarget是在構造方法裡面被賦值的,有兩個構造函數。

public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }

第一個構造函數好理解,mTarget.send(Message)實際上是將Message加入了構造函數傳入的Handler的消息隊列,Demo工程中服務端向乘客端發送信息就是使用的這種方法

第二個構造函數是不是很眼熟,這不就是獲取AIDL定義的接口嘛!!!轉了一圈回到了上面的AIDL,客戶端向服務端發送信息實際上還是通過AIDL,只不過Android系統幫我們做了一層封裝。

5. 總結

到此,從Android之Service總結到Android中常用的進程通信都已經總結完畢,算是2016的一個完結,撒花!!

最後附上Demo工程地址:https://github.com/Kyogirante/AIDLDemo

    </div>

书籍推荐