Android中的IPC方式(一)-Messenger

本章重点
IPC主要有以下方式:
1. Intent中附加extras来传递消息
2. 共享文件
3. Binder方式
4. 四大组件之一的ContentProvider
5. Sock

1 使用Bundle
四大组件中的三大组件(Activity、Service、Receiver)都支持在Intent中传递 Bundle 数据。
Bundle实现了Parcelable接口,**当我们在一个进程中启动了另一个进程的Activity、Service、Receiver,可以再Bundle中附加我们需要传输给远程进程的消息并通过Intent发送出去。被传输的数据必须能够被序
2 使用文件
一些概念:
1. 两个进程通过读写同一个文件来交换数据。还可以通过 ObjectOutputStream / ObjectInputStream 序列化一个对象到文件中,或者在另一个进程从文件中反序列这个对象。注意:反序列化得到的对象只是内容上和序列化之前的对象一样,本质是两个对象。
2. 文件并发读写会导致读出的对象可能不是最新的,并发写的话那就更严重了(书本原文,个人理解这里的严重应该是指数据丢失。)。所以文件共享方式适合对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写问题。
3. SharedPreferences 底层实现采用XML文件来存储键值对。系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences 文件的缓存,因此在多进程模式下,系统对它的读/写变得不可靠,面对高并发读/写时 SharedPreferences 有很大几率丢失数据,因此不建议在IPC中使用 SharedPreferences 。
3 使用Messenger
Messenger可以在不同进程间传递Message对象。是一种轻量级的IPC方案,底层实现是AIDL。
具体使用时,分为服务端和客户端:
1. 服务端:创建一个Service来处理客户端请求,同时创建一个Handler并通过它来创建一个Messenger,然后再Service的onBind中返回Messenger对象底层的Binder即可。
private final Messenger mMessenger = new Messenger (new xxxHandler()
2. 客户端:绑定服务端的Sevice,利用服务端返回的IBinder对象来创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个Handler并通过它来创建一个Messenger(和服务端一样),并通过 Message 的 replyTo 参数传递给服务端。服务端通过Message的 replyTo 参数就可以回应客户端了。
3. 总而言之,就是客户端和服务端 拿到对方的Messenger来发送 Message 。只不过客户端通过 bindService 而服务端通过 message.replyTo 来获得对方的Messenger。
4. Messenger中有一个 Hanlder 以串行的方式处理队列中的消息。不存在并发执行,我们不用考虑线程同步的问题。

在上节中,我们介绍了IPC的几个基础知识:序列化和Binder,本节开始详细分析各种跨进程通信方式。具体方式有很多,比如可以通过在Intent中附加extras来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,ContentProvider天生就是支持跨进程访问的,因此我们也可以采用它来进行IPC。此外,通过网络通信也是可以实现数据传递的,所以Socket也可以实现IPC。上述所说的各种方法都能实现IPC,它们在使用方法和侧重点上都有很大的区别,下面会一一进行展开。

1 使用Bundle

我们知道,四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity、Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。当然,我们传输的数据必须能够被序列化,比如基本类型、实现了Parcellable接口的对象、实现了Serializable接口的对象以及一些Android支持的特殊对象,具体内容可以看Bundle这个类,就可以看到所有它支持的类型。Bundle不支持的类型我们无法通过它在进程间传递数据,这个很简单,就不再详细介绍了。这是一种最简单的进程间通信方式,除了直接传递数据这种典型的使用场景,它还有一种特殊的使用场景。
比如A进程正在进行一个计算,计算完成后它要启动B进程的一个组件并把计算结果传递给B进程,可是遗憾的是这个计算结果不支持放入Bundle中,因此无法通过Intent来传输,这个时候如果我们用其他IPC方式就会略显复杂。可以考虑如下方式:我们通过Intent启动进程B的一个Service组件(比如IntentService),让Service在后台进行计算,计算完毕后再启动B进程中真正要启动的目标组件,由于Service也运行在B进程中,所以目标组件就可以直接获取
计算结果,这样一来就轻松解决了跨进程的问题。这种方式的核心思想在于将原本需要在A进程的计算任务转移到B进程的后台Service中去执行,这样就成功地避免了进程间通信问题,而且只用了很小的代价。

2 使用文件共享

共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。我们知道,在Windows上,一个文件如果被加了排斥锁将会导致其他线程无法对其进行访问,包括读和写,而由于Android系统基于Linux,使得其并发读/写文件可以没有限制地进行,甚至两个线程同时对同一个文件进行写操作都是允许的,尽管这可能出问题。通过文件交换数据很好使用,除了可以交换一些文本信息外,我们还可以序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象,下面就展示这种使用方法。
还是本章刚开始的那个例子,这次我们在MainActivity的onResume中序列化一个User对象到sd卡上的一个文件里,然后在SecondActivity的onResume中去反序列化,我们期望在SecondActivity中能够正确地恢复User对象的值。关键代码如下:

    private void persisToFile() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                USer uSer = new USer(1,"你好啊!!!",false);
                //序列化过程
                try {
                    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
                    out.writeObject(uSer);
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                //反序列化过程

                try {
                    ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));

                    USer newUser = (USer) in.readObject();

                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

下面看一下log,很显然,在SecondActivity中成功地从文件从恢复了之前存储的User对象的内容,这里之所以说内容,是因为反序列化得到的对象只是在内容上和序列化之前的对象是一样的,但它们本质上还是两个对象。通过文件共享这种方式来共享数据对文件格式是没有具体要求的,比如可以是文本文件,也可以是XML文件,只要读/写双方约定数据格式即可。通过文件共享的方式也是有局限性的,比如并发读/写的问题,像上面的那个例子,如果并发读/写,那么我们读出的内容就有可能不是最新的,如果是并发写的话那就更严重了。因此我们要尽量避免并发写这种情况的发生或者考虑使用线程同步来限制多个线程的写操作。通过上面的分析,我们可以知道,文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
当然,SharedPreferences是个特例,众所周知,SharedPreferences是Android中提供的轻量级存储方案,它通过键值对的方式来存储数据,在底层实现上它采用XML文件来存储键值对,每个应用的SharedPreferences文件都可以在当前包所在的data目录下查看到。一般来说,它的目录位于/data/data/package name/shared_prefs目录下,其中package name表示的是当前应用的包名。从本质上来说,SharedPreferences也属于文件的一种,但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问,Sharedpreferences有很大几率会丢失数据,因此,不建议在进程间通信中使用SharedPreferences。

3 使用Messenger

Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,为什么这么说呢,我们大致看一下Messenger这个类的构造方法就明白了。下面是Messenger的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是Stub.asInterface,这种使用方法都表明它的底层是AIDL。

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

Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简便地进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。实现一个Messenger有如下几个步骤,分为服务端和客户端。
1. 服务端进程
首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。
2. 客户端进程
客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。这听起来可能还是有点抽象,不过看了下面的两个例子,读者肯定就都明白了。首先,我们来看一个简单点的例子,在这个例子中服务端无法回应客户端。
首先看服务端的代码,这是服务端的典型代码,可以看到MessengerHandler用来处理客户端发送的消息,并从消息中取出客户端发来的文本信息。而mMessenger是一个Messenger对象,它和MessengerHandler相关联,并在onBind方法中返回它里面的Binder对象,可以看出,这里Messenger的作用是将客户端发送的消息传递给MessengerHandler处理。

package com.nextvpu.myapplication.messenger;

import android.app.Service;
import android.content.Intent;
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.util.Log;

import com.nextvpu.myapplication.utils.MyConstants;

public class MessengerService extends Service {

    private static final String TAG = "MessengerService";

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_CLIENT:
Log.i(TAG,"receive msg from Client:" + msg.getData().
getString("msg"));
break;
                default:
                    super.handleMessage(msg);
            }
        }
    }


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

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    private final Messenger messenger = new Messenger(new MessengerHandler());
}

然后,注册service,让其运行在单独的进程中:

<service
            android:name=".demo.messenger.MessengerService"
            android:process=":remote" >
            <intent-filter>
                <action android:name="com.ryg.MessengerService.launch" />
            intent-filter>
        service>

接下来再看看客户端的实现,客户端的实现也比较简单,首先需要绑定远程进程的MessengerService,绑定成功后,根据服务端返回的binder对象创建Messenger对象并使用此对象向服务端发送消息。下面的代码在Bundle中向服务端发送了一句话,在上面的服务端代码中会打印出这句话。

package com.nextvpu.myapplication.messenger;

import android.app.Activity;
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.annotation.Nullable;
import android.util.Log;

import com.nextvpu.myapplication.R;
import com.nextvpu.myapplication.utils.MyConstants;

public class MessengerActivity extends Activity {
    private static final String TAG = "MessengerActivity";

    private Messenger mService;
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());

    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MyConstants.MSG_FROM_SERVICE:
                    Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString("msg","hello this is client");
            msg.setData(data);
            msg.replyTo = mGetReplyMessenger;
            try {
                mService.send(msg);
            }catch (RemoteException e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent("com.ryg.MessengerService.launch");
        intent.setPackage("com.nextvpu.myapplication");
        bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
    }

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

通过上面的例子可以看出,在Messenger中进行数据传递必须将数据放入Message中,而Messenger和Message都实现了Parcelable接口,因此可以跨进程传输。简单来说,Message中所支持的数据类型就是Messenger所支持的传输类型。实际上,通过Messenger来传输Message,Message中能使用的载体只有what、arg1、arg2、Bundle以及replyTo。
Message中的另一个字段object在同一个进程中是很实用的,但是在进程间通信的时候,在Android 2.2以前object字段不支持跨进程传输,即便是2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的,读者可以试一下。非系统的Parcelable对象的确无法通过object字段来传输,这也导致了object字段的实用性大大降低,所幸我们还有Bundle,Bundle中可以支持大量的数据类型。上面的例子演示了如何在服务端接收客户端中发送的消息,但是有时候我们还需要能回应客户端,下面就介绍如何实现这种效果。还是采用上面的例子,但是稍微做一下修改,每当客户端发来一条消息,服务端就会自动回复一条“嗯,你的消息我已经收到,稍后会回复你。”,这很类似邮箱的自动回复功能。
首先看服务端的修改,服务端只需要修改MessengerHandler,当收到消息后,会立即回复一条消息给客户端。

 private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_CLIENT:
                    Log.e("xyz", "receive msg from Client:" + msg.getData().getString("msg"));
                    Messenger client = msg.replyTo;
                    Message replyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");
                    replyMessage.setData(bundle);
                    try {
                        client.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

接着再看客户端的修改,为了接收服务端的回复,客户端也需要准备一个接收消息的Messenger和Handler,如下所示。

 private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());

    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MyConstants.MSG_FROM_SERVICE:
                    Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

除了上述修改,还有很关键的一点,当客户端发送消息的时候,需要把接收服务端回复的Messenger通过Message的replyTo参数传递给服务端,如下所示。

  private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString("msg","hello this is client");
            msg.setData(data);
            //注意下面这句
            msg.replyTo = mGetReplyMessenger;
            try {
                mService.send(msg);
            }catch (RemoteException e){
                e.printStackTrace();
            }
        }

通过上述修改,我们再运行程序,然后看一下log,很显然,客户端收到了服务端的回复“嗯,你的消息我已经收到,稍后会回复你。”,这说明我们的功能已经完成。
Log如下:

09-05 15:32:48.559 22748-22748/com.nextvpu.myapplication:remote E/xyz: receive msg from Client:hello this is client
09-05 15:32:48.559 22727-22727/com.nextvpu.myapplication I/MessengerActivity: receive msg from Service:嗯,你的消息我已经收到,稍后会回复你。

到这里,我们已经把采用Messenger进行进程间通信的方法都介绍完了,读者可以试着通过Messenger来实现更复杂的跨进程通信功能。下面给出一张Messenger的工作原理图以方便读者更好地理解Messenger,如下图所示:
Android中的IPC方式(一)-Messenger_第1张图片
关于进程间通信,可能有的读者会觉得笔者提供的示例都是针对同一个应用的,有没有针对不同应用的?是这样的,之所以选择在同一个应用内进行进程间通信,是因为操作起来比较方便,但是效果和在两个应用间进行进程间通信是一样的。在本章刚开始就说过,同一个应用的不同组件,如果它们运行在不同进程中,那么和它们分别属于两个应用没有本质区别,关于这点需要深刻理解,因为这是理解进程间通信的基础。
还有一个小细节需要注意了:
android 5.0以上不支持隐式启动服务,有常用的两种解决办法。

Intent intent = new Intent("com.ryg.MessengerService.launch");
        intent.setPackage("com.nextvpu.myapplication");//应用包名
        bindService(intent,mConnection, ntext.BIND_AUTO_CREATE);
 <service
            android:name="com.nextvpu.myapplication.MessengerService"
            android:process=":remote" >
            <intent-filter>
                <action android:name="com.ryg.MessengerService.launch" />
            intent-filter>
        service>

DEMO下载地址

你可能感兴趣的:(android开发艺术探索)