IPC 面试知识点整理

整理自模拟面试,覆盖 Binder / AIDL / Messenger / Parcelable / TransactionTooLargeException / 共享内存


1. 什么是进程间通信

一句话:IPC(Inter-Process Communication)就是不同进程之间交换数据、传递消息的机制。

为什么 Android 需要 IPC

Android 常见 IPC 方式

面试速记


2. Binder 原理是什么

一句话:Binder 是 Android 提供的基于 Client-Server 模型的 IPC 机制,让跨进程调用看起来像本地方法调用。

四个核心角色

核心通信流程

  1. Client 调用代理对象 Proxy
  2. Proxy 把方法参数写入 Parcel
  3. 通过 Binder 驱动把数据发送给目标进程
  4. Server 端 StubonTransact() 中解包数据
  5. 调用真正的本地实现
  6. 结果再写入 Parcel 返回给 Client

Binder 为什么适合 Android


3. AIDL 是什么,有什么限制

一句话:AIDL 是 Android 定义跨进程接口的一种方式,适合复杂接口调用,但存在类型、线程、性能和传输大小等限制。

AIDL 适合的场景

常见限制

1. 参数类型有限制

2. 调用有跨进程开销

3. 线程模型有限制

4. 不适合长时间阻塞

5. 单次传输大小有限

6. 需要处理远程进程死亡

参数方向

实战建议


4. AIDL 示例,以及 Stub / Proxy / asInterface() 做了什么

典型 AIDL 示例

IMathService.aidl

package com.example.ipc;

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

服务端:

public class MathService extends Service {

    private final IMathService.Stub mBinder = new IMathService.Stub() {
        @Override
        public int add(int a, int b) {
            return a + b;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

客户端:

public class MainActivity extends AppCompatActivity {

    private IMathService mathService;

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mathService = IMathService.Stub.asInterface(service);
            try {
                int result = mathService.add(3, 5);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mathService = null;
        }
    };
}

Stub 做了什么

Proxy 做了什么

Stub.asInterface(binder) 做了什么

一句话:把一个通用的 IBinder 转换成业务接口对象,并自动屏蔽本地调用和远程调用的差异。

典型逻辑:

public static IMathService asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (iin != null && iin instanceof IMathService) {
        return (IMathService) iin;
    }
    return new Proxy(obj);
}

关键结论


5. 为什么 AIDL 不能直接传普通对象,为什么优先 Parcelable

一句话:因为跨进程不能共享对象内存,只能传序列化后的数据;Parcelable 是 Android 为 Parcel 和 IPC 专门优化的方案。

为什么不能直接传普通对象

Parcelable 的特点

Serializable 的特点

Gson 的特点

三者区别总结


6. Messenger 有什么用,和 AIDL 怎么选

一句话:Messenger 适合简单、低并发、消息驱动的跨进程通信;AIDL 适合复杂接口、复杂数据和高并发场景。

Messenger 的本质

Messenger 适合的场景

Messenger 的优缺点

AIDL vs Messenger

Messenger 跨进程使用示例

服务端:

public class MessengerService extends Service {

    public static final int MSG_ADD = 1;
    public static final int MSG_RESULT = 2;

    private final Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.what == MSG_ADD) {
                int a = msg.getData().getInt("a");
                int b = msg.getData().getInt("b");
                int result = a + b;

                Messenger client = msg.replyTo;
                if (client != null) {
                    Message reply = Message.obtain(null, MSG_RESULT);
                    Bundle bundle = new Bundle();
                    bundle.putInt("result", result);
                    reply.setData(bundle);
                    try {
                        client.send(reply);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                super.handleMessage(msg);
            }
        }
    };

    private final Messenger messenger = new Messenger(handler);

    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}

客户端:

public class MainActivity extends AppCompatActivity {

    private static final int MSG_ADD = 1;
    private static final int MSG_RESULT = 2;

    private Messenger serviceMessenger;

    private final Messenger clientMessenger = new Messenger(
            new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    if (msg.what == MSG_RESULT) {
                        int result = msg.getData().getInt("result");
                    } else {
                        super.handleMessage(msg);
                    }
                }
            }
    );
}

核心流程

  1. 服务端用 Handler 创建 Messenger
  2. 客户端绑定服务后拿到服务端 IBinder
  3. 客户端用 new Messenger(service) 构造代理
  4. 通过 send(Message) 发消息
  5. 服务端在 handleMessage() 中处理
  6. 通过 replyTo 回传结果

7. 为什么 Binder 不适合传大数据

一句话:Binder 擅长控制型、小中等数据量的 IPC,不适合直接传大对象,因为事务缓冲区有限,而且伴随明显的序列化、拷贝和线程切换成本。

主要原因

容易出问题的对象

开发建议


8. TransactionTooLargeException 的本质是什么

一句话:它的本质是一次 Binder 事务写入的数据整体过大,超出了 Binder 事务缓冲区可承受范围,导致事务失败。

关键理解

常见触发场景


9. onSaveInstanceState() 为什么容易触发 TransactionTooLargeException

一句话:因为 onSaveInstanceState() 中保存的 Bundle 最终也要通过 Binder 传给系统进程,所以它并不是一个可以无限存数据的本地缓存容器。

根本原因

为什么这个场景高频出问题

正确使用方式


10. 为什么 ViewModel 更适合保存页面大对象

一句话ViewModel 保存的是当前进程内存中的运行期数据,不需要经过 Bundle 和 Binder;而 onSaveInstanceState() 只适合保存轻量恢复状态。

ViewModel 适合保存什么

为什么更适合

ViewModel 的边界

最佳实践


11. 跨进程传大数据有什么办法

一句话:大数据不要直接放进 Binder 事务里,而是通过 Binder 传“引用、句柄或控制信息”,真实数据放到更合适的承载介质中。

常见方案

1. 文件共享

2. ContentProvider

3. 共享内存

4. ParcelFileDescriptor

5. 分片传输

实战选型


12. 共享内存使用示例

一句话:共享内存的典型做法是用 Binder 传递共享内存句柄,用共享内存本身承载真正的大数据。

AIDL

package com.example.ipc;

import android.os.ParcelFileDescriptor;

interface ISharedMemoryService {
    ParcelFileDescriptor getSharedMemoryFd();
}

服务端

@TargetApi(Build.VERSION_CODES.O_MR1)
public class SharedMemoryService extends Service {

    private final ISharedMemoryService.Stub binder = new ISharedMemoryService.Stub() {
        @Override
        public ParcelFileDescriptor getSharedMemoryFd() throws RemoteException {
            try {
                SharedMemory sharedMemory = SharedMemory.create("demo_region", 1024);
                ByteBuffer buffer = sharedMemory.mapReadWrite();
                buffer.put("hello from server process".getBytes(StandardCharsets.UTF_8));
                SharedMemory.unmap(buffer);
                return ParcelFileDescriptor.dup(sharedMemory.getFileDescriptor());
            } catch (ErrnoException | IOException e) {
                throw new RemoteException(e.getMessage());
            }
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

客户端

@TargetApi(Build.VERSION_CODES.O_MR1)
private void readSharedMemory() {
    try {
        ParcelFileDescriptor pfd = service.getSharedMemoryFd();
        SharedMemory sharedMemory = SharedMemory.fromFileDescriptor(
                pfd.getFileDescriptor()
        );
        ByteBuffer buffer = sharedMemory.mapReadOnly();
        byte[] bytes = new byte[1024];
        buffer.get(bytes);
        String result = new String(bytes, StandardCharsets.UTF_8).trim();
        SharedMemory.unmap(buffer);
        pfd.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

关键点

注意事项


13. 一句话速记

知识点 速记
Binder 原理 Proxy 打包 → Binder 驱动转发 → Stub 解包执行
AIDL 限制 类型受限、线程池并发、传输有开销、大小有限
Stub / Proxy Stub 负责服务端接收分发,Proxy 负责客户端打包发送
asInterface() 本地返回实现,远程返回 Proxy
Parcelable Android 为 Parcel 优化的高性能序列化方案
Messenger 适合简单、低并发、消息驱动 IPC
大数据传输 不直接塞 Binder,改传文件、句柄、共享内存、分片
TransactionTooLargeException 单次 Binder 事务数据过大导致失败
onSaveInstanceState() 最终也走 Binder,只能存轻量恢复状态
ViewModel 适合保存运行期大对象,不适合替代持久化