Android Messenger完全解析 實現程式間通訊

張鴻洋的部落格發表於2015-07-25

一、概述

說到Android程式間通訊,大家肯定能想到的是編寫aidl檔案,然後通過aapt生成的類方便的完成服務端,以及客戶端程式碼的編寫。如果你對這個過程不熟悉,可以檢視Android aidl Binder框架淺析

當然今天要說的通訊方式肯定不是通過編寫aidl檔案的方式,那麼有請今天的主角:Messenger。ok,這是什麼樣的一個類呢?我們看下注釋

This allows for the implementation of message-based communication across processes

允許實現基於訊息的程式間通訊的方式。

那麼,什麼叫基於訊息的程式間通訊方式呢?看個圖理解下:

Android 基於Message的程式間通訊 Messenger完全解析

可以看到,我們可以在客戶端傳送一個Message給服務端,在服務端的handler中會接收到客戶端的訊息,然後進行對應的處理,處理完成後,再將結果等資料封裝成Message,傳送給客戶端,客戶端的handler中會接收到處理的結果。

這樣的程式間通訊是不是很爽呢?

  • 基於Message,相信大家都很熟悉
  • 支援回撥的方式,也就是服務端處理完成長任務可以和客戶端互動
  • 不需要編寫aidl檔案

此外,還支援,記錄客戶端物件的Messenger,然後可以實現一對多的通訊;甚至作為一個轉接處,任意兩個程式都能通過服務端進行通訊,這個後面再說。

看到這,有沒有一些的小激動,我們可以不寫aidl檔案,方便的實現程式間通訊了,是不是又可以裝一下了。哈,下面看個簡單的例子。

二、通訊例項

這個例子,通過兩個apk演示,一個apk是Server端,一個是Client端;

(1) Server端

程式碼

package com.imooc.messenger_server;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;

public class MessengerService extends Service
{

    private static final int MSG_SUM = 0x110;

    //最好換成HandlerThread的形式
    private Messenger mMessenger = new Messenger(new Handler()
    {
        @Override
        public void handleMessage(Message msgfromClient)
        {
            Message msgToClient = Message.obtain(msgfromClient);//返回給客戶端的訊息
            switch (msgfromClient.what)
            {
                //msg 客戶端傳來的訊息
                case MSG_SUM:
                    msgToClient.what = MSG_SUM;
                    try
                    {
                        //模擬耗時
                        Thread.sleep(2000);
                        msgToClient.arg2 = msgfromClient.arg1 + msgfromClient.arg2;
                        msgfromClient.replyTo.send(msgToClient);
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    } catch (RemoteException e)
                    {
                        e.printStackTrace();
                    }
                    break;
            }

            super.handleMessage(msgfromClient);
        }
    });

    @Override
    public IBinder onBind(Intent intent)
    {
        return mMessenger.getBinder();
    }
}

服務端就一個Service,可以看到程式碼相當的簡單,只需要去宣告一個Messenger物件,然後onBind方法返回mMessenger.getBinder();

然後坐等客戶端將訊息傳送到handleMessage想法,根據message.what去判斷進行什麼操作,然後做對應的操作,最終將結果通過 msgfromClient.replyTo.send(msgToClient);返回。

可以看到我們這裡主要是取出客戶端傳來的兩個數字,然後求和返回,這裡我有意新增了sleep(2000)模擬耗時,注意在實際使用過程中,可以換成在獨立開闢的執行緒中完成耗時操作,比如和HandlerThread結合使用。

註冊檔案

<service
            android:name=".MessengerService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.zhy.aidl.calc"></action>
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

別忘了註冊service,寫完以後直接安裝。

(二)客戶端

Activity

package com.imooc.messenger_client;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity
{
    private static final String TAG = "MainActivity";
    private static final int MSG_SUM = 0x110;

    private Button mBtnAdd;
    private LinearLayout mLyContainer;
    //顯示連線狀態
    private TextView mTvState;

    private Messenger mService;
    private boolean isConn;

    private Messenger mMessenger = new Messenger(new Handler()
    {
        @Override
        public void handleMessage(Message msgFromServer)
        {
            switch (msgFromServer.what)
            {
                case MSG_SUM:
                    TextView tv = (TextView) mLyContainer.findViewById(msgFromServer.arg1);
                    tv.setText(tv.getText() + "=>" + msgFromServer.arg2);
                    break;
            }
            super.handleMessage(msgFromServer);
        }
    });

    private ServiceConnection mConn = new ServiceConnection()
    {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            mService = new Messenger(service);
            isConn = true;
            mTvState.setText("connected!");
        }

        @Override
        public void onServiceDisconnected(ComponentName name)
        {
            mService = null;
            isConn = false;
            mTvState.setText("disconnected!");
        }
    };

    private int mA;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //開始繫結服務
        bindServiceInvoked();

        mTvState = (TextView) findViewById(R.id.id_tv_callback);
        mBtnAdd = (Button) findViewById(R.id.id_btn_add);
        mLyContainer = (LinearLayout) findViewById(R.id.id_ll_container);

        mBtnAdd.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                try
                {
                    int a = mA++;
                    int b = (int) (Math.random() * 100);

                    //建立一個tv,新增到LinearLayout中
                    TextView tv = new TextView(MainActivity.this);
                    tv.setText(a + " + " + b + " = caculating ...");
                    tv.setId(a);
                    mLyContainer.addView(tv);

                    Message msgFromClient = Message.obtain(null, MSG_SUM, a, b);
                    msgFromClient.replyTo = mMessenger;
                    if (isConn)
                    {
                        //往服務端傳送訊息
                        mService.send(msgFromClient);
                    }
                } catch (RemoteException e)
                {
                    e.printStackTrace();
                }
            }
        });

    }

    private void bindServiceInvoked()
    {
        Intent intent = new Intent();
        intent.setAction("com.zhy.aidl.calc");
        bindService(intent, mConn, Context.BIND_AUTO_CREATE);
        Log.e(TAG, "bindService invoked !");
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        unbindService(mConn);
    }

}

程式碼也不復雜,首先bindService,然後在onServiceConnected中拿到回撥的service(IBinder)物件,通過service物件去構造一個mService =new Messenger(service);然後就可以使用mService.send(msg)給服務端了。

我們訊息的傳送在Btn.onclick裡面:

Message msgFromClient = Message.obtain(null, MSG_SUM, a, b);
msgFromClient.replyTo = mMessenger;
if (isConn)
{
    //往服務端傳送訊息
    mService.send(msgFromClient);
}

那麼服務端會收到訊息,處理完成會將結果返回,傳到Client端的mMessenger中的Handler的handleMessage方法中。

佈局檔案

<LinearLayout android:id="@+id/id_ll_container"
              xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              android:paddingBottom="@dimen/activity_vertical_margin"
              android:paddingLeft="@dimen/activity_horizontal_margin"
              android:paddingRight="@dimen/activity_horizontal_margin"
              android:paddingTop="@dimen/activity_vertical_margin"
              tools:context=".MainActivity">

    <TextView
        android:id="@+id/id_tv_callback"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Messenger Test!"/>

    <Button android:id="@+id/id_btn_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="add"/>

</LinearLayout>

效果圖

Android 基於Message的程式間通訊 Messenger完全解析

可以看到,我們每點選一次按鈕,就往伺服器傳送個訊息,伺服器拿到訊息執行完成後,將結果返回。

整個通訊的程式碼看起來還是相當的清爽的,那麼大家有沒有對其內部的原理有一絲的好奇呢?下面我們就來看下其內部是如何實現的。

對了,原始碼分析前,這裡插一句,大家通過程式碼可以看到服務端往客戶端傳遞資料是通過msg.replyTo這個物件的。那麼服務端完全可以做到,使用一個List甚至Map去儲存所有繫結的客戶端的msg.replyTo物件,然後想給誰發訊息都可以。甚至可以把A程式發來的訊息,通過B程式的msg.replyTo發到B程式那裡去。相關程式碼呢,可以參考官方的文件:service,注意下拉找:Remote Messenger Service Sample。

三、原始碼分析

其實Messenger的內部實現的,實際上也是依賴於aidl檔案實現的。

(一)首先我們看客戶端向服務端通訊

服務端

服務端的onBind是這麼寫的:

public IBinder onBind(Intent intent)
{
   return mMessenger.getBinder();
}

那麼點進去:

public IBinder getBinder() {
   return mTarget.asBinder();
}

可以看到返回的是mTarget.asBinder();

mTarget是哪來的呢?

別忘了我們前面去構造mMessenger物件的程式碼:new Messenger(new Handler());

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

原來是Handler返回的,我們繼續跟進去

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) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }

mTarget是一個MessengerImpl物件,那麼asBinder實際上是返回this,也就是MessengerImpl物件;

這是個內部類,可以看到繼承自IMessenger.Stub,然後實現了一個send方法,該方法就是將接收到的訊息通過 Handler.this.sendMessage(msg);傳送到handleMessage方法。

看到這,大家有沒有想到什麼,難道不覺得extends IMessenger.Stub這種寫法異常的熟悉麼?

我們傳統寫aidl檔案,aapt給我們生成什麼,生成IXXX.Stub類,然後我們服務端繼承IXXX.Stub實現介面中的方法。

沒錯,其實這裡內部其實也是依賴一個aidl生成的類,這個aidl位於:frameworks/base/core/java/android/os/IMessenger.aidl.

package android.os;  

import android.os.Message;  

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

看到這,你應該明白了,Messenger並沒有什麼神奇之處,實際上,就是依賴該aidl檔案生成的類,繼承了IMessenger.Stub類,實現了send方法,send方法中引數會通過客戶端傳遞過來,最終傳送給handler進行處理。這裡不理解,請詳細看下Android aidl Binder框架淺析

客戶端

客戶端首先通過onServiceConnected拿到sevice(Ibinder)物件,這裡沒什麼特殊的,我們平時的寫法也是這樣的,只不過我們平時會這麼寫:

IMessenger.Stub.asInterface(service)拿到介面物件進行呼叫;

而,我們的程式碼中是

mService = new Messenger(service);

跟進去,你會發現:

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

soga,和我們平時的寫法一模一樣!

到這裡就可以明白,客戶端與服務端通訊,實際上和我們平時的寫法沒有任何區別,通過編寫aidl檔案,服務端onBind利用Stub編寫介面實現返回;客戶端利用回撥得到的IBinder物件,使用IMessenger.Stub.asInterface(target)拿到介面例項進行呼叫(內部實現,參考Android aidl Binder框架淺析)。

(2)服務端與客戶端通訊

那麼,客戶端與服務端通訊的確沒什麼特殊的地方,我們完全也可以編寫個類似的aidl檔案實現;那麼服務端是如何與客戶端通訊的呢?

還記得,客戶端send方法傳送的是一個Message,這個Message.replyTo指向的是一個mMessenger,我們在Activity中初始化的。

那麼將訊息傳送到服務端,肯定是通過序列化與反序列化拿到Message物件,我們看下Message的反序列化的程式碼:

# Message

private void readFromParcel(Parcel source) {
        what = source.readInt();
        arg1 = source.readInt();
        arg2 = source.readInt();
        if (source.readInt() != 0) {
            obj = source.readParcelable(getClass().getClassLoader());
        }
        when = source.readLong();
        data = source.readBundle();
        replyTo = Messenger.readMessengerOrNullFromParcel(source);
        sendingUid = source.readInt();
    }

主要看replyTo,呼叫的是Messenger.readMessengerOrNullFromParcel

public static Messenger readMessengerOrNullFromParcel(Parcel in) {
        IBinder b = in.readStrongBinder();
        return b != null ? new Messenger(b) : null;
    }

    public static void writeMessengerOrNullToParcel(Messenger messenger,
            Parcel out) {
        out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder()
                : null);
    }

通過上面的writeMessengerOrNullToParcel可以看到,它將客戶端的messenger.mTarget.asBinder()物件進行了恢復,客戶端的message.mTarget.asBinder()是什麼?

客戶端也是通過Handler建立的Messenger,於是asBinder返回的是:

public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
 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) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }

   public IBinder getBinder() {
        return mTarget.asBinder();
    }

那麼asBinder,實際上就是MessengerImpl extends IMessenger.Stub中的asBinder了。

#IMessenger.Stub

@Override 
public android.os.IBinder asBinder()
{
    return this;
}

那麼其實返回的就是MessengerImpl物件自己。到這裡可以看到message.mTarget.asBinder()其實返回的是客戶端的MessengerImpl物件。

最終,傳送給客戶端的程式碼是這麼寫的:

msgfromClient.replyTo.send(msgToClient);

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

這個mTarget實際上就是對客戶端的MessengerImpl物件的封裝,那麼send(message)(遮蔽了transact/onTransact的細節),這個message最終肯定傳到客戶端的handler的handleMessage方法中。

好了,到此我們的原始碼分析就結束了~~

總結下:

  • 客戶端與服務端通訊,利用的aidl檔案,沒什麼特殊的
  • 服務端與客戶端通訊,主要是在傳輸的訊息上做了處理,讓Messager.replyTo指向的客戶端的Messenger,而Messenger又持有客戶端的一個Binder物件(MessengerImpl)。服務端正是利用這個Binder物件做的與客戶端的通訊。

可以考慮自己編寫aidl檔案,實現下服務端對客戶端的回撥。

相關文章