深入理解AsyncChannel、Messenger

這篇文章主要介紹Android AsyncChannel、Messenger原理及其應用實例
注:文章參考的是Andrdoid 8.0源碼

AsyncChannel 簡介
  
  AsyncChannel的源碼位於 frameworks/base/core/java/com/android/internal/util/AsyncChannel.java,是對Handler和Messenger的一個擴展,用於單個進程內(包含不同線程間)或兩個進程間的通信。在不同的進程間,AsyncChanne實質上使用的是IMessenger通過Binder的形式對消息進行發送和接收,當然,這種方式對單個進程內非remove service同樣適用。
  在Android系統中,有很多地方都使用了這個工具類,如ConnectivityService與NetworkAgent的通信,WifiServiceImpl與WifiStateMachine之間的通信等。由於AsyncChannel屬於系統內部源碼,三方應用無法直接進行使用,但我們可以學習思想,甚至可以自己簡單實現並作用於我們的APP代碼中。

AsyncChannel的主要特點:

  • 可以在單進程或不同進程間實現消息傳遞
  • 支持建立單向通信或雙向通信
  • 是對Handler,Messenger的一種包裝,並沒有實現額外的通信方式

Messenger

  由於AsyncChannel實際上使用的是Handler和Messenger的機制,考慮到很多開發者對Messenger還不夠了解,所以我們這裏有必要先對這個工具做一個詳細的解析。
  Messenger的源碼位於 frameworks/base/core/java/android/os/Messenger.java,是對Handler的一個再包裝,並結合了Binder機制,使得跨進程間Message的傳遞和處理成爲了可能。

成員變量及構造函數:

  主要成員變量是IMessenger類型的mTarget,該變量可以通過Handler或者IBinder進行初始化,分別應用於同進程消息或者跨Service(remote或者非remote)消息發送:

// IMessenger類型,mTarget代表message的目的端
private final IMessenger mTarget;

// 初始化mTarget並指向Handler中的IMessenger
public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

// 初始化mTarget並指向IBinder的實際類型
public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

Messenger的發送函數:

  無論是否跨進程,Messenger實際上都是利用IMessenger進行消息發送的:

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

IMessenger:

  源碼位於 frameworks/base/core/java/android/os/IMessenger.aidl,其目的是通過AIDL使用Binder機制對Message進行發送:

/** @hide */
oneway interface IMessenger {
    void send(in Message msg);
}

通過Handler對mTarget進行初始化:

  Handler中MessengerImpl對IMessenger進行了實現,在 send 函數中最終還是使用Handler自身對Message進行了處理,因此,這裏的IMessenger僅僅是一箇中介:

final IMessenger getIMessenger() {
    synchronized (mQueue) {
        if (mMessenger != null) {
           return mMessenger;
        }
        mMessenger = new MessengerImpl();
        return mMessenger;
    }
}

private final class MessengerImpl extends IMessenger.Stub {
    public void send(Message msg) {
        // 由於存在Binder調用,初始化sendingUid爲發送端uid
        msg.sendingUid = Binder.getCallingUid();
        // 通過Handler自身發送消息並進行處理
        Handler.this.sendMessage(msg);
    }
}

通過Service IBinder對mTarget進行初始化:
  
  這裏以Android源碼中針對Messenger的測試case作爲例子對其過程進行解析,源碼路徑:
- frameworks/base/core/tests/coretests/src/android/os/MessengerService.java
- frameworks/base/core/tests/coretests/src/android/os/MessengerTest.java

Service端實現(MessengerService.java):

public class MessengerService extends Service {
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // 在這裏處理client端發送的消息
        }
    };

    // 定義一個Messenger,Messenger中的mTarget即爲mHandler中的mMessenger
    private final Messenger mMessenger = new Messenger(mHandler);

    @Override
    public IBinder onBind(Intent intent) {
        // 返回mHandler中的mMessenger
        return mMessenger.getBinder();
    }
}

Client端實現(MessengerTest.java):

  通過 bindService 與MessengerService建立連接,連接完成後,對Messenger進行初始化:

private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (MessengerTest.this) {
            // 通過IBinder類型的service對Messenger中的mTarget進行初始化
            mServiceMessenger = new Messenger(service);
            MessengerTest.this.notifyAll();
        }
    }
    public void onServiceDisconnected(ComponentName name) {
        // service斷開後,對應的messenger也不會再有作用
        mServiceMessenger = null;
    }
};

  因此,這樣初始化後,下面的幾個變量就可以看做是同一個對象的引用,即:
  MessengerTest.mServiceMessenger.mTarget
    = MessengerService.mMessenger.mTarget
    = MessengerService.mHandler.mMessenger

Messenger小結:

  實質上是通過IMessenger利用Binder機制進行消息的發送。Handler中實現了IMessenger中的 send 函數,並在其中調用Handler自身的 sendMessage 函數進行消息處理;Service中可以定義自己的Handler和對應的Messenger,並在onBind時返回Handler中MessengerImpl的實例,Client端在 onServiceConnected 函數中通過IBinder對象對Client中Messenger進行初始化,之後,Client端就可以通過該Messenger將消息發送到Service中的Handler進行處理了。Client和Service間通過Messenger進行消息傳遞的大致流程如下圖:
通過Messenger與Service進行消息傳遞


AsyncChannel成員常量和變量

註明:從分析AsyncChannel開始,我們稱連接的首次發起端爲"source端",被連接端爲"destination端"

連接指令相關靜態常量:

/** AsyncChannel消息碼在系統中的唯一開始標誌 */
private static final int BASE = Protocol.BASE_SYSTEM_ASYNC_CHANNEL;

/** 單向連接建立後,AsyncChannel通知source端半連接已建立,可以進行單向通信,
    但此時destination端是完全沒有感知的 */
public static final int CMD_CHANNEL_HALF_CONNECTED = BASE + 0;

/** source端在半連接建立後,發送該消息碼給destination端,請求建立雙向連接 */
public static final int CMD_CHANNEL_FULL_CONNECTION = BASE + 1;

/** destination端在建立雙向連接後,發送該消息碼給source端,告知雙向連接建立完成 */
public static final int CMD_CHANNEL_FULLY_CONNECTED = BASE + 2;

/** source或者destination主動請求斷開 */
public static final int CMD_CHANNEL_DISCONNECT = BASE + 3;

/** 在一端主動斷開後,對端會收到該消息碼 */
public static final int CMD_CHANNEL_DISCONNECTED = BASE + 4;

連接和消息狀態相關靜態常量:

/** 連接狀態相關:成功半連接,成功連接,成功斷開 */
public static final int STATUS_SUCCESSFUL = 0;

/** 跨service連接,bindService成功或者失敗 */
public static final int STATUS_BINDING_UNSUCCESSFUL = 1;

/** 消息發送失敗 */
public static final int STATUS_SEND_UNSUCCESSFUL = 2;

/** 已建立雙向連接,拒絕再次連接 */
public static final int STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED = 3;

/** 跨service連接,對端service死亡,binderDied返回後發送該狀態碼 */
public static final int STATUS_REMOTE_DISCONNECTION = 4;

主要成員變量:

/** ServiceConnection,用於和service或remote service建立連接 */
private AsyncChannelConnection mConnection;

/** 連接建立端Context,僅用於bind/unbind service時使用 */
private Context mSrcContext;

/** binderDied回調監聽,僅用於與service連接後使用 */
private DeathMonitor mDeathMonitor;

/** 連接建立端Handler */
private Handler mSrcHandler;

/** 連接建立端Messenger */
private Messenger mSrcMessenger;

/** 連接對端Messenger */
private Messenger mDstMessenger;

AsyncChannel連接過程 - 半連接(Half Connect)

Source端發起連接請求:

/** 發起連接 */
public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
    // source端AsyncChannel變量初始化
    connected(srcContext, srcHandler, dstMessenger);

    // 發送"CMD_CHANNEL_HALF_CONNECTED"告知source端半連接已建立
    replyHalfConnected(STATUS_SUCCESSFUL);
}
/** 初始化source端的成員變量,創建source端Messenger */
public void connected(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
    // Initialize source fields
    mSrcContext = srcContext;
    mSrcHandler = srcHandler;
    mSrcMessenger = new Messenger(mSrcHandler);

    // Initialize destination fields
    mDstMessenger = dstMessenger; 
    linkToDeathMonitor();
}
/** 發送"CMD_CHANNEL_HALF_CONNECTED"告知source端半連接已建立 */
private void replyHalfConnected(int status) {
    Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED);
    msg.arg1 = status;
    msg.obj = this;
    msg.replyTo = mDstMessenger;
    if (!linkToDeathMonitor()) {
        // Override status to indicate failure
        msg.arg1 = STATUS_BINDING_UNSUCCESSFUL;
    }
    mSrcHandler.sendMessage(msg);
}

半連接小結:

  整個半連接過程由連接發起端(source端)完成,destination端沒有任何消息告知。source端調用AsyncChannel的 connect 函數後,開始對AsyncChannel中變量的初始化,主要是對mDstMessenger的初始化。完成後source端Handler會收到 “CMD_CHANNEL_HALF_CONNECTED” 的消息告知半連接已經完成,之後,source端就可以通過該AsyncChannel與destination進行通信。這種通信方式是單向的,只能由source端主動向destination推送消息並獲取其回覆,destination無法主動向source推送消息。這個連接過程可以由下面的時序圖來表示:
AsyncChannel half connect

AsyncChannel連接過程 - 全連接(Full Connect)

AsyncChannel的全連接建立主要有以下兩種方式:

  • 在半連接的基礎上由source端發送 “CMD_CHANNEL_FULL_CONNECTION” 請求全連接
  • source端直接調用AsyncChannel的 fullyConnectSync 進行全連接

下面對這兩種全連接建立方式分別作解析。

方式一(在半連接的基礎上由source端發送 “CMD_CHANNEL_FULL_CONNECTION” 請求全連接):

  這個過程主要是由AsyncChannel使用者自己完成,我們以ConnectivityService與NetworkAgent之間的通信進行舉例,例子的關鍵源碼路徑如下:
- frameworks/base/services/core/java/com/android/server/ConnectivityService.java
- frameworks/base/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
- frameworks/base/core/java/android/net/NetworkAgent.java

  首先,ConnectivityService作爲連接主動端主動請求半連接:

/** ConnectivityService#handleRegisterNetworkAgent */
private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
    // ...
    // 通過NetworkAgentInfo中新創建的AsyncChannel主動請求連接
    // mTrackerHandler是ConnectivityService中的Handler,作爲source handler
    // na.messenger是連接的對端,這裏就是指 NetworkAgent
    na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
    // ..
}

  通過對半連接的瞭解,在 connect 之後,source handler就會收到 “CMD_CHANNEL_HALF_CONNECTED”。由於需要建立全連接,ConnectivityService在收到該消息後,立刻向半連接對端請求全連接:

/** ConnectivityService#NetworkStateTrackerHandler */
private class NetworkStateTrackerHandler extends Handler {
    // ...
    private boolean maybeHandleAsyncChannelMessage(Message msg) {
        switch (msg.what) {
            case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
                // 收到 "CMD_CHANNEL_HALF_CONNECTED",在下面的函數中處理
                handleAsyncChannelHalfConnect(msg);
                break;
            }
            // ...
}

/** ConnectivityService#handleAsyncChannelHalfConnect */
private void handleAsyncChannelHalfConnect(Message msg) {
    // ...
    else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
            if (VDBG) log("NetworkAgent connected");
            // 發送全連接請求 "CMD_CHANNEL_FULL_CONNECTION"
            mNetworkAgentInfos.get(msg.replyTo).asyncChannel.
                 sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
        }
}

  NetworkAgent在收到 “CMD_CHANNEL_FULL_CONNECTION” 請求後,便開始創建自己的AsyncChannel,並完成其初始化,連接的對端是ConnectivityService:

/** NetworkAgent#handleMessage */
public void handleMessage(Message msg) {
    switch (msg.what) {
        // 收到由ConnectivityService發來的全連接請求
        case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
            if (mAsyncChannel != null) {
                log("Received new connection while already connected!");
            } else {
                if (VDBG) log("NetworkAgent fully connected");
                // 創建自己的AsyncChannel
                AsyncChannel ac = new AsyncChannel();
                // msg.replayTo 就是NetworkAgentInfo.asyncChannel中的mSrcMessenger,
                // 消息的處理者就是ConnectivityService.mTrackerHandler
                ac.connected(null, this, msg.replyTo);
                // 全連接已經建立,發送 "CMD_CHANNEL_FULLY_CONNECTED" 給ConnectivityService
                ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
                        AsyncChannel.STATUS_SUCCESSFUL);
                // ...
            }
            break;
     // ...
}

  從上述的過程可以看出,建立全連接的方式就是連接的兩端都需要創建自己的AsyncChannel,並且每個AsyncChannel中都保存着對端的Messenger。這種全連接的方式可以通過下面流程圖進行表示:
AsyncChannel full connect
方式二:source端直接調用AsyncChannel的 fullyConnectSync 進行全連接

  從 fullyConnectSync 這個函數名,可以知道這是一個同步的操作,AsyncChannel中使用了SyncMessenger這個靜態內部類來實現這個同步連接的操作。直接從 fullyConnectSync 這個函數開始分析。

  fullyConnectSync 中對source端AsyncChannel進行了初始化,併發送 “CMD_CHANNEL_FULL_CONNECTION” 同步消息請求全連接:

/** AsyncChannel#fullyConnectSync*/
public int fullyConnectSync(Context srcContext, Handler srcHandler, Handler dstHandler) {
    // 只是調用了connected函數,對source端AsyncChannel成員變量進行初始化
    int status = connectSync(srcContext, srcHandler, dstHandler);
    if (status == STATUS_SUCCESSFUL) {
        // 發送"CMD_CHANNEL_FULL_CONNECTION"同步消息給destination
        // 同步消息,需要等待迴應後才繼續
        Message response = sendMessageSynchronously(CMD_CHANNEL_FULL_CONNECTION);
        status = response.arg1;
    }
    return status;
}

  sendMessageSynchronously 實際使用的是 “SyncMessenger.sendMessageSynchronously”:

/** AsyncChannel.SyncMessenger#sendMessageSynchronously*/
private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {
    // 獲取一個SyncMessenger對象,啓動消息接收者SyncHandler
    SyncMessenger sm = SyncMessenger.obtain();
    try {
        if (dstMessenger != null && msg != null) {
            msg.replyTo = sm.mMessenger;
            // 獲取對象鎖:"sm.mHandler.mLockObject"
            synchronized (sm.mHandler.mLockObject) {
                // 發送 "CMD_CHANNEL_FULL_CONNECTION"
                dstMessenger.send(msg);
                // 釋放該鎖並中斷等待notify
                sm.mHandler.mLockObject.wait();
            }
        } else {
            sm.mHandler.mResultMsg = null;
        }
    } catch (InterruptedException e) {
        sm.mHandler.mResultMsg = null;
    } catch (RemoteException e) {
        sm.mHandler.mResultMsg = null;
    }
    // 被CPU喚醒,消息回覆已經收到,返回該回復
    Message resultMsg = sm.mHandler.mResultMsg;
    sm.recycle();
    return resultMsg;
}

  通過SyncMessenger向對端發送完消息後,對端如果已經完成了 connect 的操作或者拒絕連接,都應該回復消息。由於發送消息前設定了msg.replyTo = sm.mMessenger,因此回覆的消息會被SyncManager中的SyncHandler處理:

/** AsyncChannel.SyncManager#SyncHandler#handleMessage*/
public void handleMessage(Message msg) {
    mResultMsg = Message.obtain();
    mResultMsg.copyFrom(msg);
    // 獲取mLockObject對象鎖
    synchronized(mLockObject) {
        // 喚醒在mLockObject這個鎖上等待的線程
        mLockObject.notify();
    }
}

  從上面的流程可以看出,通過 fullyConnectSync 建立全連接省略了中間 “CMD_CHANNEL_HALF_CONNECTED” 消息回覆過程,並且通過SyncMessenger,實現了在對端完成connect操作後才返回。因此,這個過程不需要AsyncChannel的主動連接者進行其他操作就可以完成全連接。然而,AsyncChannel的對端依然需要處理 “CMD_CHANNEL_FULL_CONNECTION”。整個過程的大致流程圖如下:
fullyConnectSync
AsyncChannel的跨進程消息傳遞

  由於AsyncChannel內部使用的是Messenger,且Handler和Messenger都已經支持了Binder通信方式,因此,AsyncChannel同樣可以利用在跨進程通信上。
  前文在介紹Messenger時,通過例子”MessengerService”對Messenger做了解析,AsyncChannel在跨進程通信上與前文例子相似:在連接前,需要先bindService,在 onServiceConnected 時,拿到Service的Messenger並完成了對mDstMessenger的初始化,從而實現了半連接。之後,全連接的方式就與前文介紹的相同。

/** AsyncChannel#connect*/
public void connect(Context srcContext, Handler srcHandler, String dstPackageName,
            String dstClassName) {
    // 由於有bindService操作,並且需要等待onServiceConnected回調,
    // 因此實現了一個Runnable並且新開一個線程去完成這個操作
    final class ConnectAsync implements Runnable {
        // ...
        @Override
        public void run() {
            // 完成對AsyncChannel中除mDstMessenger的成員變量的初始化
            // 並執行bindService操作
            int result = connectSrcHandlerToPackageSync(mSrcCtx, mSrcHdlr, mDstPackageName,
                     mDstClassName);
            replyHalfConnected(result);
        }
    }
    ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName);
    // 新開一個線程執行異步連接操作
    new Thread(ca).start();
}

  connectSrcHandlerToPackageSync 完成了對AsyncChannel中基本成員變量的初始化,與destination相關的mDstMessenger變量需要等待 onServiceConnected 回調後再進行初始化:

/** AsyncChannel#connectSrcHandlerToPackageSync */
public int connectSrcHandlerToPackageSync(
        Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName) {
    mConnection = new AsyncChannelConnection();

    /* 初始化AsyncChannel source相關的變量 */
    mSrcContext = srcContext;
    mSrcHandler = srcHandler;
    mSrcMessenger = new Messenger(srcHandler);

    // 對端Messenger等待onServiceConnected後再進行初始化
    mDstMessenger = null;

    // bindService
    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.setClassName(dstPackageName, dstClassName);
    boolean result = srcContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    return result ? STATUS_SUCCESSFUL : STATUS_BINDING_UNSUCCESSFUL;
}

  AsyncChannelConnection#onServiceConnected 會在Service bind成功後被回調,在這個函數中,完成了對 mDstMessenger 的初始化,到此,半連接完成:

/** AsyncChannel#AsyncChannelConnection */
class AsyncChannelConnection implements ServiceConnection {
    AsyncChannelConnection() {
    }
    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        // service bind成功,初始化mDstMessenger
        mDstMessenger = new Messenger(service);
        replyHalfConnected(STATUS_SUCCESSFUL);
    }
    // ...
}

  跨進程或同進程Service的AsyncChannel連接與普通的相同,只不過多了一步bindService的操作,並且mDstMessenger 需要等待 onServiceConnected 回調後才能完成初始化。

總結

  AsyncChannel 結合Messenger,實現了兩個進程或線程間Handler消息的傳遞。這篇文章主要介紹了Messenger的原理、AsyncChannel的兩種連接方式:半連接和全連接、AsyncChannel跨進程連接等,雖然這個工具類無法直接使用,但如果應用中有需要,仍然可以借鑑其原理作適合的簡單實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章