目录
- 跨进程通讯方式一:广播
- 跨进程通讯方式二:AIDL
- 程序中的多进程:一个app如何开辟多进程呢?
- Binder是什么,AIDL的底层为什么是Binder。
- 如何传递自定义对象?
- 面试题
需求
之前有一个这样的需求:我们开发了一个App,实现了与硬件的通讯,比如实现自动售卖机的开关机。但现在有一个客户想要我们App不能联网,那么就不能远程开关机了,然后他们想要他们的App来控制远程开关机,希望我们能开发开关机的功能,这就涉及到进程间的通讯。
以前跨进程通讯我只接触过AIDL,但想着,为了这么一个小的需求大费周章,实在麻烦,然后就想到了使用广播。让他们App发送一个广播信号,然后我们来接收,执行指令,比较简单。
一、跨进程通讯方式一:广播方式
1.1 客户的App的代码
Intent intent = new Intent("com.example.spunsugar.deviceBoot");
sendBroadcast(intent);
也可以鞋带参数,比如是开机还是关机。
1.2 我们的App
public class DevBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d("外部 执行设备开机指令1。", "onReceive: deviceBoot");
if ("com.example.spunsugar.deviceBoot".equals(action)) {
// 处理接收到的广播
}
}
}
进行注册
<receiver android:name="com.example.spunsugar.receiver.DevBootReceiver"
android:exported="true">
<intent-filter>
<!--监听 `action` 为 `"com.example.spunsugar.deviceBoot"` 的广播。当客户 App 发送广播时,
系统会匹配到你的 `DevBootReceiver`,并调用其 `onReceive()` 方法。-->
<action android:name="com.example.spunsugar.deviceBoot" />
</intent-filter>
</receiver>
跨进程支持:接收方 App 的 BroadcastReceiver
在 AndroidManifest.xml
中声明了 android:exported="true"
,允许接收其他 App 的广播。
Android 8.0 (API 26) 及以上版本禁止大多数隐式广播的静态注册。
// 在 Service 中动态注册
DevBootReceiver receiver = new DevBootReceiver();
IntentFilter filter = new IntentFilter("com.example.spunsugar.deviceBoot");
registerReceiver(receiver, filter);
// 在 onDestroy() 中取消注册
unregisterReceiver(receiver);
为了安全性,我们可以增加权限 (1)我们的App
<receiver android:name="com.example.spunsugar.receiver.DevBootReceiver"
android:exported="true"
android:permission="com.example.PERMISSION_SEND_COMMAND">
<intent-filter>
<action android:name="com.example.spunsugar.deviceBoot" />
</intent-filter>
</receiver>
<permission
android:name="com.example.PERMISSION_SEND_COMMAND".>
(2)用户的App
<uses-permission android:name="com.example.PERMISSION_SEND_COMMAND" />
或发送时携带
Intent intent = new Intent("com.example.spunsugar.deviceBoot");
sendBroadcast(intent,com.example.PERMISSION_CONTROL_DEVICE);
(3)接收方
public class DevBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!"com.example.spunsugar.deviceBoot".equals(intent.getAction())) return;
// 检查权限
if (context.checkCallingPermission("com.example.PERMISSION_CONTROL_DEVICE")
!= PackageManager.PERMISSION_GRANTED) {
return;
}
String action = intent.getAction();
Log.d("外部 执行设备开机指令1。", "onReceive: deviceBoot");
if ("com.example.spunsugar.deviceBoot".equals(action)) {
// 处理接收到的广播
}
}
}
但这种方式存在局限性,比如无法实现双向通信,适合简单应用场景和需求。接下来,我们看看其他跨进程方案。
二、跨进程通讯方式二:AIDL
AIDL是 Android 定义跨进程接口的语言。通过 AIDL 接口,客户端和服务端可以跨进程调用方法,底层基于 Binder 驱动实现.
2.1 App A:服务端
(1)打开aidl,不打开,生成不了aidl文件
两个App,都需要打开,在app/build.gradle
android {
...
buildFeatures.aidl = true
...
}
(2)创建AIDL接口 先创建文件夹,然后再创建接口
定义跨进程方法 sendMessage
,客户端调用此方法发送字符串,服务端处理后返回大写的字符串。
创建出来以后,记得构建一下项目,Android 自动生成 IMessageService.java
,包含 Stub
(服务端基类)和 Proxy
(客户端代理类)。
(3)接下来我们就可以使用这个类了。创建一个service出来
public class MessageService extends Service {
private IMessageService.Stub mBinder = new IMessageService.Stub() {
@Override
public String sendMessage(String message) throws RemoteException {
// 这里可以添加一些处理逻辑,比如转换大小写等
Log.d("AIDL A", "收到 sendMessage: "+message);
return "Processed:"+ message.toUpperCase(Locale.getDefault());
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
IMessageService.Stub
是服务端实现类,负责处理客户端请求。
sendMessage
方法在服务端的 Binder 线程池 中执行。
(4)注册到清单文件
<service android:name=".MessageService"
android:exported="true" <!-- 允许其他应用绑定 -->
android:process=":remote"><!-- 服务运行在独立进程 -->
<intent-filter>
<action android:name="com.example.rxjavademo.IMessageService" />
</intent-filter>
</service>
2.2 App B
(1)打开aidl,不打开,生成不了aidl文件【和App A的步骤一一样】
两个App,都需要打开,在app/build.gradle
android {
...
buildFeatures.aidl = true
...
}
(2)直接将App A的aidl文件夹复制过来
(3)Make Project一下
(4)发送
public class MainActivity extends AppCompatActivity {
private IMessageService messageService;
private boolean mIsBound = false;
@SuppressLint("MissingInflatedId")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
TextView textView = findViewById(R.id.tv_hello);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.example.rxjavademo",// 服务端包名
"com.example.rxjavademo.MessageService"));//服务端的类名
bindService(intent,mConnection,BIND_AUTO_CREATE);
}
});
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messageService = IMessageService.Stub.asInterface(service);
try {
String s = messageService.sendMessage("hello from App B");
Log.d("AIDL B", "onServiceConnected: " + s);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
mIsBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
messageService = null;
mIsBound = false;
}
};
@Override
protected void onStop() {
super.onStop();
if (mIsBound) {
unbindService(mConnection);
mIsBound = false;
}
}
}
我们看看运行结果:可以收到数据和接收数据了。
2.3 介绍一下上述代码的执行流程:
-
IMessageService.Stub:服务端基类,继承
Binder
并实现 AIDL 接口。 -
IMessageService.Stub.Proxy:客户端代理类,封装数据序列化和跨进程调用。
1. Android 自动生成 IMessageService.java
,包含 Stub
和 Proxy
类的作用
-
Stub(服务端基类):
-
继承自
Binder
类,是服务端实现 AIDL 接口的基类。 -
服务端需要继承
Stub
并实现接口方法(如sendMessage
)。 -
核心方法
onTransact()
:负责解析客户端发送的请求,调用对应的接口方法,并返回结果。 -
示例:
-
-
-
public static abstract class Stub extends Binder implements IMessageService { @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) { // 解析客户端请求,调用 sendMessage 方法,并将结果写回 reply } }
-
-
Proxy(客户端代理类):
-
客户端通过
Proxy
类与远程服务通信。 -
封装了数据的序列化和反序列化逻辑,通过
Binder
驱动将请求发送到服务端。 -
示例:
-
-
-
private static class Proxy implements IMessageService { @Override public String sendMessage(String message) { // 将 message 序列化为 Parcel,通过 Binder 发送到服务端 // 等待服务端返回结果并反序列化 } }
-
-
核心流程:
- 客户端调用
Proxy.sendMessage()
→ 数据序列化 → 通过 Binder 驱动发送到服务端 →Stub.onTransact()
处理 → 结果返回客户端。
- 客户端调用
2. 为什么将 AIDL 接口实现放到 Service 中?
-
Service 的作用:
- Service 是 Android 的四大组件之一,用于在后台执行长时间运行的操作。
- Service 可以作为跨进程通信的载体,通过
onBind()
返回Binder
对象,供其他应用绑定并调用方法。
-
原因解释:
- 生命周期管理:Service 提供绑定(
bindService
)和解绑(unbindService
)机制,自动管理连接状态。 - 跨进程支持:Service 的
android:process
属性允许运行在独立进程,实现内存隔离。
- 生命周期管理:Service 提供绑定(
3. public IBinder onBind(Intent intent) { return mBinder; }
的作用
-
方法功能:
onBind()
是 Service 的生命周期方法,当客户端调用bindService()
时,系统会回调此方法。- 返回的
IBinder
对象(即mBinder
)是客户端与服务端通信的桥梁。
-
关键点:
mBinder
是服务端实现Stub
的实例(如IMessageService.Stub
)。- 客户端通过
ServiceConnection
获取IBinder
对象后,可将其转换为 AIDL 接口实例,调用远程方法。
4. <intent-filter>
中 <action>
的意义
-
作用:
- 定义服务的唯一标识,允许客户端通过 隐式 Intent 绑定服务。
- 客户端通过指定
action
匹配服务端的 Service。
-
代码解析:
<intent-filter>
<action android:name="com.example.rxjavademo.IMessageService" />
</intent-filter>
-
客户端绑定服务时,通过
action
字符串匹配服务: -
-
Intent intent = new Intent("com.example.rxjavademo.IMessageService"); bindService(intent, connection, BIND_AUTO_CREATE);
-
-
注意事项:
action
应全局唯一,通常使用 包名 + 接口名 的命名方式,避免与其他应用冲突。- 若使用
ComponentName
显式指定服务(如示例代码),则无需依赖action
。
5. bindService(intent, mConnection, BIND_AUTO_CREATE)
的含义
-
方法作用:
-
绑定服务:客户端通过此方法与服务端建立连接,获取
IBinder
对象。 -
参数解析:
intent
:标识目标服务的 Intent(显式或隐式)。mConnection
:ServiceConnection
对象,监听绑定状态。BIND_AUTO_CREATE
:标志位,表示如果服务未启动,则自动创建。
-
-
ServiceConnection 的作用:
-
回调接口,用于接收服务绑定成功或失败的通知。
-
核心方法:
-
-
-
public interface ServiceConnection { void onServiceConnected(ComponentName name, IBinder service); // 绑定成功 void onServiceDisconnected(ComponentName name); // 绑定异常断开 }
-
-
流程示例:
- 客户端调用
bindService()
。 - 系统查找匹配的 Service,若未运行则启动它。
- Service 的
onBind()
返回IBinder
。 - 系统回调
onServiceConnected()
,客户端通过IBinder
获取 AIDL 接口实例。
- 客户端调用
三、Binder是什么,AIDL的底层为什么是Binder
-
传统 IPC(如管道、消息队列、Socket):数据需要从发送方用户空间拷贝到内核空间,再从内核空间拷贝到接收方用户空间(两次拷贝),效率较低。
-
Binder:通过内存映射(mmap)实现单次数据拷贝。发送方用户空间数据直接写入内核缓冲区,接收方可直接读取内核缓冲区,无需二次拷贝,性能显著提升。
Binder 是 Android 系统实现进程间通信(IPC)的核心机制,它基于 Linux 内核模块(binder driver
)和用户空间的库。
AIDL 的 Stub
和 Proxy
类主要在 用户空间 操作,但会通过 Binder 驱动(内核空间)完成跨进程通信。
Binder 驱动在内核空间通过 mmap
创建一块 共享内存缓冲区,该缓冲区会映射到 用户空间的虚拟内存。具体流程如下:
-
客户端(Proxy)发送请求:
- Proxy 在用户空间将参数序列化为
Parcel
对象。 - 调用
IBinder.transact()
,触发 Binder 驱动将数据从用户空间拷贝到内核缓冲区(一次拷贝)。 - Binder 驱动记录目标进程信息,将数据放入目标进程的内核缓冲区。
- Proxy 在用户空间将参数序列化为
-
内核空间调度:
- Binder 驱动在内核空间找到目标进程的缓冲区,并通知目标进程(服务端)有新的请求。
-
服务端(Stub)处理请求:
- Stub 在用户空间直接从映射的内核缓冲区读取数据(无需二次拷贝)。
- 反序列化数据,调用实际的服务方法,并将结果序列化为
Parcel
。 - 结果通过 Binder 驱动返回给客户端的内核缓冲区。
-
客户端接收结果:
- 客户端从内核缓冲区读取返回数据(同样通过内存映射,无需拷贝)。
四、如何在一个App里面启动多个进程。
其实流程是一样,只不过将Activity的代码放到同一个App里面。
我们运行,也是可以进行通讯的。因为是多进程,即使在一个App内,也需要使用AIDL。
五、如何传递自定义对象?
(1)创建一个User.aidl
// User.aidl
package com.example.rxjavademo;
parcelable User; // 声明 User 为 Parcelable 类型
(2)创建User对象
// User.java
package com.example.rxjavademo;
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 从 Parcel 反序列化对象的构造函数
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
// 必须的 Creator 对象
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; // 一般返回 0
}
// 序列化对象到 Parcel
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
// Getter 方法
public String getName() { return name; }
public int getAge() { return age; }
}
(3)服务提供增加一下方法。
// IMessageService.aidl
package com.example.rxjavademo;
import com.example.rxjavademo.User; // 必须显式导入 User 类
// Declare any non-default types here with import statements
interface IMessageService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
String sendMessage(String message);
void sendUser(in User user); // in 表示数据从客户端流向服务端
User getUser(); // 返回 User 对象
}
构建一下项目
(4)服务端配置
private User receivedUser;
private IMessageService.Stub mBinder = new IMessageService.Stub() {
@Override
public String sendMessage(String message) throws RemoteException {
// 这里可以添加一些处理逻辑,比如转换大小写等
Log.d("AIDL A", "收到 sendMessage: "+message);
return "Processed:"+ message.toUpperCase(Locale.getDefault());
}
@Override
public void sendUser(User user) throws RemoteException {
receivedUser = user; // 接收客户端传递的 User 对象
Log.d("AIDL Server", "Received user: " + user.getName() + ", Age: " + user.getAge());
}
@Override
public User getUser() throws RemoteException {
return new User("ServerUser", 30); // 返回一个 User 对象给客户端
}
};
(5)客户端配置
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messageService = IMessageService.Stub.asInterface(service);
try {
String s = messageService.sendMessage("hello from App B");
Log.d("AIDL B", "onServiceConnected: " + s);
// 向服务端发送 User 对象
User clientUser = new User("ClientUser", 25);
messageService.sendUser(clientUser);
// 从服务端获取 User 对象
User serverUser = messageService.getUser();
Log.d("AIDL Client", "Received user: " + serverUser.getName() + ", Age: " + serverUser.getAge());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
mIsBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
messageService = null;
mIsBound = false;
}
};
六、面试题
面试题一: 广播实现 IPC 时如何提高安全性?
- 权限控制:
<receiver android:permission="com.example.PERMISSION">
<intent-filter>...</intent-filter>
</receiver>
-
定向广播:通过
setPackage()
指定接收方包名。 -
本地广播:使用
LocalBroadcastManager
(仅限应用内通信)。
面试题二:多进程应用有哪些注意事项?
-
静态变量隔离:各进程独立内存空间,需通过 IPC 共享数据。
-
Application 多次初始化:区分进程初始化逻辑:
if (getProcessName().endsWith(":remote")) {
// 子进程初始化
} else {
// 主进程初始化
}
面试题三: 为什么 Android 选择 Binder 而不是传统的 IPC(如管道、Socket)?
- 性能:Binder 基于内存映射,只需一次拷贝;传统 IPC 需两次拷贝。
面试题四: Service 的 onBind()
方法返回的是什么?
-
返回
IBinder
对象,作为客户端与服务端通信的桥梁。- 服务端:继承
AIDL 接口的 Stub
类(如IMyService.Stub
)。 - 客户端:通过
Stub.asInterface(IBinder)
转换为接口代理。
- 服务端:继承
面试题五: 解释 Binder 机制的工作原理。
-
Binder 是 Android 核心 IPC 机制,基于内核驱动实现,高效且安全。
-
流程:
- 客户端:调用代理接口方法,数据序列化为
Parcel
,通过 Binder 驱动发送。 - Binder 驱动:跨进程传递数据,管理线程和内存。
- 服务端:接收请求,反序列化数据,执行方法并返回结果。
- 客户端:调用代理接口方法,数据序列化为
-
特点:
- 高效:基于内存映射(mmap),一次拷贝。
- 安全:支持 UID/PID 校验和权限控制。