深入理解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进行消息传递的大致流程如下图:
深入理解AsyncChannel、Messenger_第1张图片


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、Messenger_第2张图片

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、Messenger_第3张图片
方式二: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”。整个过程的大致流程图如下:
深入理解AsyncChannel、Messenger_第4张图片
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跨进程连接等,虽然这个工具类无法直接使用,但如果应用中有需要,仍然可以借鉴其原理作适合的简单实现。

你可能感兴趣的:(Android,Framework)