1. 程式人生 > >Android-Messenger跨程序通訊

Android-Messenger跨程序通訊

http://blog.csdn.net/lmj623565791/article/details/47017485

 

一.概述

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

有這麼幾個特點:

  • 基於Message,相信大家都很熟悉

  • 支援回撥的方式,也就是服務端處理完成長任務可以和客戶端互動

  • 不需要編寫aidl檔案

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

 

二.應用

(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>

複製程式碼

 

(二)客戶端

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)給服務端了。

 

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

 

三.原理

 

原始碼解析那篇文章裡有非常詳細的分析,這裡給出我的個人理解:

 

先簡單總結一下應用流程:

  • 伺服器端new Messenger(new Handler()),然後在Handler裡定義handleMessage()接收和處理客戶端傳來的message,最後通過message.replyto.send()把結果傳送回去
  • 客戶端new Messenger(new Handler()),然後在Handler裡定義handleMessage()接收和處理伺服器端傳來的message。實現ServiceConnection類,並在onServiceConnected裡用mService = new Messenger(service)得到遠端介面,bindService連線後用mService.send傳送資料給伺服器端

 

(一)客戶端向服務端通訊

我們直接看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);
        }
    }

複製程式碼

其實看到這就知道了,Messenger內部也是用AIDL實現的

 

下面就直接用AIDL來理解Messenger:

  • 首先是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);  
}

複製程式碼

  • 然後要在stub中實現這個方法,在onbind中返回stub

複製程式碼

private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }

複製程式碼

和AIDL一樣,服務端stub的方法是給客戶端呼叫的,客戶端利用AIDL把msg傳來,再呼叫send方法交給伺服器端的handleMessage處理

 

客戶端

得到伺服器端的IBinder物件: mService = new Messenger(service);

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

 

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

客戶端向伺服器端傳送訊息:

複製程式碼

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

複製程式碼

客戶端利用AIDL把msg傳過去,再呼叫send方法交給伺服器端的handleMessage處理

 

伺服器接收並處理訊息:

複製程式碼

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);
        }

複製程式碼

取出客戶端發過來的message,處理後用message.replyTo.send()就可以直接把結果發回去

 

這裡的replyTo是客戶端指定的

msgFromClient.replyTo = mMessenger;

mMessenger就是客戶端的Messenger物件。伺服器端在反序列化客戶端傳來的message時,會通過這個replyTo得到客戶端的MessengerImpl物件。

 

換句話說,伺服器端通過客戶端的stub裡的send方法,把message結果傳送回去,最後在客戶端的handlerMessage裡處理

 

總結下:

  • 客戶端與服務端通訊,利用的aidl檔案,沒什麼特殊的

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

 

 

上面提到過通過Meaager跨程序不適合併發量大的情況,那麼如果併發量大的話,我們用什麼來處理呢?那就可以通過AIDL來進行,這裡是Google的描述

Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want 
to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you 
should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle 
multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before 
implementing an AIDL.

主要意思就是你可以用Messager處理簡單的跨程序通訊,但是高併發量的要用AIDL