Android Service IPC通信之Messenger机制 - Go语言中文社区

Android Service IPC通信之Messenger机制


概述

之前我写过一篇博客介绍Binder:Android Binder机制完全解析,里面讲过如何实现Service的跨进程(IPC)通信,主要是通过编写AIDL接口文件来实现的。本篇我们来讲讲Service IPC通信的另外一种方式—Messenger
Messenger,也称为信使,通过它可以在不同的进程间传递message对象,在message中放入我们需要传递的数据你就可以实现跨进程通信和传递数据了。所以说Messenger机制是基于消息的跨进程通信方式。
这里写图片描述

可以看到,我们可以在客户端发送一个Message给服务端,在服务端的handler中会接收到客户端的消息,然后进行相应的处理,处理完成后,再将结果等数据封装成Message,发送给客户端,客户端的handler中会接收到处理的结果。

客户端和服务端可相互持有对方的Messenger来进行通信,除此之外,服务端还可以记录客户端对象的Messenger,然后实现一对多的通信;甚至作为一个转接处,任意两个进程都能通过服务端进行通信,这个后面再说。

实例演示

下面我们通过一个例子来演示一下Messenger机制的用法,客户端发送一条消息给服务端,提供两个参数和一个运算符,服务端完成两个参数的特定运算,并通过消息将运算结果发回给客户端,客户端再显示出来。

我们来看看具体的实现。在Studio下创建两个Module,分别为客户端和服务端:
这里写图片描述

先看看服务端代码:

public class MessengerService extends Service {
    private static final int MSG_SUM = 0x110;

    //最好换成HandlerThread的形式
    private Messenger mSeviceMessenger = 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 mSeviceMessenger.getBinder();
    }
}

服务端就一个Service,代码相当的简单,只需要去声明一个服务端Messenger对象,然后在onBind方法返回mSeviceMessenger.getBinder();

然后就坐等客户端将消息发送到handleMessage了,根据message.what去判断进行什么操作,然后做相应的操作(这里只实现了相加运算),最终将结果通过 msgfromClient.replyTo.send(msgToClient);发回给客户端。

可以看出这里是取出客户端传来的两个数字,然后求和返回,这里我有意添加了Thread.sleep(2000);模拟耗时,注意在实际使用过程中,应该换成在独立开辟的线程中完成耗时操作(否则耗时过长会引起服务端ANR),比如和HandlerThread结合使用。

public class MessengerService extends Service {
    private HandlerThread mThread;
    private Handler mHandler;
    private Messenger mSeviceMessenger;
    private static final int MSG_SUM = 0x110;

    @Override
    public void onCreate() {
        super.onCreate();
        initBackThread();
    }

    private void initBackThread() {
        mThread = new HandlerThread("MessengerService");
        mThread.start();
        //注意这里Handler中传入的是HandlerThread的Looper
        mHandler = new Handler(mThread.getLooper()) {
            @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);
            }
        };
        mSeviceMessenger = new Messenger(mHandler);
    }

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

别忘了在Manifest里注册service:

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

接下来看客户端代码
布局文件:

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

思路是这样的,最上面的TextView显示当前和远程Service的连接状态。Button按钮每点击一次就会生成两个整形参数,用一个TextView显示出来动态添加到LinearLayout,并且将这两个参数通过message发送给服务端,待服务端返回结果后将结果也显示到对应的TextView上。

public class MainActivity extends AppCompatActivity {
    private Button mBtnAdd;
    private LinearLayout mContainer;
    private TextView mTvState;      //显示连接状态

    private Messenger mServiceMessenger;
    private boolean isConn;
    private static final int MSG_SUM = 0x110;
    private int mNum = 0; //相加运算第一个参数

    private Messenger mClientMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msgFromServer) {
            switch (msgFromServer.what) {
                case MSG_SUM:
                    TextView tv = (TextView) mContainer.findViewById(msgFromServer.arg1); //根据Id找到对应的TextView,防止错乱
                    tv.setText(tv.getText() + " = " + msgFromServer.arg2);
                    break;
            }
            super.handleMessage(msgFromServer);
        }
    });


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

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

    @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);
        mContainer = (LinearLayout) findViewById(R.id.id_ll_container);

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

                    //创建一个TextView,添加到LinearLayout中
                    TextView tv = new TextView(MainActivity.this);
                    tv.setText(a + " + " + b);
                    tv.setId(a); //这里给TextView赋一个Id,之后可以从mContainer中通过Id找到这个TextView
                    mContainer.addView(tv);

                    Message msgFromClient = Message.obtain(null, MSG_SUM, a, b);
                    msgFromClient.replyTo = mClientMessenger;
                    if (isConn) {
                        //往服务端发送消息
                        mServiceMessenger.send(msgFromClient);
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    private void bindServiceInvoked() {
        Intent intent = new Intent();
        intent.setAction("com.hx.messenger");
        bindService(intent, mConn, Context.BIND_AUTO_CREATE);
    }

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

首先bindService,然后在onServiceConnected中拿到回调的service(IBinder)对象,通过service对象去构造一个服务端Messenger 对象,然后就可以使用mServiceMessenger.send(msg)发送消息给服务端了。服务端会收到消息,处理完成后会将结果返回,传到客户端的mClientMessenger中的Handler的handleMessage方法中。

这里写图片描述

实际上,通过Messenger来传输Message,Message中能使用的载体只有what,arg1,arg2,Bundle和replyTo这几种。Message的另一个字段object在同一进程中很实用,但在IPC通信时,2.2之前不支持,2.2之后也仅仅是系统提供的实现了Parcelable接口的对象才能通过它传输。所幸还有bundle,bundle中支持大量数据类型。

Bundle bundle = new Bundle();
bundle.putString("msg", "hello");
msgFromClient.setData(bundle);

接收端:

msgfromClient.getData().getString("msg");

注:若你使用的是android 5.0版本以上的设备,运行客户端程序会崩溃,控制台报如下错误:Java.lang.IllegalArgumentException: Service Intent must be explicit
原因:在android 5.0版本(Lollipop)以后,service intent必须为显式指出
这里有两种解决方法

  • 设置intent的packageName

在bindService之前,调用intent.setPackage(packagename);方法,packagename为定义service所在的包名。此方式是google官方推荐使用的解决方法。

Intent intent = new Intent();
intent.setAction("com.hx.messenger");    intent.setPackage("com.hx.messenger_server.MessengerService");
bindService(intent, mConn, Context.BIND_AUTO_CREATE);

实验证明这种方式只适用于调用同进程的Service,而不适用于调用远程Service。所以不能用于本例的情况。

  • 将隐式启动转换为显示启动
    public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
        // Retrieve all services that can match the given intent
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
        // Make sure only one match was found
        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }
        // Get component info and create ComponentName
        ResolveInfo serviceInfo = resolveInfo.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);
        // Create a new intent. Use the old one for extras and such reuse
        Intent explicitIntent = new Intent(implicitIntent);
        // Set the component to be explicit
        explicitIntent.setComponent(component);
        return explicitIntent;
    }

    //调用方式如下
    private void bindServiceInvoked() {
        Intent intent = new Intent();
        intent.setAction("com.hx.messenger");
        //for android 5.0 and later, service intent must be explicit
        Intent eintent = new Intent(getExplicitIntent(this,intent));
        bindService(eintent, mConn, Context.BIND_AUTO_CREATE);
    }

本例可以使用这种方法解决。

同时运行一下服务端和客户端程序,多次点击ADD按钮,效果如下:
这里写图片描述

可以看到,我们每点击一次按钮,就往服务器发送一条消息,服务器拿到消息执行完成后,将结果返回。
这里值得说明的一点是,大家通过代码可以看到服务端往客户端传递数据是通过msg.replyTo这个对象的。那么服务端完全可以做到,使用一个List甚至Map去存储所有绑定的客户端的msg.replyTo对象,然后想给谁发消息都可以。甚至可以把A进程发来的消息,通过B进程的msg.replyTo发到B进程那里去,从而实现任意两个进程都能通过服务端进行通信。

源码分析

其实Messenger的内部实现也是依赖于aidl文件的。
(1)首先我们看客户端向服务端通信
服务端:
服务端的onBind是这么写的:

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

看看getBinder()方法

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

可以看到返回的是mTarget.asBinder(); mTarget是什么呢?别忘了我们前面去构造mServiceMessenger对象的代码: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进行处理。
客户端:
客户端首先通过onServiceConnected拿到sevice(Ibinder)对象,这里没什么特殊的,我们平时的写法也是这样的,只不过我们平时会这么写:

Service mService = IMessenger.Stub.asInterface(service);

而我们的代码中是

mServiceMessenger = new Messenger(service);

跟进去,你会发现:

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

原来和我们平时的写法一模一样!
到这里就可以明白,客户端与服务端通信,实际上和我们平时的写法没有任何区别,通过编写aidl文件,服务端onBind利用Stub编写接口实现返回;客户端利用回调得到的IBinder对象,使用IMessenger.Stub.asInterface(target)拿到接口实例进行调用。

(2)服务端与客户端通信
客户端与服务端通信的确没什么特殊的地方,我们完全也可以编写个类似的aidl文件实现;那么服务端是如何与客户端通信的呢?
还记得,客户端send方法发送的是一个Message,这个Message.replyTo指向的是一个mClientMessenger,我们是在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(source);

    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对象做到与客户端的通信。

Demo下载地址

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/huaxun66/article/details/52966469
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-16 12:54:56
  • 阅读 ( 874 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢