11月更文挑战|Android基础-IPC通信方式

1,843 阅读7分钟

这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战

IPC Inter-Process Communication

进程间通信或者跨进程通信,指两个进程之间数据交换过程。

IPC方式

Bundle

Intent中可以使用Bundle进行数据传递。例如支持序列化SerializableParcellable的对象以及Bundle支持的基本类型都能实现跨进程通信。另外在一般情况下Bundle是附加在Intent中进行传递,所以在四大组件中的三大组件(ActivityServiceReceiver)使用广泛。

文件共享

文件共享也是另外一种进程间通信方式。两个进程间通过读写同一个文件来实现数据共享。例如A进程对文件进行写操作,B进程对文件进行读操作,从而实现两个进程间的数据交换。但文件共享存在一个致命问题,当两个进程对同一个文件进行并发操作时可能会存在数据不同步的情况。所以在并发情况下文件共享会造成获取的数据非最新状态,从而影响跨进程通信。因此当用文件共享作为进程通信方式需要格外注意并发的情况。同样的Sharepreferences同样不适合在高并发读写访问中使用,每份内存中都缓存一份Sharepreferencse文件。

Messenger

Messenger作为信使,可以在不同进程中传递Message对象。Messenger是一种轻量级IPC方案,底层实现AIDL。从Messenger的构造函数可以看出端倪,无论是IMessenger.Stub.asInterface还是target.getIMessenger,都能看到AIDL的影子。

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

其实Messenger是对AIDL的封装,从而能够让开发者更方便简单的使用进程间通信。由于它只支持串行处理模式,所以不用考虑线程同步问题,因此也不存在并发问题。

  • 服务端代码

创建服务端Service服务,新建Messenger对象,创建MessengerHandler接收客户端发送的消息,Service绑定MessengerIBinder。这里的IBinderHandlerIMessenger.asBinder()对象。另外客户端发送的msg.replyTo保存是客户端的Messenger对象,通过replyTo可以实现服务端向客户端进行通信,具体再看下面的客户端代码。

<service
    android:name=".MessengerExercise.MessengerService"
    android:process=":messenger"
    />
public class MessengerService extends Service{
    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0:
                    LogUtils.printI(getClass().getName(),"服务端收到消息 receive msg from Client:" + msg.getData().getString("msg"));
                    //回复客户端消息
                    Messenger client = msg.replyTo;
                    Message relpyMessage = Message.obtain(null,0);
                    Bundle bundle = new Bundle();
                    bundle.putString("msg","Service Msg 服务端回值,我收到了谢谢");
                    relpyMessage.setData(bundle);
                    try{
                        client.send(relpyMessage);
                    }catch (RemoteException e){
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    private final Messenger messenger = new Messenger(new MessengerHandler());
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}
  • 客户端代码

首先创建ServiceConnection,在onServiceConnected方法中实现客户端向服务端发送Message,通过在Message中附加Bundle跨进程传递数据,并把MessagereplyTo赋值为客户端接收Messenger对象,创建意图IntentbindService绑定跨进程服务传递数据。这里需要注意的是Message中有个Object对象它并不支持跨进程传递数据,它支持系统默认的Parceable对象而不支持自定义Parceable,好在Message可以附加Bundle。其次再看客户端Messenger对象,其实和服务端Messenger实现方式相同,新建Messenger对象,创建MessengerHandler接收服务端发送的message消息。

public class MessengerClientActivity extends BaseToolBarTitleActivity {
    private Messenger messenger;
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    LogUtils.printI(getClass().getName(), "客户端消息收到消息 receviced msg from MessegerService: " + msg.getData().getString("msg"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            messenger = new Messenger(service);
            Message message = Message.obtain(null, 0);
            Bundle  bundle  = new Bundle();
            bundle.putString("msg", "Client Msg 客户端消息");
            message.setData(bundle);
            message.replyTo = mGetReplyMessenger;
            try {
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    .....省略部分无关联关系代码
   
    @Override
    protected void initDate() {
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }

以上就是Messenger的跨进程通信方式,其实就是客户端和服务端这两端各自存在一个Messenger用于接收或者发送跨进程消息,并在Handler中接收相应消息。Messenger算是简易的串行跨进程通信,若需要实现大量并发请求通信时,Messenger可能就不太合适了,以及有的时候可能需要我们去跨进程调用服务端的方法,Messenger也无法做到。前面提到过Messenger的底层实现方式就是AIDL,它是对AIDL的封装。若想实现更多跨进程方法,我们就需要理解和开发底层AIDL了。

AIDL

在介绍AIDL使用之前先了解AIDL文件所支持的数据类型:

  • 基本数据类型(int、long、char、boolean、double等)
  • String和CharSequence
  • List:只支持ArrayList
  • Map: 只支持HashMap
  • Parcelable
  • 所有AIDL接口本身

比起Messenger跨进程通信AIDL其实稍微复杂和自定义一些,包括AIDL文件需要自己编写接口以及服务绑定过程中一些操作需要自行设置。

  1. 编写AIDL文件 Android项目创建aidl文件夹在aidl文件夹下编写aidl文件,需要注意的是编写的aidl文件还需要编译后才能引用,所以在Android Studio平台下rebuild project后才能在项目中拿到编译后的文件加以引用。

Book.java

public class Book implements Parcelable {
    public int bookId;
    public String bookName;
    public Book() {}
    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }
    ......
}

Book.aidl

parcelable Book;

IBookManager.aidl

import com.julyyu.arsenal.beta.Book;
import com.julyyu.arsenal.beta.IOnNewBookArrivedListener;
interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
     void registerListener(IOnNewBookArrivedListener listener);
     void unregisterListener(IOnNewBookArrivedListener listener);
}

IOnNewBookArrivedListener.aidl

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}
  1. 编写服务端代码 和之前一样继承Service创建服务,不同的是IBinder使用的是如下IBookManage.Stub()对象。该对象是由我们编写的aidl文件编译后生成的。实例化对象中实现具体接口方法,在此不做详情介绍。
private Binder mBinder = new IBookManager.Stub() {
    ......
    //具体实现IBookMananger接口方法以及权限判断等操作。
}
  1. 编写客户端代码 与之前Messenger创建服务类似,创建Intent执行bindService绑定ServiceConnection。从绑定的IBinder中获取IBookManager接口实现从服务端拉取相应的数据。另外也能实现注册服务端监听器,从而实现在服务端特定情况下主动向客户端发送数据。所有的操作由开发者自行设置实现,类比Messenger是不是觉得AIDL自由度更高一些。
public void onServiceConnected(ComponentName className, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;
            try {
                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
                List<Book> list = bookManager.getBookList();
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

由于AIDL篇幅太大,多数代码就不一一展示。具体内容参考开发艺术探索的讲义代码

ContentProvider

它是Android提供的专门用于不同应用间进行数据共享的方式,适合进程间通信。同理MessengerContentProvider底层也是Binder,也是对底层做了封装方便开发者使用,无需关心底层细节实现IPC。除了系统预置了许多ContentProvider,我们也能自定义ContentProvider供自己使用,其操作方式与SQLite类似,例如CRUD常规操作。这里也不做详细介绍,可以自行参考代码实践。

Socket

Socket也是实现进程间通信的方式之一。Socket俗称套接字,分为流式套接字(TCP)和用户数据报套接字(UDP)。TCP面向连接的协议,提供稳定双向通信功能.UDP是无连接的,提供不稳定的单向通信功能。 Socket具体实现进程间的通信代码如下参考

总结

名称优点缺点使用场景
Bundle简单易用只能传输Bundle支持的数据类型四大组件间的进程间通信
文件共享简单易用不适合高并发场景,并且无法做到进程间的即时通无法并发访问情形, 交换简单的数据实时性不高的场景
AIDL功能强大使用稍复杂,需要处理好线程同步一对多通信且有RPC需求
ContentProvider在数据源访问方面功能强大,支持一对多并发数据共享可以理解为受约束的AIDL,主要提供数据源的CRUD操作一对多的进程间的数据共享
Messenger功能一般, 支持一对多串行通信,支持实时通信不能很好处理高并发,不支持RPC,数据通过Message进行传输, 因此只能传输Bundle支持的数据类型低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求
Socket功能强大,可以通过网络传输字节流,支持一对多并发实时通信实现细节稍微有点繁琐,不支持直接的RPC网络数据交换

参考