IPC 简介
IPC(Inter-Process Communication)的含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。在 Android 和 Linux 中都有各自的 IPC 机制。
首先了解Liunx中的几个概念。
内核空间和用户空间 为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User space)和内核空间(Kernel space)。Linux 操作系统将最高的1GB字节供内核使用,称为内核空间,较低的3GB 字节供各进程使用,称为用户空间。
内核空间是Linux内核的运行空间,用户空间是用户程序的运行空间。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不会受到影响。内核空间的数据是可以进程间共享的,而用户空间则不可以。比如在上图进程A的用户空间是不能和进程B的用户空间共享的。
进程隔离 进程隔离指的是,一个进程不能直接操作或者访问另一个进程。也就是进程A不可以直接访问进程B的数据。
系统调用 用户空间需要访问内核空间,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。
进程A和进程B的用户空间可以通过如下系统函数和内核空间进行交互。
copy_from_user:将用户空间的数据拷贝到内核空间。
copy_to_user:将内核空间的数据拷贝到用户空间。
内存映射 由于应用程序不能直接操作设备硬件地址,所以操作系统提供了一种机制:内存映射,把设备地址映射到进程虚拟内存区。 举个例子,如果用户空间需要读取磁盘的文件,如果不采用内存映射,那么就需要在内核空间建立一个页缓存,页缓存去拷贝磁盘上的文件,然后用户空间拷贝页缓存的文件,这就需要两次拷贝。 采用内存映射,如下图所示。
由于新建了虚拟内存区域,那么磁盘文件和虚拟内存区域就可以直接映射,少了一次拷贝。
内存映射全名为Memory Map,在Linux中通过系统调用函数mmap来实现内存映射。将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间,反之亦然。内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。
Linux的IPC通信原理:
内核程序在内核空间分配内存并开辟一块内核缓存区,发送进程通过copy_from_user函数将数据拷贝到到内核空间的缓冲区中。同样的,接收进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程。这样数据发送进程和数据接收进程完成了一次数据传输,也就是一次进程间通信。
Linux的IPC通信原理有两个问题:
一次数据传递需要经历:用户空间 –> 内核缓存区 –> 用户空间,需要2次数据拷贝,这样效率不高。
接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用API接收消息头来获取消息体的大小,浪费了空间或者时间。
Linux中的IPC机制种类
Linux中提供了很多进程间通信机制,主要有管道(pipe)、信号(sinal)、信号量(semophore)、消息队列(Message)、共享内存(Share Memory)、套接字(Socket)等。
管道 :管道是Linux由Unix那里继承过来的进程间的通信机制,它是Unix早期的一个重要通信机制。管道的主要思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。这个共享文件比较特殊,它不属于文件系统并且只存在于内存中。另外还有一点,管道采用的是半双工通信方式的,数据只能在一个方向上流动。
信号 :信号是软件层次上对中断机制的一种模拟,是一种异步通信方式,进程不必通过任何操作来等待信号的到达。信号可以在用户空间进程和内核之间直接交互,内核可以利用信号来通知用户空间的进程发生了哪些系统事件。信号不适用于信息交换,比较适用于进程中断控制。
信号量 :信号量是一个计数器,用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列 :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识,并且允许一个或多个进程向它写入与读取消息。信息会复制两次,因此对于频繁或者信息量大的通信不宜使用消息队列。
共享内存 :多个进程可以直接读写的一块内存空间,是针对其他通信机制运行效率较低而设计的。 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大的提高效率。
套接字 :套接字是更为基础的进程间通信机制,与其他方式不同的是,套接字可用于不同机器之间的进程间通信。
Activity 中的多进程模式
开启多进程模式 一般在 Android 中,多进程指一个应用中存在多个进程的情况。同一个应用间的多进程,就相当于两个不同的应用采用了 SharedUID 的模式。 可通过在 AndroidMenifest 中给四大组件指定 android:process 属性,来开启多进程。(也可以通过 JNI 在 native 层去 fork 一个新的进程,但不常用)可通过 getProcessName(this) 来获取进程名。
android:process=”:remote” 简写的方式,指在当前进程名前面附加上当前的包名。 属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
android:process=”com.ryg.chapter_2.remote” 完整的命名方式,不会附加包名信息。 属于全局进程,每个应用有唯一的 UID,其他应用通过 ShareUID 方式可以和它跑在同一进程中(需要它们有相同的 ShareUID 并且签名相同)。
多进程模式的运行机制 使用多进程会造成的几个问题:
静态成员和单例模式完全失效:Android 为每一个应用(进程)分配了一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这就导致在不同虚拟机中访问同一个类对象会产生多份副本。
线程同步机制完全失效:本质上和上个问题类似,不同进程锁的不是同一个对象。
SharedPreferences 的可靠性下降:SP 不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。
Application 会多次创建:其实,就是启动一个应用的过程。不同进程的组件会拥有独立的虚拟机、Application 以及内存空间。(假设项目的 Application 为 LagouApplication,新创建 RemoteActivity 的 process 为 “lagou.process”,这将导致它会在一个新的进程中创建。当在 MainActivity 中跳转到 RemoteActivity 时,LagouApplication 会被再次创建,因此各种初始化的操作也会被执行 2 遍。)针对这个问题,目前有两种比较好的处理方式:
onCreate 方法中判断进程的名称,只有在符合要求的进程里,才执行初始化操作;
抽象出一个与 Application 生命周期同步的类,并根据不同的进程创建相应的 Application 实例。
IPC 基础概念介绍
Serializable 接口 是 Java 提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。 实现序列化,只需要类实现 Serializable 接口并声明一个 serialVersionUID 即可(这个声明不是必须的,但会对反序列化过程产生影响)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 User user = new User (0 ,"str" ,true );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(); in.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }
注意几点:
静态成员变量属于类不属于对象,所以不会参与序列化过程。
用 transient 关键字标记的成员变量不参与序列化过程。
类结构发生毁灭性改变,会反序列化失败。
如果不指定 serialVersionUID,在当前类有所改变时,那么系统会重新计算当前类的 hash 值并把它赋值给 serialVersionUID,这样不匹配时,程序会 crash。
系统默认的序列化过程也是可以改变的,但一般不需要这么做。(writeObject(),readObject())
Parcelable 接口 系统提供了许多实现了此接口的类,它们都是可以直接序列化的,比如 Intent、Bundle、Bitmap 等,同时 List 和 Map 也可以序列化,前提是它们里面的每个元素都是可序列化的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class User implements Parcelable { public int userId; public String userName; public boolean isMale; public Book book; public User (int userId,String userName,boolean isMale) { this .userId = userId; this .userName = userName; this .isMale = isMale; } protected User (Parcel in) { userId = in.readInt(); userName = in.readString(); isMale = in.readInt() == 1 ; book = in.readParcelable(Thread.currentThread().getContextClassLoader()); } public static final Creator<User> CREATOR = new Creator <User>() { @Override public User createFromParcel (Parcel in) { return new User (in); } @Override public User[] newArray(int size) { return new User [size]; } }; @Override public int describeContents () { return 0 ; } @Override public void writeToParcel (Parcel dest, int flags) { dest.writeInt(userId); dest.writeString(userName); dest.writeInt(isMale?1 :0 ); dest.writeParcelable(book,0 ); } }
二者区别 1、Serilizable 是 Java 中的序列化接口,使用简单,但开销很大,序列化和反序列化过程需要大量的 I/O 操作。 2、Parcelable 是 Android 中的序列化方式,使用麻烦,但效率很高,推荐使用。 3、Parcelable 主要用在内存序列化上,通过它将对象序列化到存储设备中或者将对象序列化后通过网络传输也都可以,但过程稍显复杂,这两种情况建议使用 Serilizable。
Android 中的 IPC 方式
Android系统是基于Linux内核的,在Linux内核基础上,又拓展出了一些IPC机制。Android系统除了支持套接字,还支持序列化、Messenger、AIDL、Bundle、文件共享、ContentProvider、Binder(除了传统的技术,是Android新开发的一种进程间通信机制,也是实现IPC的主要底层机制)等。
使用序列化 序列化指的是Serializable/Parcelable,Serializable是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。Parcelable接口是Android中的序列化方式,更适合在Android平台上使用,用起来比较麻烦,效率很高。
使用 Bundle 四大组件中的 Activity、Service、Receiver 都是支持在 Intent 中通过Bundle来进行数据传递。 Bundle 实现了 Parcelable 接口,使用简单方便,但传输的数据必须能够被序列化。
使用文件共享 两个进程通过读/写同一个文件来交换数据(数据共享,共享的文件可以是文本、XML、JOSN)。 文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
SharedPreferences 从本质上来说,也属于文件的一种,它是 Android 中提供的轻量级存储方案,通过键值对的方式存储数据,在底层实现上采用 XML 文件来存储键值对。 但由于系统对它的读/写有一定的缓存策略,所以不建议在进程间通信中使用 SP。
使用 Messenger Messenger在Android应用开发中的使用频率不高,Messenger 可以翻译为信使,通过它可以在不同进程中传递 Message 对象,在Message中加入我们想要传的数据就可以在进程间的进行数据传递了。 Message 中能使用的载体只有 what、arg1、arg2、Bundle 以及 replyTo,而 object 只支持系统提供的实现了 Parcelable 接口的对象。 Messenger 是一种轻量级的 IPC 方案,底层实现是 AIDL,并对 AIDL 做了封装,使用更简便。 同时,它一次处理一个请求,因此在服务端不用考虑线程同步的问题,因为服务端中不存在并发执行的情形。 但 Messenger 是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务器,服务器仍然只能一个一个处理,如果有大量的并发请求,用 Messenger 就不太合适了。 而且,Messenger 的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用 Messenger 就无法做到了,可以使用 AIDL。
实现 Messenger 的步骤:
服务端进程
首先,在服务端创建一个 Service 来处理客户端的连接请求。
同时创建一个 Handler 并通过它来创建一个 Messenger 对象。
然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。
客户端进程
首先,绑定服务端的 Service。
用服务端返回的 IBinder 对象创建一个 Messenger(通过它就可以向服务端发送消息了,发消息类型为 Message 对象)。
如果需要服务端能够回应客户端,还需要创建一个 Handler 并创建一个新的 Messenger,并把这个 Messenger 对象通过 Message 的 replyTo 参数传递给服务端,服务端通过这个 replyTo 参数就可以回应客户端。
使用 AIDL AIDL(Android interface definition Language),即 Android 接口定义语言。Messenger是以串行的方式来处理客户端发来的信息,如果有大量的消息发到服务端,服务端仍然一个一个的处理再响应客户端显然是不合适的。另外还有一点,Messenger用来进程间进行数据传递但是却不能满足跨进程的方法调用,这个时候就需要使用AIDL了。
AIDL 可生成在进程间通信的代码,底层由Binder机制实现。实现主要分三部分:客户端调用远程服务器,服务器提供服务,AIDL接口,参数传递。当作为客户的一方要和作为服务器的一方进行通信时,需指定一些双方都认可的接口,这样才能顺利地进行通信,AIDL就是定义这些接口的一种工具。
使用流程:
服务端
创建一个 Service 监听客户端的连接请求。
创建 一个 AIDL 文件,将暴露给客户端的接口在这个文件中声明。
在 Service 中实现这个 AIDL 接口。
客户端
绑定服务端的 Service。
将服务端返回的 Binder 对象转成 AIDL 接口所属的类型。
可以调用 AIDL 中的方法了。
AIDL 接口的创建 首先,在 AIDL 文件中,并不是所有的数据类型都是可以使用的。 AIDL 中每个实现了 Parcelable 接口的类都需要创建相应的 AIDL 文件并声明那个类为 parcelable(注意为小写)。 AIDL 中除了基本数据类型,其他类型的参数必须标上方向。 AIDL 接口中只支持方法,不支持声明静态常量,这一点有别于传统的接口。
使用 ContentProvider ContentProvider为存储和获取数据了提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的。ContentProvider底层实现也是Binder,但是使用起来比AIDL要容易许多。系统中很多操作都采用了ContentProvider,例如通讯录,音视频等,这些操作本身就是跨进程进行通信。
它是 Android 中提供的专门用于不同应用间进行数据共享的方式。要跨进程访问信息,需使用它的 query、update、insert 和 delete 方法。
使用 Socket Socket 也称为”套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应网络的传输控制层中的 TCP 和 UDP 协议。 简易的聊天室程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 @SuppressLint("Registered") public class TCPServerService extends Service { private boolean mIsServiceDestoryed = false ; private String[] mDefinedMessages = new String []{"111" ,"222" ,"333" ,"444" ,"555" }; @Override public void onCreate () { new Thread (new TcpServer ()).start(); super .onCreate(); } @Nullable @Override public IBinder onBind (Intent intent) { return null ; } @Override public void onDestroy () { mIsServiceDestoryed = true ; super .onDestroy(); } private class TcpServer implements Runnable { @Override public void run () { ServerSocket serverSocket = null ; try { serverSocket = new ServerSocket (8688 ); } catch (IOException e) { System.err.println("establish tcp server failed,port: 8688" ); e.printStackTrace(); return ; } while (!mIsServiceDestoryed){ try { final Socket client = serverSocket.accept(); System.out.println("accept" ); new Thread (){ @Override public void run () { try { responseClient(client); } catch (IOException e) { e.printStackTrace(); } } }.start(); } catch (IOException e) { e.printStackTrace(); } } } } private void responseClient (Socket client) throws IOException{ BufferedReader in = new BufferedReader (new InputStreamReader (client.getInputStream())); PrintWriter out = new PrintWriter (new BufferedWriter (new OutputStreamWriter (client.getOutputStream())),true ); out.println("欢迎来到聊天室" ); while (!mIsServiceDestoryed){ String str = in.readLine(); System.out.println("msg from client:" +str); if (str == null ){ break ; } int i = new Random ().nextInt(mDefinedMessages.length); String msg = mDefinedMessages[i]; out.println(msg); System.out.println("send:" +msg); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 public class MainActivity extends AppCompatActivity implements View .OnClickListener { private static final int MESSAGE_RECEIVE_NEW_MSG = 1 ; private static final int MESSAGE_SOCKET_CONNECTED = 2 ; private Button mSendButton; private TextView mMessageTextView; private EditText mMessageEditText; private PrintWriter mPrintWriter; private Socket mClientSocket; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler (){ @Override public void handleMessage (Message msg) { switch (msg.what){ case MESSAGE_RECEIVE_NEW_MSG: mMessageTextView.setText(mMessageTextView.getText()+(String)msg.obj); break ; case MESSAGE_SOCKET_CONNECTED: mSendButton.setEnabled(true ); break ; default : break ; } } }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSendButton = findViewById(R.id.btn_send); mMessageTextView = findViewById(R.id.tv_message); mMessageEditText = findViewById(R.id.et_message); mSendButton.setOnClickListener(this ); Intent server = new Intent (this ,TCPServerService.class); startService(server); new Thread (){ @Override public void run () { connectTCPServer(); } }.start(); } @Override protected void onDestroy () { if (mClientSocket != null ){ try { mClientSocket.shutdownInput(); mClientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } super .onDestroy(); } @Override public void onClick (View v) { if (v == mSendButton){ new Thread (){ @Override public void run () { final String msg = mMessageEditText.getText().toString(); if (!TextUtils.isEmpty(msg) && mPrintWriter != null ){ mPrintWriter.println(msg); mMessageEditText.setText("" ); String time = formatDateTime(System.currentTimeMillis()); final String showedMsg = "self" +time+":" +msg+"\n" ; mMessageTextView.setText(mMessageTextView.getText()+showedMsg); } } }.start(); } } private String formatDateTime (long time) { return new SimpleDateFormat ("(HH:mm:ss)" ).format(new Date (time)); } private void connectTCPServer () { Socket socket = null ; while (socket == null ){ try { socket = new Socket ("localhost" ,8688 ); mClientSocket = socket; mPrintWriter = new PrintWriter (new BufferedWriter (new OutputStreamWriter (socket.getOutputStream())),true ); mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED); System.out.println("connect server success" ); } catch (IOException e) { SystemClock.sleep(3000 ); System.out.println("connect tcp server failed,retry..." ); } } try { BufferedReader br = new BufferedReader (new InputStreamReader (socket.getInputStream())); while (!MainActivity.this .isFinishing()){ String msg = br.readLine(); System.out.println("receive: " +msg); if (msg != null ) { String time = formatDateTime(System.currentTimeMillis()); final String showedMsg = "server " +time+":" +msg+"\n" ; mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showedMsg).sendToTarget(); } } System.out.println("quit..." ); if (mPrintWriter != null ) mPrintWriter.close(); br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout android:orientation ="vertical" 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" tools:context ="com.example.myapplication.MainActivity" android:background ="@android:color/darker_gray" > <TextView android:id ="@+id/tv_message" android:layout_width ="match_parent" android:layout_height ="0dp" android:layout_weight ="1" android:text ="" /> <LinearLayout android:orientation ="horizontal" android:layout_width ="match_parent" android:layout_height ="40dp" > <EditText android:layout_width ="0dp" android:layout_weight ="1" android:layout_height ="match_parent" android:id ="@+id/et_message" /> <Button android:id ="@+id/btn_send" android:layout_width ="wrap_content" android:layout_height ="match_parent" android:text ="send" /> </LinearLayout > </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="utf-8" ?> <manifest xmlns:android ="http://schemas.android.com/apk/res/android" package ="com.example.myapplication" > <uses-permission android:name ="android.permission.INTERNET" /> <uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE" /> <application android:allowBackup ="true" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:roundIcon ="@mipmap/ic_launcher_round" android:supportsRtl ="true" android:theme ="@style/AppTheme" > <activity android:name =".MainActivity" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > <service android:name =".TCPServerService" /> </application > </manifest >
Binder 连接池
首先,AIDL 是一种最常用的进程间通信方式,是日常开发中涉及进程间通信时的首选。使用 AIDL 的大致流程为:首先创建一个 Service 和 一个 AIDL 接口,接着创建一个类继承自 AIDL 接口中的 Stub 类并实现 Stub 中的抽象方法,在 Service 的 onBind() 中返回这个类的对象,然后客户端就可以绑定服务端 Service,建立连接后就可以访问远程服务端的方法了。
但是当项目变得庞大之后,如果有很多的业务模块都需要用到 AIDL,那就需要先创建相应多的 Service,而 Service 是四大组件之一,本身是一种系统资源,我们不能无限制地增加 Service。而且太多的 Service 会使得我们的应用看起来很重量级,因为正在运行的 Service 可以在应用详情页看到。针对这些问题,我们需要减少 Service 的数量,将所有的 AIDL 放在同一个 Service 中去管理。
在这种模式下,整个工作机制是这样的:
每个业务模块创建自己的 AIDL 接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的 Binder 对象;
对于服务端来说,只需要一个 Service 就可以了,服务端提供一个 queryBinder 接口,这个接口能够根据业务模块的特征来返回相应的 Binder 对象给它们,不同的业务模块拿到所需的 Binder 对象后就可以进行远程方法调用了。
由此可见,Binder 连接池的主要作用就是将每个业务模块的 Binder 请求统一转发到远程 Service 中去执行,从而避免了重复创建 Service 的过程。
选择合适的 IPC 方式
名称
优点
缺点
适用场景
Bundle
简单易用
只能传输 Bundle 支持的数据类型
四大组件间的进程间通信
文件共享
简单易用
不适合高并发场景,并且无法做到进程间的即时通信
无并发访问情形,交换简单的数据实时性不高的场景
AIDL
功能强大,支持一对多并发通信,支持实时通信
使用稍复杂,需要处理好线程同步
一对多通信且有 RPC 需求
Messenger
功能一般,支持一对多串行通信,支持实时通信
不能很好处理高并发情形,不支持 RPC,数据通过 Message 进行传输,因此只能传输 Bundle 支持的数据类型
低并发的一对多即时通信,无 RPC 需求,或者无需要返回结果的 RPC 需求
ContentProvider
在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作
可理解为受约束的 AIDL,主要提供数据源的 CRUD 操作
一对多的进程间的数据共享
Socket
功能强大,可通过网络传输字节流,支持一对多并发实时通信
实现细节稍微有点繁琐,不支持直接的 RPC
网络数据交换
备注
参考资料: Android 开发艺术探索
http://liuwangshu.cn/tags/Binder%E5%8E%9F%E7%90%86/