循序渐进 Android Binder(一):IPC 基本概念和 AIDL 跨进程通信的简单实例

article/2025/7/13 8:30:18

Binder 给人的第一印象是”捆绑者“,即将两个需要建立关系的事物用某些工具束缚在一起。在 Android 中,Binder 是一种高效的跨进程通信(IPC)机制,它将可以将运行在不同进程中的组件进行绑定,以实现彼此通信和数据共享。Binder 机制在 Android 系统中扮演这至关重要的角色,特别是在管理应用程序和系统服务之间的交互方面。

在 Android 系统中,Binder 占有举足轻重的地位。而 Binder 机制涉及的东西即多且复杂,对于学习者来说,想要全面理解 Binder 仅靠一两篇文章就融会贯通是不可能的。越复杂的东西,越需要循序渐进,逐步理解,以免迷失了方向。因此,我将通过一系列文章,逐步带领大家进入 Binder 的内容,最终实现对于 Binder 的理解和运用。

我们知道 Binder 是一个 IPC 工具,那什么是 IPC 呢?我们为什么需要这玩意呢?接下来,我们就从此问题开始,逐步进入 Binder 的世界。

什么是 IPC?为什么我们需要它?

在操作系统中,每个进程都是在一个独立的沙箱环境中的,它拥有自己的内存空间、权限边界、资源管理。为了保证系统稳定性和安全性,进程之间是无法直接访问彼此的数据或代码的。

但是在实际开发中,我们经常需要不同进程之间进行协作。例如:

  • APP 通过 DisplayManager 获取屏幕的物理像素;
  • 从一个 Activity 跳转到另一个 Activity;

这些场景都离不开一个关键词:IPC(Inter-Process Communication),进程间通信。这是一种技术或方法,用于在至少两个进程或线程之间传输数据或信号。

这时候,有人会问了,为什么操作系统要将应用程序放到独立的沙箱环境中运行呢?都放到系统空间中,效率岂不是更高,而且避免了进程间通信的这些麻烦事。

这是个好问题,也确实有些系统不强制应用程序运行在独立空间中,而允许其直接运行在内核态。例如,在早期的 DOS 系统中,没有用户态/内核态之分,程序可以直接操作硬件;而现在的很拖 RTOS 也是不区分用户态、内核态的,其目的就是为了极致的性能表现。

但这些操作系统基本都是面向特定领域,不做应用程序隔离的设计使其无法成为面向大众的通用操作系统。毕竟,谁都不喜欢一个 BUG 就蓝屏的系统吧。

因此,现代主流通用操作系统(Linux、Windows、macOS、Android、iOS)都强制将应用运行在用户态中。这里我们解释下用户态和内核态这两个概念:

  • 用户态(User Mode):应用程序运行的环境,不能直接访问硬件和内核资源,必须通过系统调用来请求服务。
  • 内核态(Kernel Mode):操作系统内核拥有最高权限,负责调度、资源管理、设备控制等。

这么设计的核心原因是:安全性 + 稳定性 + 可控性。如果允许一个普通程序在内核态运行,那么它一旦崩溃,就可能导致整个系统内核崩溃,影响所有进程。所以这是一种“以安全和控制为核心”的设计哲学。

Android 系统中支持的 IPC 机制

上面已经介绍了 IPC 的基本概念,那 Android 系统支持哪些 IPC 机制呢?

首先,Android 基于 Linux 内核,因此天然继承了 Linux 提供的各种经典 IPC 机制,这这些机制主要包括:

  • 管道(Pipe):单向通信、半双工,只适用于亲缘进程间通信(如父子进程)
  • 命名管道(FIFO):不支持双向通信,只适用于简单的数据流
  • 消息队列(Message Queue):内核对象生命周期管理复杂,数据大小有限,不适合大数据结构
  • 共享内存(Shared Memory):高效但非常依赖开发者手动控制同步,容易出错
  • 信号量(Semaphore):仅限控制用途,无法传递数据
  • 套接字(Socket):支持远程通信但性能较低,安全性不够强,需要开发者自己维护协议等

Android 虽然基于 Linux,但并没有直接采用传统的 Linux IPC 机制来构建系统服务框架。原因很简单:这些机制(如管道、消息队列、信号)要么功能太弱,要么难以管理、缺乏安全保障。为了构建一个高性能、可控、安全的跨进程通信模型,Google 选择在内核层扩展了一套独特的机制 —— Binder。

Binder 的优势

Binder 是 Android 独有的一套进程通信机制,底层由内核模块 binder 实现,用户态通过 Binder 驱动访问。使用它能够像调用本地方法一样,调用另一个进程中的方法。

特征描述
高性能基于内核实现,采用零拷贝(zero-copy)技术,远高于 Socket 传输效率
统一接口调用语义支持同步、异步、回调,封装成面向对象接口,通过 Stub/Proxy 自动生成代码
安全性强每个 Binder 调用都带有 UID/PID,可用于权限验证和访问控制
支持大数据传输配合 Ashmem 可高效传输图像、音频等大对象
服务注册/发现机制有 centralized 的 ServiceManager 管理所有系统服务
内存安全/生命周期管理每个 Binder 对象都有引用计数,不会悬空引用

Binder 的出现,是 Android 构建自身 IPC 世界的重要基石。它不仅替代了 Linux 的传统 IPC 机制,还通过提供高效、安全、可扩展的通信模型,成功支撑起整个 Android 系统服务架构 —— 从最底层的启动服务到最顶层的应用组件通信,Binder 是 Android 系统的“神经网络”。

上面我们介绍了 IPC 和 Binder 的一些基本概念,接下来,我们就用一个 AIDL 的简单实例,来演示一下 Binder 的基本调用。

基于 AIDL 的跨进程通信的简单实例

Binder 通信采用客户端-服务器(Client/Server)模型,因此即使是一个简单的跨进程通信实例,我们也要写服务端和客户端两个进程代码。在继续写代码之前,我们需要接受一个新的词:AIDL(Android Interface Definition Language)。

什么是 AIDL?

虽然 Android Binder 提供了强大的进程通信机制,但直接使用它代价极高,开发者需要手动处理方法编号、参数序列化等细节,既麻烦又容易出错。因此 Android 引入了 AIDL 接口描述语言,其目的就是自动生成一套可靠的、类型安全的、跨进程可用的接口通信代码。

其实,使用 Binder 调用并不是必须要使用 AIDL,但是,当你需要进行 IPC,调用时,AIDL 是最清晰、高效、安全的解决方案。

一个简单的 AIDL 接口定义就如下:

interface IComputer {int add(int a, int b);
}

当你将其添加到项目并编译后,Android Studio 会自动生成一整套 Java 类,其主要包括:

  • IComputer.Stub:服务端实现
  • IComputer.Sutb.Proxy:客户端调用

你只需要在服务端继承 Stub,并在客户端调用 Proxy,就可以像调用本地方法一样进行 IPC 调用。
下面我们就以这个 AIDL 为例,来完成一个简单的 IPC 实例。

服务端

首先我们创建一个包名为 lic.swift.binder.server 的程序用于服务端。

然后添加上面的 IComputer.aidl 接口到代码中,先编译下,以便生成对应的 Java 文件。

接着创建一个全类名 lic.swift.binder.server.ComputeService 的 Service 作为服务端实现,如下:

public class ComputeService extends Service {private final static IComputer.Stub computer = new IComputer.Stub() { @Overridepublic int add(int a, int b) {Log.d("avril", "server, stub, add(a = " + a + ", b = " + b + "), " + getProcessLog());return a + b;}};@Overridepublic IBinder onBind(Intent intent) {Log.d("avril", "onBind(intent = " + intent + "), return " + computer + ", " + getProcessLog());return computer;}
}

这个 ComputerService 里面实现了一个 IComputer.Stub 这个就是作为服务端代码的实现,也就是真正执行 add(int, int) 的地方。在 onBind 中返回这个实例对象。

同时我们需要在 AndroidManifest.xml 中配置一下这个 ComputerService:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><permission android:name="lic.swift.binder.server.CALL_SERVER" android:protectionLevel="normal" /><applicationandroid:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round" ><service android:name=".ComputeService"android:permission="lic.swift.binder.server.CALL_SERVER"android:exported="true" ><intent-filter><action android:name="lic.swift.binder.server.ComputeService" /><category android:name="android.intent.category.DEFAULT"/></intent-filter></service></application>
</manifest>

为了能够让其他进程的应用访问到这个 Service,需要将其 exported 设置为 true。另外在 XML 中还为这个 Service 配置了 permission。其实在 Android 中,Service 本身并不强制要求声明访问权限,但如果你希望控制哪些应用可以访问你的服务,特别是涉及跨进程调用或敏感功能的服务,那么通过 android:permission 是一种标准且推荐的方式。

客户端

上面说的 IComputer.aidl 文件,它类似于 HTTP 协议,这个协议在服务端有一个,在客户端当然也需要有一个。因此在客户端的项目中,我们需要先把这个文件复制过来,注意,这里复制时必须要保证 .aidl 文件的包名、类名都相同。

接下来我们在 Activity 中添加一个按钮,每次点击这个按钮,就调用服务端的 add 方法计算一次加法并返回。

但为了能够进行远程调用,我们首先要连接到远程服务,以下是在 Activity 中用于连接到远程服务的代码:

private IComputer computer = null;
private final ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.d("avril", "client onServiceConnected(name = " + name + ", service = " + service + ") " + getProcessLog());computer = IComputer.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.d("avril", "client onServiceConnected(name = " + name + ") " + getProcessLog());computer = null;}
};final Intent intent = new Intent();
intent.setComponent(new ComponentName("lic.swift.binder.server", "lic.swift.binder.server.ComputeService"));
boolean bindResult = bindService(intent, connection, Context.BIND_AUTO_CREATE);
Log.d("avril", "client bindService 返回 " + bindResult);

为了连接到远程服务器,需要在清单文件中配置远程服务器需要的权限:

<uses-permission android:name="lic.swift.binder.server.CALL_SERVER" /><queries><package android:name="lic.swift.binder.server"/>
</queries>

在能够连接到远程服务器之后,我们就可以设置一个点击事件用于 IPC 调用了:

findViewById(R.id.button_1).setOnClickListener(v -> {if (computer == null)return;try {final int p0 = new Random().nextInt(1000);final int p1 = new Random().nextInt(1000);Log.d("avril", "client 远程调用 computer.add(" + p0 + ", " + p1 + "),等待返回...");final long time = SystemClock.uptimeMillis();int r = computer.add(p0, p1);Log.d("avril", "client 远程调用返回结果:" + r + ". 耗时:" + (SystemClock.uptimeMillis() - time) + "ms. " + getProcessLog());} catch (RemoteException e) {Log.d("avril", "client 远程调用发生异常 RemoteException:\n" + e.getLocalizedMessage());}
});

这就是一个很简单的 AIDL 实例了。服务端和客户端的代码结构如下:


在运行客户端程序并点击按钮后,我们能得到如下的 log 信息:

lic.swift.binder.client  D  client bindService 返回 true
lic.swift.binder.server  D  onBind(intent = Intent { cmp=lic.swift.binder.server/.ComputeService }), return lic.swift.binder.server.ComputeService$1@a97ebb8, Process.myPid() = 2139, Process.myUid() = 10149
lic.swift.binder.client  D  client onServiceConnected(name = ComponentInfo{lic.swift.binder.server/lic.swift.binder.server.ComputeService}, service = android.os.BinderProxy@7c18e1b) Process.myPid() = 2219, Process.myUid() = 10150lic.swift.binder.client  D  client 远程调用 computer.add(136, 333),等待返回...
lic.swift.binder.server  D  server, stub, add(a = 136, b = 333), Process.myPid() = 2139, Process.myUid() = 10149
lic.swift.binder.client  D  client 远程调用返回结果:469. 耗时:26ms. Process.myPid() = 2219, Process.myUid() = 10150

可以看到,代码已经完成了远程调用,服务端进程号为2139,而客户端是2219,虽然这个耗时有点高,但正确性是没问题的。

到此为止,这个基于 AIDL 的跨进程通信的简单实例就算是介绍完了。后续我们将逐步深入到 Binder 的更深层的内容当中。


http://www.hkcw.cn/article/HqNNjUwJri.shtml

相关文章

ISBN书号查询接口如何用PHP实现调用?

一、什么是ISBN书号查询接口 ISBN数据查询接口是一项图书信息查询服务。它基于全球通用的ISBN编码系统&#xff0c;帮助用户快速获取图书的详细信息&#xff0c;包括书名、作者、出版社、出版时间、价格、封面等关键字段。 该接口广泛应用于电商平台、图书馆管理系统、二手书…

Linux(信号)

目录 一 什么是信号 二 Linux中的信号 1. 查看信号&#xff1a;kill -l 2. 自定义信号的处理方式 2.1 API 2.2 demo 3. 理解信号的发送 4. 信号产生的方式 三 信号保存 四 捕捉信号 1. 先来说说硬件中断&#xff1a; 1. 谁调度操作系统&#xff1f; 2. 理解时间片…

[Windows] Simple Live v1.8.3 开源聚合直播 :支持哔哩哔哩 虎牙 斗鱼 抖音

Simple Live 是一款基于 AllLive 项目 开发的开源聚合直播 APP&#xff0c;支持 哔哩哔哩、虎牙、斗鱼、抖音 等主流平台&#xff0c;具备 无广告、低占用、弹幕互动 等核心优势。其核心功能包括&#xff1a;全平台覆盖&#xff1a;一站式聚合多平台直播资源&#xff0c;无需切…

第十天:Java反射

反射 反射就是&#xff1a;加载类&#xff0c;并编写代码获取类中的成员变量&#xff0c;方法&#xff0c;构造器等。 注意&#xff1a;反射&#xff0c;注解&#xff0c;动态代理就是用来学习框架做框架的&#xff0c;在平时业务开发需求上很少用到。 1 反射学什么&#xf…

整数有约 | 刘乾专访:继续预训练策略与数据优化之道

人工智能多语言处理近年来得到了极大的关注&#xff0c;尤其是在以东南亚为代表的小语种环境中&#xff0c;其特殊的语言多样性和语料库稀缺性使得研究挑战和机遇并存。在现有的自然语言处理模型中&#xff0c;英语和中文因为有海量高质量数据的支持&#xff0c;常被作为核心语…

Google 发布的全新导航库:Jetpack Navigation 3

前言 多年来&#xff0c;Jetpack Navigation 库一直是开发者的重要工具&#xff0c;但随着 Android 用户界面领域的发展&#xff0c;特别是大屏设备的出现和 Jetpack Compose 的兴起&#xff0c;Navigation 的功能也需要与时俱进。 今年的 Google I/O 上重点介绍了 Jetpack Na…

抖音商城抓包 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 抓包展示 总结 1.出于安全考虑,本章未…

uniapp-商城-77-shop(8.2-商品列表,地址信息添加,级联选择器picker)

地址信息,在我们支付订单上有这样一个接口,就是物流方式,一个自提,我们就显示商家地址。一个是外送,就是用户自己填写的地址。 这里先说说用户的地址添加。需要使用到的一些方式方法,主要有关于地址选择器,就是uni-data-picker级联选择。 该文介绍了电商应用中地址信息处…

AlmaLinux OS 10 正式发布:兼容 RHEL 10 带来多项技术革新

AlmaLinux OS 基金会日前宣布推出 AlmaLinux OS 10&#xff0c;该版本代号代号紫色的狮子 (Purple Lion)&#xff0c;新版本带来多项新功能和技术更新&#xff0c;旨在为用户提供更强大的企业级 Linux 体验。 该系统使用与 RHEL 10 相同的源代码构建并于 RHEL 10 保持完全兼…

深入理解C# MVVM模式:从理论到实践

在现代软件开发中&#xff0c;良好的架构设计对于构建可维护、可测试和可扩展的应用程序至关重要。Model-View-ViewModel (MVVM) 是一种特别适合XAML-based应用程序&#xff08;如WPF、Xamarin和UWP&#xff09;的架构模式。本文将全面探讨MVVM模式的概念、实现细节、最佳实践以…

Git GitHub Gitee

一、Git 是一个免费、开源的分布式版本控制系统。 版本控制&#xff1a;一种记录文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。它最重要的就是可以记录文件修改历史记录&#xff0c;从而让用户可以看历史版本&#xff0c;方便版本切换。 1.和集中式版本控制…

数据库管理-第332期 大数据已死,那什么当立?(20250602)

数据库管理332期 2025-06-02 数据库管理-第332期 大数据已死&#xff0c;那什么当立&#xff1f;&#xff08;20250602&#xff09;1 概念还是技术2 必然的大数据量3 离线到实时4 未来总结 数据库管理-第332期 大数据已死&#xff0c;那什么当立&#xff1f;&#xff08;202506…

Java Netty 中处理粘包和半包问题的解决方案 | TCP消息完整性校验(XOR )

文章目录 引言I 处理TCP粘包和半包问题背景粘包问题的产生原因解决方案WebSocket中的粘包和半包问题及解决方案II Java Netty 中处理粘包和半包问题粘包和半包问题可以通过以下几种方式解决:使用分隔符解码器基于长度字段的解码器实现自定义解码器III TCP常见封装处理消息接收…

鸿蒙next系统以后会取代安卓吗?

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 官方可没说过取代谁谁&#xff0c;三足鼎立不好吗&#xff1f;三分天下&#xff0c;并立共存。 鸿蒙基于Linux&#xff0c;有人说套壳&#xff1b;ios/macos基于Unix&#xff0c;说它ios开源了…

排便不是一件可以随意“延后”的事:长期便秘->直肠敏感性降低->功能性便秘->大便失禁

文章目录 引言知识扩展: 快乐排便的黄金姿势I 便秘并不是一种单一成因的疾病便秘成因临床治疗II 总是压抑排便,身体会发生的变化III 排便不是一件可以随意“延后”的事引言 排便是一种复杂的反射行为: 由“直肠充盈—产生便意—括约肌协调—排出”的完整生理链条完成的。 …

基于Spring Boot 电商书城平台系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

Golang——5、函数详解、time包及日期函数

函数详解、time包及日期函数 1、函数1.1、函数定义1.2、函数参数1.3、函数返回值1.4、函数类型与变量1.5、函数作参数和返回值1.6、匿名函数、函数递归和闭包1.7、defer语句1.8、panic和recover 2、time包以及日期函数2.1、time.Now()获取当前时间2.2、Format方法格式化输出日期…

HTTP详解

使用的工具&#xff1a;fiddler 一、请求和响应报文的结构 1.请求 ⾸⾏: [⽅法] [url] [版本]Header: 请求的属性, 冒号分割的键值对;每组属性之间使⽤\n分隔;遇到空⾏表⽰Header部分结束Body: 空⾏后⾯的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有…

解决:install via Git URL失败的问题

为解决install via Git URL失败的问题&#xff0c;修改安全等级security_level的config.ini文件&#xff0c;路径如下&#xff1a; 还要重启&#xff1a; 1.reset 2.F5刷新页面 3.关机服务器&#xff0c;再开机&#xff08;你也可以省略&#xff0c;试试&#xff09; 4.Wind…

【小工具开发】通过Java实现批量修改文件名小工具

1. 创建Project&#xff08;使用Gradle&#xff09; 2.安装Gradle 修改 distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-8.12-bin.zip 修改 distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-8.12-all.zip 阅读了以下博客&#xff0c;发…