安卓进程间通信概述

管道:

管道允许在进程之间按先进先出的方式传送数据,是进程间通信的一种常见方式。

管道是Linux 支持的最初Unix IPC形式之一,具有以下特点:

1) 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

2) 匿名管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);

3) 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

管道分为pipe(无名管道)和fifo(命名管道)两种,除了建立、打开、删除的方式不同外,这两种管道几乎是一样的。他们都是通过内核缓冲区实现数据传输。

  • pipe用于相关进程之间的通信,例如父进程和子进程,它通过pipe()系统调用来创建并打开,当最后一个使用它的进程关闭对他的引用时,pipe将自动撤销。
  • FIFO即命名管道,在磁盘上有对应的节点,但没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。

管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。

无名管道:

pipe的例子:父进程创建管道,并在管道中写入数据,而子进程从管道读出数据

命名管道:

和无名管道的主要区别在于,命名管道有一个名字,命名管道的名字对应于一个磁盘索引节点,有了这个文件名,任何进程有相应的权限都可以对它进行访问。

而无名管道却不同,进程只能访问自己或祖先创建的管道,而不能访任意访问已经存在的管道——因为没有名字。

Linux中通过系统调用mknod()或makefifo()来创建一个命名管道。最简单的方式是通过直接使用shell

mkfifo myfifo

 等价于

mknod myfifo p

以上命令在当前目录下创建了一个名为myfifo的命名管道。用ls -p命令查看文件的类型时,可以看到命名管道对应的文件名后有一条竖线"|",表示该文件不是普通文件而是命名管道。

使用open()函数通过文件名可以打开已经创建的命名管道,而无名管道不能由open来打开。当一个命名管道不再被任何进程打开时,它没有消失,还可以再次被打开,就像打开一个磁盘文件一样。

可以用删除普通文件的方法将其删除,实际删除的事磁盘上对应的节点信息。

例子:用命名管道实现聊天程序,一个张三端,一个李四端。两个程序都建立两个命名管道,fifo1,fifo2,张三写fifo1,李四读fifo1;李四写fifo2,张三读fifo2。

用select把,管道描述符和stdin假如集合,用select进行阻塞,如果有i/o的时候唤醒进程。(粉红色部分为select部分,黄色部分为命名管道部分)

 

在linux系统中,除了用pipe系统调用建立管道外,还可以使用C函数库中管道函数popen函数来建立管道,使用pclose关闭管道。

例子:设计一个程序用popen创建管道,实现 ls -l |grep main.c的功能

分析:先用popen函数创建一个读管道,调用fread函数将ls -l的结果存入buf变量,用printf函数输出内容,用pclose关闭读管道;

接着用popen函数创建一个写管道,调用fprintf函数将buf的内容写入管道,运行grep命令。

popen的函数原型:

FILE* popen(const char* command,const char* type);

参数说明:command是子进程要执行的命令,type表示管道的类型,r表示读管道,w代表写管道。如果成功返回管道文件的指针,否则返回NULL。

使用popen函数读写管道,实际上也是调用pipe函数调用建立一个管道,再调用fork函数建立子进程,接着会建立一个shell 环境,并在这个shell环境中执行参数所指定的进程。

消息队列:

消息队列,就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。

消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。

可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息。

消息队列的常用函数如下表:

进程间通过消息队列通信,主要是:创建或打开消息队列,添加消息,读取消息和控制消息队列。

例子:用函数msget创建消息队列,调用msgsnd函数,把输入的字符串添加到消息队列中,然后调用msgrcv函数,读取消息队列中的消息并打印输出,最后再调用msgctl函数,删除系统内核中的消息队列。(黄色部分是消息队列相关的关键代码,粉色部分是读取stdin的关键代码)

共享内存:

共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取错做读出,从而实现了进程间的通信。

采用共享内存进行通信的一个主要好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队里等通信方式,则需要再内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一次从共享内存到输出文件。

一般而言,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时在重新建立共享内存区域;而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件,因此,采用共享内存的通信方式效率非常高。

共享内存有两种实现方式:1、内存映射 2、共享内存机制

1、内存映射

内存映射 memory map机制使进程之间通过映射同一个普通文件实现共享内存,通过mmap()系统调用实现。普通文件被映射到进程地址空间后,进程可以

像访问普通内存一样对文件进行访问,不必再调用read/write等文件操作函数。

例子:创建子进程,父子进程通过匿名映射实现共享内存。

分析:主程序中先调用mmap映射内存,然后再调用fork函数创建进程。那么在调用fork函数之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap函数的返回地址,这样,父子进程就可以通过映射区域进行通信了。

2、UNIX System V共享内存机制

IPC的共享内存指的是把所有的共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。

和前面的mmap系统调用通过映射一个普通文件实现共享内存不同,UNIX system V共享内存是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。

例子:设计两个程序,通过unix system v共享内存机制,一个程序写入共享区域,另一个程序读取共享区域。

分析:一个程序调用fotk函数产生标准的key,接着调用shmget函数,获取共享内存区域的id,调用shmat函数,映射内存,循环计算年龄,另一个程序读取共享内存。

(fotk函数在消息队列部分已经用过了,

根据pathname指定的文件(或目录)名称,以及proj参数指定的数字,ftok函数为IPC对象生成一个唯一性的键值。)

key_t ftok(char* pathname,char proj)

信号:

信号是Linux系统中用于进程之间通信或操作的一种机制,信号可以在任何时候发送给某一进程,而无须知道该进程的状态。如果该进程并未处于执行状态,则该信号就由内核保存起来,知道该进程恢复执行并传递给他为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

Linux提供了几十种信号,分别代表着不同的意义。信号之间依靠他们的值来区分,但是通常在程序中使用信号的名字来表示一个信号。在Linux系统中,这些信号和以他们的名称命名的常量被定义在/usr/includebitssignum.h文件中。通常程序中直接包含<signal.h>就好。

信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式,信号可以在用户空间进程和内核之间直接交互。内核也可以利用信号来通知用户空间的进程来通知用户空间发生了哪些系统事件。信号事件有两个来源:

1)硬件来源,例如按下了cltr+C,通常产生中断信号sigint

2)软件来源,例如使用系统调用或者命令发出信号。最常用的发送信号的系统函数是kill,raise,setitimer,sigation,sigqueue函数。软件来源还包括一些非法运算等操作。

一旦有信号产生,用户进程对信号产生的相应有三种方式:

1)执行默认操作,linux对每种信号都规定了默认操作。

2)捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。

3)忽略信号,当不希望接收到的信号对进程的执行产生影响,而让进程继续执行时,可以忽略该信号,即不对信号进程作任何处理。

  有两个信号是应用进程无法捕捉和忽略的,即SIGKILL和SEGSTOP,这是为了使系统管理员能在任何时候中断或结束某一特定的进程。

上图表示了Linux中常见的命令

1、信号发送:

信号发送的关键使得系统知道向哪个进程发送信号以及发送什么信号。下面是信号操作中常用的函数:

例子:创建子进程,为了使子进程不在父进程发出信号前结束,子进程中使用raise函数发送sigstop信号,使自己暂停;父进程使用信号操作的kill函数,向子进程发送sigkill信号,子进程收到此信号,结束子进程。

2、信号处理

当某个信号被发送到一个正在运行的进程时,该进程即对次特定的信号注册相应的信号处理函数,以完成所需处理。设置信号处理方式的是signal函数,在程序正常结束前,在应用signal函数恢复系统对信号的

默认处理方式。

3.信号阻塞

有时候既不希望进程在接收到信号时立刻中断进程的执行,也不希望此信号完全被忽略掉,而是希望延迟一段时间再去调用信号处理函数,这个时候就需要信号阻塞来完成。

 

例子:主程序阻塞了cltr+c的sigint信号。用sigpromask将sigint假如阻塞信号集合。

RPC 即 Remote Procedure Call (远程过程调用) 是一种计算机通讯协议,它为我们定义了计算机 C 中的程序如何调用另外一台计算机 S 的程序,让程序员不需要操心底层网络协议,使得开发包括网络分布式多程序在内的应用程序更加容易。

RPC 是典型的 Client/Server 模式,由客户端对服务器发出若干请求,服务器收到后根据客户端提供的参数进行操作,然后将执行结果返回给客户端。

RPC 位于 OSI 模型中的会话层: 


在面向对象编程中,它也被叫做 “远程方法调用”。

IDL 是什么
RPC 只是一种协议,规定了通信的规则。

在实际工作中客户端与服务端会有各种各样的平台,就好像日常开发一样,为了统一处理不同的实现,需要定义一个共同的接口,于是有了 IDL。

IDL 即 Interface Description Language (接口定义语言)。

它通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流。比如,一个组件用 C++ 写成,另一个组件用 Java 写,仍然可以通信。

è¿éåå¾çæè¿°

IPC 是什么
IPC 即 Inter-Process Communication (进程间通信)。

Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。

“进程隔离”更详细的介绍(节选自:http://blog.csdn.net/u010132993/article/details/72582655):

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

Binder这个名词大家或许有些许陌生,但是在Android系统中却具有至关重要的作用,作为Android系统所特有的特征之一,无论是系统服务的调度还是跨进程通信中,处处皆可见其身影,接下来我会从四个层面逐一介绍Binder 
(简称Binder的WH2W),即:

WHAT :What Is Binder?这部分从Binder的引入以及背景来粗浅地为Binder下定义;
HOW :How To Use Binder?知道Binder的概念并不够,还要知道怎么用它,这一部分从Binding Service(绑定服务)入手,浅析Binder的应用场景,进而验证第一部分的概念;
HOW :How It Works?知道了Binder的概念和使用场景,接下来这部分就是讲述它是如何在这些场景中发挥作用的。
WHY:众所周知,Android基于Linux内核,而Linux本身具有众多跨进程通信机制,为什么非得使用Binder?这部分主要比较Binder与其他跨进程机制的区别,以说明使用Binder的必要性。
Android Binder Analysis1
本篇文章已授权微信公众号 guolin_blog 郭霖独家发布
Linux OS Foundation
MultitaskingProcesses and Threads
Process Isolation进程隔离
User Space and Kernel Space用户空间和内核空间
IPCInter-Process Communication
Android OS Foundation
Java Native Interface
Dalvik Virtual Machine
Kernel
Android Component
Component Communication
Binder Concept
Linux OS Foundation
这一部分中我们讲介绍Binder相关的一些Linux基础知识,为了便于大家更好的理解,为中英文对照部分,各位英文好的看英文,英文不好的看中文。

Multitasking,Processes and Threads
Multitasking is the ability to execute multiple instances of programs or processes at the same time. An operating system therefore creates for every binary exe-cutable le a certain memory frame with its own stack, heap, data and shared mapped libraries. It also assigns special internal management structures. This is called a process .The operating system must provide fair proportioning, because only one process can use the CPU at the same time. All processes must be interruptible. The operating system sends them to sleep or wakes them on their time slot. This work is done by a scheduler , supplying each process with an optimal time slot.A thread is a process without own address space in memory, it shares the address space with the parent process. Processes can have child threads, and a thread must be assigned to a process.

上述英文中描述了对任务,进程和线程的基本概念,学过操作系统的小伙伴对这些应该很熟悉了,我把这部分英文翻译成如下三句定义: 
- 多任务 :多任务是操作系统同时执行多个程序或进程实例的能力; 
- 进程 :操作系统为一个二进制可执行文件创建了一个载有该文件自己的栈,堆、数据映射以及共享库的内存片段,还为其分配特殊的内部管理结构。这就是一个进程。操作系统必须提供公平的比例,因为在同一时间只有一个进程在使用CPU。所有进程都是可以中断的。操作系统在流水线上唤醒或中断进程,这项工作是由调度器完成,提供每个流水线的最佳时段; 
- 线程 :线程是没有自己内存地址空间的过程,它与父进程共享内存地址空间。进程可以有多个子线程,一个线程必然拥有唯一的一个进程

Process Isolation(进程隔离)
Due to security and safety reasons, one process must not manipulate the data of another process. For this purpose an operating system must integrate a concept for process isolation. In Linux, the virtual memory mechanism achieves that by assigning each process accesses to one linear and contiguous memory space. This virtual memory space is mapped to physical memory by the operating system.Each process has its own virtual memory space, so that a process cannot manip-ulate the memory space of another process. The memory access of a process is limited to its virtual memory. Only the operating system has access to physical memory。The process isolation ensures for each process memory security, but in many cases the communication between process is wanted and needed. The operating system must provide mechanisms to approve interprocess communication。

出于安全考虑,一个进程不能操作另一个进程的数据,进而一个操作系统必须具备进程隔离这个特性。在Linux系统中,虚拟内存机制为每个进程分配了线性连续的内存空间,操作系统将这种虚拟内存空间映射到物理内存空间,每个进程有自己的虚拟内存空间,进而不能操作其他进程的内存空间,每个进程只能操作自己的虚拟内存空间,只有操作系统才有权限操作物理内存空间.进程隔离保证了每个进程的内存安全,但是在大多数情形下,不同进程间的数据通讯是不可避免的,因此操作系统必须提供跨进程通信机制。

User Space and Kernel Space(用户空间和内核空间)
Processes run normally in an unprivileged operation mode, that means they have no access to physical memory or devices. This operation mode is called in Linux user space. More abstractly, the concept of security boundaries of an operating system introduces the term ring. Note, that this must be a hardware supported feature of the platform. A certain group of rights is assigned to a ring. Intel hardware [21] supports four rings, but only two rings are used by Linux. These are ring 0 with full rights and ring 3 with least rights. Ring 1 and 2 are unused.System processes run in ring 0 and user processes in ring 3. If a process needs higher privileges, it must perform a transition from ring 3 to ring 0. The transition passes a gateway, that performs security checks on arguments. This transition is called system call and produces a certain amount of calculating overhead。

进程通常运行在一个特定的操作模式中,在这种模式下它们并没有接触物理内存或设备的权限,这种操作模式被称为Linux 用户空间。 
抽象来看,操作系统中安全边界的概念就像环路的概念一样(前提是系统支持这种特性),一个环上持有一个特定的权限组,Intel 支持四层环,但是Linux只使用了其中的两环(0号环持有全部权限,3号环持有最少权限,1号和2号环未使用),系统进程运行在0号环,用户进程运行在3号环,如果一个用户进程需要其他高级权限,其必须从3号环过渡成0号环,过渡需要通过一个安全参数检查的网关,这种过渡被称为系统调用,在执行过程中会产生一定数量的计算开销。 
对于用户空间和内核空间的环路模型,我画了如下图示方便大家理解: 

在这个环路中,用户空间要访问内核空间的唯一方式就是系统调用(System Call),系统调用指的是内核提供一系列具备预定功能的多内核函数,通过一组称为系统调用的(System Call)的接口呈现给用户。系统调用把应用程序的请求传给内核,调用相应的的内核函数完成所需的处理,将处理结果返回给应用程序.系统调用是用户程序和内核交互的接口.

IPC(Inter-Process Communication)
关于IPC我主要说明如下两点: 
- IPC是一种用于多进程间数据信号交换的框架; 
- IPC方式包括信息传递,数据同步,共享内存和远程过程调用等;

这里以一个客户端调用服务器的接口的过程来简单说明上述机制发挥作用的具体位置: 


Android OS Foundation
Java Native Interface
Four programming languages are used for system development: Assembler, C,C++ and Java. The kernel has a small amount of Assembler but is mainly written in C. Some native applications and libraries are written in C++.All other applications, especially custom apps, are written in Java. 
A distinction is made between programs compiled for the virtual machine and programs compiled to run on a specific computation platform, like Intel x86 or ARM. Programs compiled for a specific platform are called native. Because Java is executed in a virtual machine with its own byte-code, no native code can be executed directly. Due to the need to access low-level os mechanism like kernel calls, Java has to overcome this obstacle. This is done by the Java native interface (JNI) [22], which allows Java to execute compiled code from libraries written in other languages, e.g. C++. This is a trade-off between gaining capabilities of accessing the system and decreasing the level of security in Java.

在Android系统中使用了四种编程语言,汇编,C,C++和JAVA,其中Kernel部分主要由C编写,包含少量汇编,一些底层的库或者应用使用C++编写,其他的用户,尤其是用户的自定义应用,使用JAVA编写。

虚拟机编译程序和特定平台编译程序的区别在于(如英特尔x86或ARM之间的区别),为特定平台编译的程序称为native,因为是java字节码在虚拟机中执行。原生代码不可以直接执行。出于访问系统底层机制的需要,如Kernel call,java不得不使用一种机制克服这种障碍,Java Native Interface就是用来处理这个障碍,JNI赋予JAVA执行其他语言编译后代码的能力(如C++),这种机制平衡了访问系统底层机制与降低JAVA安全机制之间的矛盾。

Dalvik Virtual Machine
The Dalvik virtual machine (DVM) [5] runs the Java programmed apps. The DVM does not claim to be a Java virtual machine (JVM) due to license reasons,but fulfills the same purpose. Java 5 programs can run in that environment.The Sun JVM is stack based, because a stack machine can be run on every hardware. Hardware and platform independence were major design principles of Java. The DVM is register based for performance reasons and well adapted to ARM hardware. This is a different design principle, taking the advantage of hardware independence for high performance and less power consumption, which is essential for mobile purposes with limited battery capability. The possibility to use the Java native interface weakens the security guarantying property of Java to implicit checking the bounds of variables and to encapsulate system calls and the force to use JVM defined interfaces to the system. The use of native libraries can allow by passing the type and border checking of the virtual machine and opens the door to stack-overflow attacks. Even it is a security issue, the JNI is essential for the interprocess communication mechanism because the middleware of Binder are C++ libraries and must be accessed with JNI.

上述英文简单介绍了DVM的概念,以及DVM,JVM的区别联系,主要需要注意如下两点: 
- 在Android系统中,每个应用都运行在一个进程上,具有自己的DVM实例; 
- Dalvik基于寄存器,而JVM基于栈,在受电量限制的移动平台上,硬件独立所带来的性能及电量优化不容小视,JNI弱化了JAVA的变量安全边界检查,系统调用的封装及系统调用JVM接口方法,可以通过本地库的参数类型以及边界进行系统攻击。尽管这是一个安全隐患,但是JNI仍然是系统中不可缺失的内部通信部分,因为中间件Binder就是使用C++编写的,只能通过JNI调用;

Kernel
Android is based on a Linux 2.6 standard kernel but enhanced with new exten-sions for mobile needs. These are kernel modules Alarm,Ashmem,Binder, Powermanagement,Low Memory Killer, a kernel debugger and a logger. We will analyze the Binder driver in this work, that overs a new IPC mechanism to Linux.

Android是一个基于Linux 内核(2.6 版)开发的具备移动平台特性的操作系统,这些特性包括:定时器,匿名共享内存,粘合剂,低内存管理器,电源管理驱动,日志系统和调试系统。 
- 低内存管理器(Low Memory Killer) 比Linux的标准的OOM(Out Of Memory)机制更加灵活,它可以根据需要杀死进程以释放需要的内存。源代码位于drivers/staging/ android/lowmemorykiller.c; 
- 匿名共享内存(Ashmem) 为进程间提供大块共享内存,同时为内核提供回收和管理这个内存的机制。源代码位于mm/ashmem.c; 
- Android电源管理(PM) 一个基于标准Linux电源管理系统的轻量级Android电源管理驱动,针对嵌入式设备做了很多优化; 
- Android Alarm 提供了一个定时器,用于把设备从睡眠状态唤醒,同时它还提供了一个即使在设备睡眠时也会运行的时钟基准。源代码位于drivers/rtc/alarm.c;

Android Component

è¿éåå¾çæè¿°

如上图,Android中任一应用均是由这四大组件中的一个或多个组合而成,这些组件有可能运行在同一个进程,也有可能运行在不同进程,同一个进程通信自然很简单,但是不同进程通信就受到进程隔离的限制,因为Android系统必须提供一种行之有效的IPC方案,以用于实现组件件跨进程通信。

Component Communication

è¿éåå¾çæè¿°

这里我绘制了常见的组件交互情形,以供大家更清楚的认识到跨进程通信机制的必要性。

Binder Concept
前文中描述了IPC机制的由来以及必要性,我想大家已经深刻认识到跨进程通信机制的重要性,那么Android所特有的Binder,作为IPC机制的一种实现方式,又是什么呢?这里我引用刚哥(任玉刚)提出的基本概念: 
- 从代码角度来看,Binder是一个类,实现了IBinder接口; 
- 从来源看,Binder来自于OpenBinder,是Android IPC机制中的一种,Binder还可以理解成一个虚拟物理设备,设备驱动是dev/binder; 
- 从Framework层看,Binder是Service Manager连接各种Manager(ActivityManager,PackageManager...)和相应
Service(ActivityManagerService,PackageManagerService...)的桥梁; 
- 从客户端看,Binder是客户端服务器通讯的媒介

@(数据库系统)[Binder, AIDL, Messenger] 
前文中,我们简单介绍了Binder相关的OS 基础知识,从Linux,Android以及Component communication等角度详细阐述了跨进程通信在OS内部的必要性,随后简单粗暴地提出了Binder的概念,在本节中我们将围绕Binding Service,从Binder的应用场景入手,简单介绍其使用方法。

我们知道服务的启动方式有两种,一种是bindService(),另一种是startService(),在大多数情况下,我们请求后台服务执行某一项工作,往往需要得到其反馈。例如在播放音乐的后台服务中(Service内含MediaPlayer对象的实现方式),我们需要暂停或者继续播放音乐都需要向Service发出指令,也就意味着该播放音乐的服务与其调用者之间存在着某种通信绑定机制,使得二者之间可以相互交换信息,甚至于调用内部公开方法。对Service有所了解的朋友应该知道,以startService()方式启动的服务,就是一篇脱缰的野马,启动后则与调用者不再产生联系,直到应用某处调用stopService()方法终止服务,自然实现不了服务和调用者产生依赖,剩下的就只能是bindService()这种启动方式了,bindService()的参数原型如下:

bindService(android.content.Intent,android.content.ServiceConnection,int)
1
从上面可以看出,使用bindService()方式启动服务时,必须提供一个ServiceConnection对象,该ServiceConnection会监听与服务的连接,在连接成功后会调用ServiceConnection#onServiceConnected()方法,向客户端传递与服务通信的IBinder对象。在绑定服务中,多个调用者可同时连接一个服务,不过只有在第一个调用者建立绑定关系时,系统才会调用服务的onBind()方法,随后无需再次调用onBind()方法,直接返回第一次的IBinder对象即可,当最后一个客户端与服务取消绑定时,系统才会销毁服务(除非startService()也启动了该服务)。至于什么是IBinder对象,我们在使用过程中再做简介。 
实现绑定服务一般有如下三种方式:

Extends Binder Class:如果服务是应用内部服务,并且在与客户端相同的进程中运行,可以通过继承Binder类来实现绑定服务,在onBind()方法中返回一个该实现类的实例,客户端接收到Binder后,可利用它直接访问Binder乃至Service中的公有方法;
Messenger:如果应用服务需要跨进程工作,那么可以考虑用Messenger为服务创建接口,服务可以按照Message定义不同的Handler,Handler是Messenger的基础,Messenger可与客户端共享一个IBinder,从而使得客户端能利用Message对象向服务发送命令,此外客户端还可以定义自己的Messenger,以方便服务器回传数据。这是执行IPC的最简单方式,Messenger会在单一线程中创建所有请求队列。
AIDL:AIDL执行将对象分解成数据流的工作,操作系统可以识别这些数据流并将它们传递到各进程中,执行IPC过程,AIDL用于服务器需要多线程访问的情况,其不同于Messenger,服务一次只接收一个请求,在AIDL中,服务可以同时执行多个请求,在这种情况下,服务必须具备多线程处理能力,并采用线程安全式设计(如果你不确定你的服务器必须支持并发操作,请不要使用AIDL,尽量用Messenger或Extends Binder Class代替)。
大家一定要注意这三种方式的适用场合哦。

好了,说了这么多,我知道你们想要我JUST SHOW U THE FUCKING CODE,接下来一起进入撸码环节吧。

Android Binder Analysis2
Extends Binder Class
ServiceConnection
Binder
Extends Binder Class Code Demo
Messenger
Messenger Class
Messenger IPC Demo
Extends Binder Class
以继承Binder类实现绑定服务的过程是三种方式中最简单的一种,适用于同进程内的Service&Activty交互。接下来就让我们一起来敲个Demo吧。

ServiceConnection
在Android官网上是这样介绍ServiceConnection的:

Interface for monitoring the state of an application service.

由上述英文可以看出,ServiceConnection是用于监听应用服务的接口,其内部包含三个回调函数:

onBindingDied(ComponentName name)
onServiceConnected(ComponentName name, IBinder service)
onServiceDisconnected(ComponentName name)
其中我们一般只需要重写最后两个函数即可,这里可以看到当Service绑定操作完成时,会返回一个IBinder对象,这里的IBinder是一个接口,其中定义了远程对象所拥有的基本方法,Android官网是这样描述IBinder接口的:

Base interface for a remotable object, the core part of a lightweight remote procedure call mechanism designed for high performance when performing in-process and cross-process calls. This interface describes the abstract protocol for interacting with a remotable object. Do not implement this interface directly, instead extend from Binder.

上面这段英文的意思是,IBinder是一个远程对象所要实现的基础接口,是高性能轻量级远程过程调用机制的主要组成部分,通常用于实现进程内部或者跨进程通信。在这个接口中描述了与一个远程对象交互的抽象协议。我们采用继承Binder类的方式来进行进程通信,而不是直接实现该接口,Binder正是这个接口的实现类。 
对于Binder接口,我们需要知道如下几点:

Binder接口中的关键API是transact()函数,它的默认实现在Binder类中,这种事务型API整体上来说是一种同步操作,一次transact()调用的结束完全依赖于Binder.onTransact()是否结束,这种行为是调用进程内部对象的期望行为并且基础IPC机制也保证了在跨进程通信时其表现为同样的行为
在transact()函数中传递的数据必须是Parcel类型的,实体类必须实现Parcelable接口
系统在每一个进程中都维护了一个transact的线程池,这些线程用来执行来自于其他进程的IPC请求,例如当进程A向进程B发出一个IPC请求时,A的请求线程会阻塞在transact部分并发送IPC请求到进程B,进程B的下一个可用线程接收进程A的请求,调用onTransact函数并发送执行结果到进程A,进程A中的线程接收到返回的结果后被唤醒继续执行。实质上,其他进程就像一个你所拥有的额外线程一样,但是这种线程并不需要在自身进程创建执行
Binder系统也支持递归跨进程调用
如上描述请大家仔细理解,实质上已经简单描述了一次跨进程通信的基本过程,在后面的源码解析部分中,我们会对这四点有更加深刻清晰的理解。

Binder
对于Binder,这里就不做更多详细介绍了,其官网描述与IBinder接口描述相似。

Extends Binder Class Code Demo
现在让我们来编写一个继承Binder类实现绑定服务的demo,

编写服务 
新建一个Service类
继承Binder类,并在服务onBind()方法中返回该类对象,如下图所示(Service中编写代码):

è¿éåå¾çæè¿°

编写客户端,连接服务 
在启动服务的Activity中创建ServiceConnection类对象,并使用bindService方式启动该服务,在onServiceConnected回调中,获取LocalBinder对象,从而调用LocalBinder,乃至于Service中的共有方法,详细代码如下:

private LocalBinder mLocalBinder;
  private PlayMusicService mPlayMusicService;
  private TextView mTextViewPlay,mTextViewStop,mTextViewIsPlay;


  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTextViewPlay = (TextView)findViewById(R.id.start_music);
    mTextViewStop = (TextView)findViewById(R.id.stop_music);
    mTextViewIsPlay = (TextView)findViewById(R.id.is_music_play);
  }

  @Override
  protected void onStart() {
    super.onStart();
    if (!mBound){
      Intent intent = new Intent(this,PlayMusicService.class);
      bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    if (mBound){
      unbindService(mServiceConnection);
      mBound = false;
    }
  }

  @Override
  public void onClick(View view) {
    switch (view.getId()){
      case R.id.start_music:
        processStartMusic();
        break;
      case R.id.stop_music:
        processStopMusic();
        break;
    }
  }

  public void processStartMusic(){
    Log.e(TAG,"call Server method playMusic(),music start play!");
    if (mLocalBinder.isMusicPlay()){
      Log.e(TAG,"call Server method playMusic(),music has already play!");
    }else{
      mPlayMusicService.playMusic();
    }
  }

  public void processStopMusic(){
    Log.e(TAG,"call Server method playMusic(),music start play!");
    mPlayMusicService.stopMusic();
  }

  private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
      mBound = true;
      Log.e(TAG,"call Server method Connect server");
      mTextViewPlay.setOnClickListener(MainActivity.this);
      mTextViewStop.setOnClickListener(MainActivity.this);
      mLocalBinder = (LocalBinder) iBinder;
      mPlayMusicService = mLocalBinder.getPlayMusicService();
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
      mBound = false;
    }
  };

运行结果如下:

è¿éåå¾çæè¿°

从上述描述及编码过程中,我们可以看出继承Binder这种实现方式关键在于如何正确的定义Binder类所持有的方法或变量以满足不同的场景需求,这个Demo的大概框架如下图所示: 

è¿éåå¾çæè¿°
Messenger
Messenger这种实现方式与我们普通的Handler通信机制略有相似,具体调用过程如下图所示:

è¿éåå¾çæè¿°

在Service类中使用Handler构造了一个Messenger对象,Messenger自身持有Binder,在onBind函数中返回Messenger对象所持有的Binder,当客户端使用bindService方式启动服务后,服务启动成功回调至onServiceConnected中,从而得到服务器Messenger的Binder对象,使用该Binder对象构造客户端的Messenger,利用客户端Messenger向服务sendMessage即可完成客户端与服务的交互。 
Messenger方式是对AIDL方式的一种简化封装,其唯一的局限性在于服务中只支持单线程操作。

Messenger Class
Reference to a Handler, which others can use to send messages to it. This allows for the implementation of message-based communication across processes, by creating a Messenger pointing to a Handler in one process, and handing that Messenger to another process. 
Note: the implementation underneath is just a simple wrapper around a Binder that is used to perform the communication. This means semantically you should treat it as such: this class does not impact process lifecycle management (you must be using some higher-level component to tell the system that your process needs to continue running), the connection will break if your process goes away for any reason, etc. 
上面是Android官网对Messenger这个类的描述,该类持有一个Handler类的引用,其他类可以使用该类向Messenger发消息,通过将该Messenger指向一个同进程的Handler并暴露给其他进程实现了基于消息的跨进程通信方式。 
注意这种实现方式只是对Binder的一种封装,也就是说这种交互方式并不会影响进程的生命周期,在开发过程中,你仍然应该关注进程是否中止。

Messenger IPC Demo
在上面我们已经给出了Messenger方法实现IPC的函数调用过程,接下来我们就编写一个Demo来实践一下:

1.编写服务,并创建Handler,Messenger变量,Messenger变量由Handler构造而来,在Service的onBind方法中返回Messenger内部持有的Binder

class IncomingHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what){
        case MSG_SAY_HELLO:
          Toast.makeText(getApplicationContext(), "Hello Messenger!", Toast.LENGTH_SHORT).show();
          break;
        default:
          super.handleMessage(msg);
      }
    }
  }
  final Messenger mMessenger = new Messenger(new IncomingHandler());
  public MessengerService() {
  }

  @Override
  public IBinder onBind(Intent intent) {
    // TODO: Return the communication channel to the service.
    Toast.makeText(getApplicationContext(), "Service Binding", Toast.LENGTH_SHORT).show();
    return mMessenger.getBinder();
  }

2.MainActivity以bindService方式启动服务并在onServiceConnected回调中获取服务的Messenger#Binder,构造客户端Messenger,代码如下:

Messenger mService = null;
  boolean mBound;

  private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
      mService = new Messenger(iBinder);
      mBound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
      mService = null;
      mBound = false;
    }
  };

3.客户端Messenger发送消息到服务进行通信

findViewById(R.id.say_hello).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
          processSayHello();
        }
      });
该部分代码较简单,请大家自行运行demo查看效果。 
从上述描述和编码过程中我们可以看到Messenger方式的大概处理框架如下:

è¿éåå¾çæè¿°

下一篇我们将讲解AIDL部分Demo,包括同步AIDL和异步回调AIDL,https://blog.csdn.net/u010132993/article/details/72597148

https://blog.csdn.net/u010132993/article/details/72632458

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

在 Linux 系统中,虚拟内存机制为每个进程分配了线性连续的内存空间,操作系统将这种虚拟内存空间映射到物理内存空间,每个进程有自己的虚拟内存空间,进而不能操作其他进程的内存空间,只有操作系统才有权限操作物理内存空间。 
进程隔离保证了每个进程的内存安全。

但是在大多数情形下,不同进程间的数据通讯是不可避免的,因此操作系统必须提供跨进程通信机制。

Android 几种进程通信方式
跨进程通信要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。

然后,返回值将沿相反方向传输回来。

Android 为我们提供了以下几种进程通信机制(供开发者使用的进程通信 API)对应的文章链接如下:

文件
AIDL (基于 Binder) 
Android 进阶:进程通信之 AIDL 的使用
Android 进阶:进程通信之 AIDL 解析
Binder 
Android 进阶:进程通信之 Binder 机制浅析
Messenger (基于 Binder) 
Android 进阶:进程通信之 Messenger 使用与解析
ContentProvider (基于 Binder) 
Android 进阶:进程通信之 ContentProvider 内容提供者
Socket 
Android 进阶:进程通信之 Socket (顺便回顾 TCP UDP)
在上述通信机制的基础上,我们只需集中精力定义和实现 RPC 编程接口即可。

è¿éåå¾çæè¿°

如何选择这几种通信方式

只有允许不同应用的客户端用 IPC 方式调用远程方法,并且想要在服务中处理多线程时,才有必要使用 AIDL
如果需要调用远程方法,但不需要处理并发 IPC,就应该通过实现一个 Binder 创建接口
如果您想执行 IPC,但只是传递数据,不涉及方法调用,也不需要高并发,就使用 Messenger 来实现接口
如果需要处理一对多的进程间数据共享(主要是数据的 CRUD),就使用 ContentProvider
如果要实现一对多的并发实时通信,就使用 Socket
 

什么是 Binder?
Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。Android中的四大组件Activity,Service,Broadcast,ContentProvider,不同的App等都运行在不同的进程中,它是这些进程间通讯的桥梁。正如其名“粘合剂”一样,它把系统中各个组件粘合到了一起,是各个组件的桥梁。

理解Binder对于理解整个Android系统有着非常重要的作用,如果对Binder不了解,就很难对Android系统机制有更深入的理解。

1. Binder 架构

è¿éåå¾çæè¿°
Binder 通信采用 C/S 架构,从组件视角来说,包含 Client、 Server、 ServiceManager 以及 Binder 驱动,其中 ServiceManager 用于管理系统中的各种服务。
Binder 在 framework 层进行了封装,通过 JNI 技术调用 Native(C/C++)层的 Binder 架构。
Binder 在 Native 层以 ioctl 的方式与 Binder 驱动通讯。

Binder 机制


首先需要注册服务端,只有注册了服务端,客户端才有通讯的目标,服务端通过 ServiceManager 注册服务,注册的过程就是向 Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。

有了服务端,客户端就可以跟服务端通讯了,通讯之前需要先获取到服务,拿到服务的代理,也可以理解为引用。比如下面的代码:

//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
1
2
获取服务端的方式就是通过 ServiceManager 向 svcinfo 列表中查询一下返回服务端的代理,svcinfo 列表就是所有已注册服务的通讯录,保存了所有注册的服务信息。

有了服务端的引用我们就可以向服务端发送请求了,通过 BinderProxy 将我们的请求参数发送给 ServiceManager,通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。

怎么样是不是很简单,以上就是 Binder 机制的主要通讯方式,下面我们来看看具体实现

Binder 驱动
我们先来了解下用户空间与内核空间是怎么交互的。

è¿éåå¾çæè¿°

先了解一些概念

用户空间/内核空间
详细解释可以参考 Kernel Space Definition; 简单理解如下:

Kernel space 是 Linux 内核的运行空间,User space 是用户程序的运行空间。 为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。

Kernel space 可以执行任意命令,调用系统的一切资源; User space 只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发出指令。

系统调用/内核态/用户态
虽然从逻辑上抽离出用户空间和内核空间;但是不可避免的的是,总有那么一些用户空间需要访问内核的资源;比如应用程序访问文件,网络是很常见的事情,怎么办呢?

Kernel space can be accessed by user processes only through the use of system calls.

用户空间访问内核空间的唯一方式就是系统调用;通过这个统一入口接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。用户软件良莠不齐,要是它们乱搞把系统玩坏了怎么办?因此对于某些特权操作必须交给安全可靠的内核来执行。

当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)此时处理器处于特权级最高的(0级)内核代码中执行。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。处理器在特权等级高的时候才能执行那些特权CPU指令。

内核模块/驱动

跨进程通信是需要内核空间支持的,在Android系统中,这个运行在内核空间,负责各个用户进程通过Binder实现通信的内核模块就叫Binder驱动。Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

Binder IPC通信过程是这样的,首先Binder驱动在内核空间创建一个数据接收缓存区,接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区以及内核中数据接收缓存区和接受进程用户空间地址之间的映射关系。发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

通过系统调用,用户空间可以访问内核空间,那么如果一个用户空间想与另外一个用户空间进行通信怎么办呢?很自然想到的是让操作系统内核添加支持;传统的 Linux 通信机制,比如 Socket,管道等都是内核支持的;但是 Binder 并不是 Linux 内核的一部分,它是怎么做到访问内核空间的呢? Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)机制解决了这个问题;模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。这样,Android系统可以通过添加一个内核模块运行在内核空间,用户进程之间的通过这个模块作为桥梁,就可以完成通信了。

在 Android 系统中,这个运行在内核空间的,负责各个用户进程通过 Binder 通信的内核模块叫做 Binder 驱动;

驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作;

驱动就是操作硬件的接口,为了支持Binder通信过程,Binder 使用了一种“硬件”,因此这个模块被称之为驱动。

熟悉了上面这些概念,我们再来看下上面的图,用户空间中 binder_open(), binder_mmap(), binder_ioctl() 这些方法通过 system call 来调用内核空间 Binder 驱动中的方法。内核空间与用户空间共享内存通过 copy_from_user(), copy_to_user() 内核方法来完成用户空间与内核空间内存的数据传输。 Binder驱动中有一个全局的 binder_procs 链表保存了服务端的进程信息。

Binder 进程与线程


对于底层Binder驱动,通过 binder_procs 链表记录所有创建的 binder_proc 结构体,binder 驱动层的每一个 binder_proc 结构体都与用户空间的一个用于 binder 通信的进程一一对应,且每个进程有且只有一个 ProcessState 对象,这是通过单例模式来保证的。在每个进程中可以有很多个线程,每个线程对应一个 IPCThreadState 对象,IPCThreadState 对象也是单例模式,即一个线程对应一个 IPCThreadState 对象,在 Binder 驱动层也有与之相对应的结构,那就是 Binder_thread 结构体。在 binder_proc 结构体中通过成员变量 rb_root threads,来记录当前进程内所有的 binder_thread。

Binder 线程池:每个 Server 进程在启动时创建一个 binder 线程池,并向其中注册一个 Binder 线程;之后 Server 进程也可以向 binder 线程池注册新的线程,或者 Binder 驱动在探测到没有空闲 binder 线程时主动向 Server 进程注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 binder 线程,例如 Android 的 system_server 进程就存在16个线程。对于所有 Client 端进程的 binder 请求都是交由 Server 端进程的 binder 线程来处理的。

5. ServiceManager 启动
了解了 Binder 驱动,怎么与 Binder 驱动进行通讯呢?那就是通过 ServiceManager,好多文章称 ServiceManager 是 Binder 驱动的守护进程,大管家,其实 ServiceManager 的作用很简单就是提供了查询服务和注册服务的功能。下面我们来看一下 ServiceManager 启动的过程
è¿éåå¾çæè¿°

ServiceManager 分为 framework 层和 native 层,framework 层只是对 native 层进行了封装方便调用,图上展示的是 native 层的 ServiceManager 启动过程。

ServiceManager 的启动是系统在开机时,init 进程解析 init.rc 文件调用 service_manager.c 中的 main() 方法入口启动的。 native 层有一个 binder.c 封装了一些与 Binder 驱动交互的方法。

ServiceManager 的启动分为三步,首先打开驱动创建全局链表 binder_procs,然后将自己当前进程信息保存到 binder_procs 链表,最后开启 loop 不断的处理共享内存中的数据,并处理 BR_xxx 命令(ioctl 的命令,BR 可以理解为 binder reply 驱动处理完的响应)。

6. ServiceManager 注册服务

è¿éåå¾çæè¿°

注册 MediaPlayerService 服务端,我们通过 ServiceManager 的 addService() 方法来注册服务。

首先 ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令(ioctl 的命令,BC 可以理解为 binder client 客户端发过来的请求命令)携带 ADD_SERVICE_TRANSACTION 命令,同时注册服务的线程进入等待状态 waitForResponse()。 Binder 驱动收到请求命令向 ServiceManager 的 todo 队列里面添加一条注册服务的事务。事务的任务就是创建服务端进程 binder_node 信息并插入到 binder_procs 链表中。

事务处理完之后发送 BR_TRANSACTION 命令,ServiceManager 收到命令后向 svcinfo 列表中添加已经注册的服务。最后发送 BR_REPLY 命令唤醒等待的线程,通知注册成功。

7. ServiceManager 获取服务


获取服务的过程与注册类似,相反的过程。通过 ServiceManager 的 getService() 方法来注册服务。

首先 ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令携带 CHECK_SERVICE_TRANSACTION 命令,同时获取服务的线程进入等待状态 waitForResponse()。

Binder 驱动收到请求命令向 ServiceManager 的发送 BC_TRANSACTION 查询已注册的服务,查询到直接响应 BR_REPLY 唤醒等待的线程。若查询不到将与 binder_procs 链表中的服务进行一次通讯再响应。

8. 进行一次完整通讯
è¿éåå¾çæè¿°

我们在使用 Binder 时基本都是调用 framework 层封装好的方法,AIDL 就是 framework 层提供的傻瓜式是使用方式。假设服务已经注册完,我们来看看客户端怎么执行服务端的方法。

首先我们通过 ServiceManager 获取到服务端的 BinderProxy 代理对象,通过调用 BinderProxy 将参数,方法标识(例如:TRANSACTION_test,AIDL中自动生成)传给 ServiceManager,同时客户端线程进入等待状态。

ServiceManager 将用户空间的参数等请求数据复制到内核空间,并向服务端插入一条执行执行方法的事务。事务执行完通知 ServiceManager 将执行结果从内核空间复制到用户空间,并唤醒等待的线程,响应结果,通讯结束。
 

发布了74 篇原创文章 · 获赞 36 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/kdsde/article/details/86154469