进程间通信(IPC) 相关

通过引入相关问题来进行记录思考:

1.简述 IPC?

IPC 就是指进程之间的通信机制,在 Android 系统中启动 Activity/Service 等都涉及跨进程调用的过程。

2.Android中的IPC 方式有哪些:

Bundle,文件共享,Messenger,AIDL,ContentProvider,Socket等

3.如何开启Android中的多进程:

  • (常规)在AndroidMenifest中给四大组件指定属性android:process
  • (不常规)通过JNI在native层fork一个新的进程。

进程名的默认规则:

默认进程:

  • 没有指定该属性则运行在默认进程,其进程名就是包名。

以“:”开头的进程:

  • 省略包名,如android:process=":remote",表示进程名为com.example.myapplication:remote
  • 属于当前应用的私有进程,其他进程的组件不能跟它跑在同一进程中。

完整命名的进程:

  • android:process="com.example.myapplication.remote"
  • 属于全局进程,其他应用可以通过**ShareUID**方式和它跑在用一个进程中。
  • UID: Android系统会为每个应用分配一个唯一的UID,具有相同的UID才能共享数据。

    两个应用通过ShareUID跑在同一个进程中的条件:具有相同的ShareUID和签名。

  • 满足上述条件的两个应用,无论是否跑在同一进程,它们可共享data目录,组件信息。
  • 若跑在同一进程,它们除了可共享data目录、组件信息,还可共享内存数据。它们就像是一个应用的两个部分。

4.多进程的运行机制会导致哪些问题?

Android为每一个进程分配了一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这也导致了不同虚拟机中访问同一个对象会产生多份副本。

一般来说使用多进程会带来以下四个方面的问题:

  • 静态变量和单例模式失效 原因:不同虚拟机没有存储在同一个空间上;

  • 线程同步机制失效 原因:线程处于不同的进程,无法同步

  • SharedPreference的可靠性下降 原因:底层是通过读写XML文件实现的,发生并发问题。

  • Application多次创建- 原因:Android系统会为新的进程分配独立虚拟机,相当于应用重新启动了一次。

 5.为什么要设计 Binder?

在Linux中,进程通信的方式肯定不止Binder这一种,还有以下这些:

管道(Pipe)
信号(Signal)
消息队列(Message)
共享内存(Share Memory)
套接字(Socket)

Binder在这之后主要有以下优点:
效率上 :Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。Binder 只需要一次数据拷贝,性能上仅次于共享内存。
稳定性:Binder 基于 C|S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,稳定性好。共享内存虽然无需拷贝,但是控制负责,难以使用。从稳定性的角度讲,Binder 机制是优于内存共享的。
安全性:Binder 通过在内核层为客户端添加身份标志 UID|PID,来作为身份校验的标志,保障了通信的安全性。 传统 IPC 访问接入点是开放的,无法建立私有通道。比如,命名管道的名称,SystemV 的键值,Socket 的 ip 地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,无法阻止恶意程序通过猜测接收方地址获得连接。

注:熟悉Zygote的朋友可能知道,在fork()进程的时候,也就是向Zygote进程发出创建进程的消息的时候,用到的进程间通信方式就不是Binder了,而换成了Socket,这主要是因为fork不允许存在多线程,Binder通讯偏偏就是多线程。

6.Binder通信过程和原理

进程间通信(IPC) 相关_第1张图片

首先要明确的是客户端进程是无法直接操作服务端中的类和方法的,因为不同进程之间是不共享资源的。所以客户端这边操作的只是服务端进程的一个代理对象,也就是一个服务端的类引用,也就是Binder引用。

总体通信流程就是:

  • 客户端通过代理对象向服务器发送请求。

  • 代理对象通过Binder驱动发送到服务器进程

  • 服务器进程处理请求,并通过Binder驱动返回处理结果给代理对象

  • 代理对象将结果返回给客户端。

再看看在我们应用中常常用到的工作模型,上图:

进程间通信(IPC) 相关_第2张图片

这就是在应用层面我们常用的工作模型,通过ServiceManager去获取各种系统进程服务。这里的通信过程如下:

  • 服务端跨进程的类都要继承Binder类,所以也就是服务端对应的Binder实体。这个类并不是实际真实的远程Binder对象,而是一个Binder引用(即服务端的类引用),会在Binder驱动里还要做一次映射。

  • 客户端要调用远程对象函数时,只需把数据写入到Parcel,在调用所持有的Binder引用的transact()函数

  • transact函数执行过程中会把参数、标识符(标记远程对象及其函数)等数据放入到Client的共享内存,Binder驱动从Client的共享内存中读取数据,根据这些数据找到对应的远程进程的共享内存。

  • 然后把数据拷贝到远程进程的共享内存中,并通知远程进程执行onTransact()函数,这个函数也是属于Binder类。

  • 远程进程Binder对象执行完成后,将得到的写入自己的共享内存中,Binder驱动再将远程进程的共享内存数据拷贝到客户端的共享内存,并唤醒客户端线程。

所以通信过程中比较重要的就是这个服务端的Binder引用,通过它来找到服务端并与之完成通信。

看到这里可能有的人疑惑了,图中线程池怎么没用到啊?

  • 可以从第一张图中看出,Binder线程池位于服务端,它的主要作用就是将每个业务模块的Binder请求统一转发到远程Servie中去执行,从而避免了重复创建Service的过程。也就是服务端只有一个,但是可以处理多个不同客户端的Binder请求。

7.说下你对进程与线程的理解

一个进程就是一个执行单元,在 Android 中,一个应用默认只有一个进程,每个进程都有自己独立的资源和内存空间,其它进程不能任意访问当前进程的内存和资源,系统给每个进程分配的内存会有限制。实现的方式很简单就是在 Manifest 中注册 Activity 等的时候,使用 process 属性指定一个进程即可。process 分私有进程和全局进程,以 : 号开头的属于私有进程,其他应用组件不可以和他跑在同一个进程中;不以 : 号开头的属于全局进程,其他应用可以通过 ShareUID 的方式和他跑在同一个进程中

Android 系统启动的时候会先启动 Zygote 进程,当我们需要创建应用程序进程的时候的会通过 Socket 与之通信,Zygote 通过 fork 自身来创建我们的应用程序的进程。

线程是 CPU 调度的最小单元,一个进程可包含多个线程。Java 线程的实现是基于一对一的线程模型,即通过语言级别层面程序去间接调用系统的内核线程。内核线程由操作系统内核支持,由操作系统内核来完成线程切换,内核通过操作调度器进而对线程执行调度,并将线程的任务映射到各个处理器上。由于我们编写的多线程程序属于语言层面的,程序一般不会直接去调用内核线程,取而代之的是一种轻量级的进程(Light Weight Process),也是通常意义上的线程。由于每个轻量级进程都会映射到一个内核线程,因此我们可以通过轻量级进程调用内核线程,进而由操作系统内核将任务映射到各个处理器。这种轻量级进程与内核线程间1对1的关系就称为一对一的线程模型。

进程间通信(IPC) 相关_第3张图片

 

你可能感兴趣的:(Android)