简介

IPC方式其实有很多,比如在Intent中附加extras来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外ContentProvider天生就是支持跨进程访问的,还有网络通信,用Socket实现IPC,但它们在使用和侧重点上有很大区别,下面来详细逐个介绍一下。

Bundle

由于Bundle实现了Parcelable接口,所以可以很方便的在不同进程间传输。在四大组件中,Activity、Service、Receiver 这三大组件都支持在Intent中传递Bundle数据。
由于Bundle中需要添加数据,所以这些数据,必须是能够被序列化的数据,如基本类型、实现了Parcelable或Serializable接口的对象、以及一些Android支持的特殊对象,具体支持哪些类型,可以查看Bundle类源码。
如果某计算结果无法被序列化,导致在Bundle中无法添加,那么可以尝试用Bundle先向目标进程传递计算前的数据,在目标进程中再去计算结果。

使用文件共享

共享文件方式就是两个进程通过读写同一个文件数据,来实现进程间通信。
在Windows上,一个文件被加入了排斥锁,将会导致其它进程无法对其进行访问。但是由于Android基于Linux,使其读写可以并发操作且无限制,所以共享文件其实有操作风险。
在文件中,除了可以交换文本信息,也可以将对象序列化保存到文件中,然后在另一个进程中反序列化恢复这个对象。如下示例:
我们在MainActiivty的onResume()中调用如下方法:

private void persistToFile() {
        File file = new File("data/data/com.example.onsaveinstancestateteset/SaveFileTest.txt");
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    FileOutputStream fileStream = new FileOutputStream(file);
                    User user = new User(1, "hello word", true);
                    ObjectOutputStream outputStream = new ObjectOutputStream(fileStream);
                    outputStream.writeObject(user);
                    fileStream.close();
                    outputStream.close();

                    Log.e("MainActivity", "user:" + user.toString());
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

然后在SencondActivity中的onResume()调用如下方法:

private void recoverFromFile() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                User user = null;
                File file = new File("data/data/com.example.onsaveinstancestateteset/SaveFileTest.txt");
                if (file.exists()) {
                    try {
                        ObjectInputStream input = new ObjectInputStream(new FileInputStream(file));
                        user = (User) input.readObject();
                        Log.e("MainActivity2", "recover user:" + user.toString());
                        input.close();
                    } catch (IOException | ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

打印结果如下

2021-03-31 14:46:29.615 19511-19555/com.example.onsaveinstancestateteset E/yanjianyang: user:id = 1 userName = hello word isMale =true
2021-03-31 14:46:31.612 19511-19564/com.example.onsaveinstancestateteset E/yanjianyang: recover user:id = 1 userName = hello word isMale =true

通过文件共享这种方式来进行IPC是没有具体文件格式要求的,可以是txt,也可以是XML,只要读/写双方约定好数据格式即可。

通过文件共享这种方式也是有局限性的,比如读/写并发的问题,如果读写同时进行,读出的内容可能不是最新的了,如果同时写入,并发问题就更严重了,所以我们应该使用这种方式的同时,考虑线程同步的问题。

当然SharedPreferences是个特例,它是Android中提供的轻量级缓存方案,它通过键值对的方式来存储数据,在底层使用的是XML文件来存储,默认文件位置在/data/data/package name/shared_prefs目录下,其中package name表示的是当前的包名。系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读写变的不再可靠,并发高时很有可能丢失数据,因此,不建议在IPC中使用SharedPreferences。

使用Messenger

Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,比如它的构造方法中这样写到:

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

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

从IMessenger、Stub.asInterface()等部分都可以表明,它的底层就是AIDL。

Messenger对AIDL做了封装,我们可以很简便的使用它。同时由于它一次就处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为在服务端中不存在并发执行的情景。接下来我们讲如何实现:首先分为客户端和服务端

  • 服务端

首先我们在服务端要创建一个Service来处理客户端的请求,同时创建一个Handler,并通过它来创建一个Messenger对象,然后在Service的onBind()方法中返回这个Messenger对象底层的Binder即可。示例代码如下:

public class MessageService extends Service {
    private static final String TAG = "MessageService";

    private static class Messagehandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case 1:
                    Bundle message = msg.getData();
                    String msgData = message.getString("msg");
                    Log.i(TAG, "receive msg from Client:" + msgData);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private final Messenger mMessenger = new Messenger(new Messagehandler());

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

然后将Service注册为单独的线程中:

<service
   android:name=".MessageService"
   android:process=":remote" />
  • 客户端
    客户端先要绑定服务端Service,绑定成功后,用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发送消息的类型为Message对象。

如果需要服务端能够响应客户端,那么客户端就像服务端一样,我们也还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个参数就可以回应客户端。

下面我们创建客户端,绑定服务端的MessengerService之后,根据服务端返回的binder对象创建Messenger对象,并利用这个对象向服务器发送消息,示例代码如下:

public class MessengerActivity extends AppCompatActivity {
    private static final String TAG = "MessengerActivity";
    private Messenger mService;
    
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null, 1);
            Bundle data = new Bundle();
            data.putString("msg", "hello,this is from client.");
            msg.setData(data);
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent(this, MessageService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

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

运行之后我们得到如下log内容:

I/MessageService: receive msg from Client:hello,this is from client.

如上表示我们的服务端已经成功接收到了客户端发来的消息。

通过上面的例子我们能看出,Messenger想要进行数据传递必须通过Message,而Messenger和Message都实现了Parcelable接口,因此都可以进行跨进程传输,简单来说Message传输的类型就是Messenger传输的类型。

实际上,通过Messenger来传输Message,Message中能使用的载体只有what、arg1、arg2、Bundle以及replyTo。

Message中的另一个字段object在同一个进程中是很实用的,但在进程间通信的时候,Android2.2以前,object字段是不支持跨进程传输的,2.2之后系统才支持了Parcelable接口,才可以传输。这意味着我们自定义的Parcelable对象是无法通过object字段来传输的。由于非系统的Parcelable对象无法通过object字段来传输,导致该字段实用性大大降低,而Bundle支持大量的数据类型,所以我们的Bundle更常用一些。

上面介绍了,客户端向服务端发送一条数据,如果服务端向客户端返回一条响应数据,我们可以这么做:

首先在服务端的Handler中做如下修改:

private static class Messagehandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case 1:

                    Bundle message = msg.getData();
                    String msgData = message.getString("msg");
                    Log.i(TAG, "receive msg from Client:" + msgData);
					//如下修改
                    Messenger client = msg.replyTo;
                    Message replyMessage = Message.obtain(null, 2);
                    Bundle data = new Bundle();
                    data.putString("reply", "我收到你的消息了,稍后回复你~");
                    replyMessage.setData(data);
                    try {
                        client.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

而客户端为了接受服务端返回的响应,需要也添加一个接收消息的Handler和Messenger:

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

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

        }
    }

另外,最重要的一点,即在向服务端发送Message之前,配置replyTo参数:

private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            ...
            data.putString("msg", "hello,this is from client.");
            msg.setData(data);
            //下面这句是关键!
            msg.replyTo = mGetReplyMessenger;
            ...
        }
        ...
    };

完成如上代码后,运行效果如下:

I/MessengerActivity: receive msg from service: Bundle[{reply=我收到你的消息了,稍后回复你~}]

最后贴上一副Messenger的工作原理图,以便于更好的理解Messenger:

Android常见的 IPC 机制 android ipc方式_ide


更多Android中的IPC方式,将在下一篇中继续探讨。