Android 跨进程通讯IPC(一):广播如何扩进程通讯;AIDL;如何在一个App里面启动多个进程;跨进程面试题

171 阅读11分钟

目录

  1. 跨进程通讯方式一:广播
  2. 跨进程通讯方式二:AIDL
  3. 程序中的多进程:一个app如何开辟多进程呢?
  4. Binder是什么,AIDL的底层为什么是Binder。
  5. 如何传递自定义对象?
  6. 面试题

需求

之前有一个这样的需求:我们开发了一个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 的 BroadcastReceiverAndroidManifest.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接口 先创建文件夹,然后再创建接口

图片.png

图片.png

图片.png

图片.png

图片.png

定义跨进程方法 sendMessage,客户端调用此方法发送字符串,服务端处理后返回大写的字符串。

创建出来以后,记得构建一下项目,Android 自动生成 IMessageService.java,包含 Stub(服务端基类)和 Proxy(客户端代理类)。

图片.png

(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;
        }
    }
}

我们看看运行结果:可以收到数据和接收数据了。

图片.png

2.3 介绍一下上述代码的执行流程:

  • ​IMessageService.Stub​​:服务端基类,继承 Binder 并实现 AIDL 接口。

  • ​IMessageService.Stub.Proxy​​:客户端代理类,封装数据序列化和跨进程调用。

​1. Android 自动生成 IMessageService.java,包含 StubProxy 类的作用​
  • ​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 属性允许运行在独立进程,实现内存隔离。
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(显式或隐式)。
      • mConnectionServiceConnection 对象,监听绑定状态。
      • BIND_AUTO_CREATE:标志位,表示如果服务未启动,则自动创建。
  • ​ServiceConnection 的作用​​:

    • 回调接口,用于接收服务绑定成功或失败的通知。

    • ​核心方法​​:

    •   public interface ServiceConnection {
            void onServiceConnected(ComponentName name, IBinder service); // 绑定成功
            void onServiceDisconnected(ComponentName name); // 绑定异常断开
        }
      
  • ​流程示例​​:

    1. 客户端调用 bindService()
    2. 系统查找匹配的 Service,若未运行则启动它。
    3. Service 的 onBind() 返回 IBinder
    4. 系统回调 onServiceConnected(),客户端通过 IBinder 获取 AIDL 接口实例。

三、Binder是什么,AIDL的底层为什么是Binder

  • 传统 IPC(如管道、消息队列、Socket)​​:数据需要从发送方用户空间拷贝到内核空间,再从内核空间拷贝到接收方用户空间(​​两次拷贝​​),效率较低。

  • ​Binder​​:通过内存映射(mmap)实现​​单次数据拷贝​​。发送方用户空间数据直接写入内核缓冲区,接收方可直接读取内核缓冲区,无需二次拷贝,性能显著提升。

​Binder​​ 是 Android 系统实现进程间通信(IPC)的核心机制,它基于 Linux 内核模块(binder driver)和用户空间的库。

AIDL 的 StubProxy 类主要在 ​​用户空间​​ 操作,但会通过 Binder 驱动(内核空间)完成跨进程通信。

Binder 驱动在内核空间通过 mmap 创建一块 ​​共享内存缓冲区​​,该缓冲区会映射到 ​​用户空间的虚拟内存​​。具体流程如下:

  1. ​客户端(Proxy)发送请求​​:

    • Proxy 在用户空间将参数序列化为 Parcel 对象。
    • 调用 IBinder.transact(),触发 Binder 驱动将数据从用户空间拷贝到内核缓冲区(​​一次拷贝​​)。
    • Binder 驱动记录目标进程信息,将数据放入目标进程的内核缓冲区。
  2. ​内核空间调度​​:

    • Binder 驱动在内核空间找到目标进程的缓冲区,并通知目标进程(服务端)有新的请求。
  3. ​服务端(Stub)处理请求​​:

    • Stub 在用户空间直接从映射的内核缓冲区读取数据(​​无需二次拷贝​​)。
    • 反序列化数据,调用实际的服务方法,并将结果序列化为 Parcel
    • 结果通过 Binder 驱动返回给客户端的内核缓冲区。
  4. ​客户端接收结果​​:

    • 客户端从内核缓冲区读取返回数据(同样通过内存映射,无需拷贝)。

四、如何在一个App里面启动多个进程。

其实流程是一样,只不过将Activity的代码放到同一个App里面。

我们运行,也是可以进行通讯的。因为是多进程,即使在一个App内,也需要使用AIDL。

图片.png

五、如何传递自定义对象?

(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;

    }
};

图片.png

六、面试题

面试题一: 广播实现 IPC 时如何提高安全性?​

  1. ​权限控制​​:
  <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 机制,基于内核驱动实现,高效且安全。

  • ​流程​​:

    1. ​客户端​​:调用代理接口方法,数据序列化为 Parcel,通过 Binder 驱动发送。
    2. ​Binder 驱动​​:跨进程传递数据,管理线程和内存。
    3. ​服务端​​:接收请求,反序列化数据,执行方法并返回结果。
  • ​特点​​:

    • ​高效​​:基于内存映射(mmap),一次拷贝。
    • ​安全​​:支持 UID/PID 校验和权限控制。