在基础使用篇我们在service中定义了一个内部类继承自Binder,然后在onBind()方法中返回一个该类的实例,以此来实现服务与活动之间的通信。但是,这种方式只能在应用程序自身内部进行通信,不能支持跨进程通信。那如果需要用到跨进程通信,怎么办呢?可以通过Messenger和AIDL。本篇将介绍Messenger在跨进程通信中的使用。

Messenger,通俗讲就是信使,带信的哥们。它是一种轻量级的IPC方案,底层实现其实就是AIDL。

一、特点:

  1. 串行处理客户端发送过来的消息;
  2. 主要用于传递消息

二、支持的数据类型:

Messenger是使用Message来进行消息传递的,所以基本上可以说Message支持的消息类型就是Messenger所支持的类型了。为什么说基本上呢?肯定是有原因的,就是Message的object字段比较特殊。这里借用任玉刚在《Android开发艺术探索》第二章中的一段话:

简单来说,Message中所支持的数据类型就是Messenger所支持的传输类型。实际上,通过Messenger来传输Message,Message中能使用的载体只有what、arg1、arg2、Bundle以及replyTo。Message中的另一个字段object在同一个进程中是很实用的,但是在进程间通信的时候,在Android 2.2以前object字段不支持跨进程传输,即便是2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的。

三、具体使用

1、单向通信(客户端->服务端)

服务端

步骤如下:

  1. 新建一个项目,在项目中新建一个MessengerService类继承自Service;
  2. 在MessengerService类的内部定义一个内部类MessengerServiceHandler继承自Handler,根据具体业务需求重写handleMessage()方法;
  3. 通过传入一个MessengerServiceHandler的实例来创建Messenger对象;
  4. 在onBind()方法中,将Messenger对象的Binder字段返回;

具体代码如下

public class MessengerService extends Service {

    private static final String TAG = "MessengerService";

    //通过传入一个MessengerServiceHandler实例来创建一个Messenger对象
    private Messenger messenger = new Messenger(new MessengerServiceHandler());

    public MessengerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        //返回Messenger的Binder字段给客户端
        return messenger.getBinder();
    }


    //定义一个MessengerServiceHandler继承自Handler,并重新handleMessage()方法
    public static class MessengerServiceHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1: //这里的1主要起消息动作区分作用,需要自己在客户端和服务端对应定义好,通俗来讲就是要知道1的时候是干嘛,2的时候是干嘛
                    //这里只是做演示,所以只是直接打印客户端传递过来的数据
                    Log.d(TAG, "MessengerService receive a message:" + msg.getData().getString("hello"));
                    break;
                 default:
                    break;
            }
        }
    }
}

记得在AndroidManifest.xml中注册

<service
            android:name=".MessengerService"
            android:enabled="true"
            android:exported="true">
             <!--这里intent-filter用于隐式启动,如果是显示启动,则可以不写-->
            <intent-filter>
                <action android:name="com.czh.messengerservice.MessengerService"></action>
            </intent-filter>
        </service>

客户端

同样,因为是要验证进程间通信,所以我们重新创建一个项目,在MainAcitvity中编写代码,主要步骤为:

  1. 通过客户端与服务端的绑定,在onServiceConnected()中获取到IBinder实例,利用该实例创建出Messenger对象;
  2. 调用Messenger对象的send()方法将消息发送给服务端;

代码如下:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MessengerClient";

    private Button btn_bind;
    private Button btn_unbind;
    private Button btn_sayhello;

    private Messenger serviceMessenger;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            
            //在绑定成功后拿到IBinder的实例,并通过该实例来创建用来与服务端通信的serviceMessenger
            serviceMessenger = new Messenger(iBinder);
            Log.d(TAG, "绑定到服务端成功");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            serviceMessenger = null;
            Log.d(TAG, "连接异常断开");
        }
    };

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

        btn_bind = findViewById(R.id.btn_bind);
        btn_unbind = findViewById(R.id.btn_unbind);
        btn_sayhello = findViewById(R.id.btn_sayhello);

        btn_bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //这里用的是显示启动
                Intent intent = new Intent();
                ComponentName componentName = new ComponentName("com.czh.messengerservice", "com.czh.messengerservice.MessengerService");
                intent.setComponent(componentName);

                //service隐式启动,5.0之后必须指定包名,否则报错
                //远程服务的service需要在intentFilter中添加对应action
                //intent.setAction("com.czh.messengerservice.MessengerService");
                //intent.setPackage("com.czh.messengerservice");

                bindService(intent, conn, BIND_AUTO_CREATE);
            }
        });

        btn_unbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (serviceMessenger != null) {
                    serviceMessenger = null;
                    unbindService(conn);
                    Log.d(TAG, "解除绑定");
                }
            }
        });

        btn_sayhello.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (serviceMessenger != null) {
                    Message message = Message.obtain();
                    Bundle bundle = new Bundle();
                    bundle.putString("hello", "hello, service!");
                    message.setData(bundle);
                    message.what = 1;//用作标识,根据服务端定义,用来区分要做什么事情,下同
                    try {
                        //将消息发送给服务端
                        serviceMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Log.d(TAG, "请先绑定服务");
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (serviceMessenger != null) {
            serviceMessenger = null;
            unbindService(conn);
            Log.d(TAG, "解除绑定");
        }
    }
}

可以看到,我们再客户端应用放置了3个按钮,分别用来绑定、解绑和打招呼,启动两个应用程序,分别点击绑定、打招呼、解绑。

客户端Log如下:

android samba 安装 android samba server_进阶

服务端Log如下:

android samba 安装 android samba server_android samba 安装_02

这样,我们就实现了简单的从客户端到服务端的跨进程通信了。

 

2、双向通信(客户端<->服务端)

有时候,我们不仅需要服务端能够接收到客户端发送过来的消息,还需要服务端能够发送消息给客户端,怎么搞?

让我们思考一下,客户端能给服务端发消息,靠的就是服务端派过来的Messenger对象(确切地说,是利用从服务端传递过来的IBinder实例创建出的Messenger对象)。那我们服务端要给客户端发消息,需要的就是客户端给服务端一个信使(Messenger对象),这样,通过这个信使,服务端就能给客户端发消息了。

下面请看具体的代码改造:

客户端

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MessengerClient";

    private Button btn_bind;
    private Button btn_unbind;
    private Button btn_sayhello;
    private Button btn_doplus;

    private Messenger serviceMessenger;

    //新增
    //我们需要在客户端也定义一个类继承自Handler,利用该类的实例来创建客户端的Messenger实例
    public static class MessengerClientHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Log.d(TAG, "result:" + msg.getData().getInt("result"));
                    break;
                default:
                    break;
            }
        }
    }

    //新增
    //通过MessengerClientHandler实例来创建clientMessenger对象
    private Messenger clientMessenger = new Messenger(new MessengerClientHandler());

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            serviceMessenger = new Messenger(iBinder);
            Log.d(TAG, "绑定到服务端成功");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            serviceMessenger = null;
            Log.d(TAG, "连接异常断开");
        }
    };

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

        btn_bind = findViewById(R.id.btn_bind);
        btn_unbind = findViewById(R.id.btn_unbind);
        btn_sayhello = findViewById(R.id.btn_sayhello);
        btn_doplus = findViewById(R.id.btn_doplus);

        btn_bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //这里用的是显示启动
                Intent intent = new Intent();
                ComponentName componentName = new ComponentName("com.czh.messengerservice", "com.czh.messengerservice.MessengerService");
                intent.setComponent(componentName);

                //service隐式启动,5.0之后必须指定包名,否则报错
                //远程服务的service需要在intentFilter中添加对应action
                //intent.setAction("com.czh.messengerservice.MessengerService");
                //intent.setPackage("com.czh.messengerservice");

                bindService(intent, conn, BIND_AUTO_CREATE);
            }
        });

        btn_unbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (serviceMessenger != null) {
                    serviceMessenger = null;
                    unbindService(conn);
                    Log.d(TAG, "解除绑定");
                }
            }
        });

        btn_sayhello.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (serviceMessenger != null) {
                    Message message = Message.obtain();
                    Bundle bundle = new Bundle();
                    bundle.putString("hello", "hello, service!");
                    message.setData(bundle);
                    message.what = 1;//用作标识,根据服务端定义,用来区分要做什么事情,下同
                    try {
                        serviceMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Log.d(TAG, "请先绑定服务");
                }
            }
        });

        //新增
        //这里做了个示例,让客户端发送消息给服务端,服务端做加法运算后将结果返回给客户端
        btn_doplus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (serviceMessenger != null) {
                    Message message = Message.obtain();
                    message.what = 2;
                    message.arg1 = 1;
                    message.arg2 = 2;
                    
                    //注意这里,这里需要将客户端的Messenger存在消息中,在服务端将结果返回给客户端的时候要用到。
                    message.replyTo = clientMessenger;
                    try {
                        serviceMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Log.d(TAG, "请先绑定服务");
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (serviceMessenger != null) {
            serviceMessenger = null;
            unbindService(conn);
            Log.d(TAG, "解除绑定");
        }
    }
}

接下来我们来看一下服务端代码有哪些改动

服务端

public class MessengerService extends Service {

    private static final String TAG = "MessengerService";

    private Messenger messenger = new Messenger(new MessengerServiceHandler());

    public MessengerService() {
    }

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

    public static class MessengerServiceHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Log.d(TAG, "MessengerService receive a message:" + msg.getData().getString("hello"));
                    break;

                //新增
                //这里就是加法运算了
                case 2:
                    int a = msg.arg1;
                    int b = msg.arg2;
                    int result = a + b;

                    //做完加法运算后,从消息缓存池中拿到一个将要发往客户端的Message对象
                    Message message = Message.obtain();
                    message.what = 1; //这里根据你自己的业务逻辑定义好就可以,用于在接收端收到信息后方便进行分类处理
                    Bundle bundle = new Bundle();
                    bundle.putInt("result", result);
                    message.setData(bundle);

                    //从消息中拿出客户端的信使
                    Messenger clientMessenger = msg.replyTo;

                    try {
                        //通过客户端的信使将消息发给客户端
                        clientMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }

                    break;

                default:
                    break;
            }
        }
    }
}

再次启动两个应用程序,在客户端分别点击绑定,打招呼,做加法运算,解除绑定。

客户端Log如下:

android samba 安装 android samba server_服务端_03

服务端Log如下:

android samba 安装 android samba server_service_04

好了,愉快地玩耍吧!