搞懂Android内Binder的前因后果

感情迁移 提交于 2019-12-07 16:28:06

瞻仰了前辈们的研究成果, 并掺入了自己的理解, 如有不对, 敬请批评.


为什么Android要使用Binder

Binder 作为一种 IPC 机制, 在 Linux 内有很多的前辈, 为什么 google 会创建这么一种新的方式呢?

Linux 现有 IPC 方式有这几类:

  1. 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;
  2. 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
  3. 共享内存:无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
  4. 套接字:作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信;
  5. 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 信号: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;

与以上方式相比较, Binder 有以下特点:

  1. 性能较好: 数据拷贝 Binder 只需要一次, 管道/消息队列/socket 都需要两次, Binder 仅次于共享内存(一次都不需要);
  2. 稳定性较好: 基于C/S架构的Binder在逻辑上更加解耦, 架构清晰, 而共享内存容易出现各种并发同步死锁问题;
  3. 安全性好: Android每个App都有自己的UID, 传统的 IPC 机制的接收方无法拿到发送方可靠的UID, 而 Binder 的 Server 端可以通过 Android 给每个 App 暴露出的 Client 端获取到, 从而对不同UID的App进行权限判断等安全性控制;
  4. 语言层面: Linux基于C, Android基于java, Binder机制更符合面向对象的环境;
  5. 协议: Linux 受开源代码许可协议GPL的保护, 如果上层应用调用到 Linux Kernel, 就必须也遵循GPL协议; Android利用Binder隔离了Linux Kernel层, 把GPL控制在内核空间, 而用户空间采用了允许不反馈源码的Apache-2.0协议, 并在中间采用BSD授权, 有利于Google实现开源和商业化的共存

综上所述, Binder是Android系统中IPC的最好方式.


Binder 概述

Binder是android内独有的跨进程通信方式, 它在native层有一套完整的C/S架构, framework层也通过jni技术实现了一套镜像功能的Binder C/S架构, framework层的binder功能最终都交给native的binder来完成. 引用一张Gityuan大神的Binder架构图:

  • 对于Android Driver层: Binder可以理解为一种虚拟的物理设备, 它的设备驱动是/dev/binder, 连接了Video,Camera等设备;
  • 对于Android Native层: Binder是创建Service Manager/BpBinder/BBinder模型、搭建与binder驱动的桥梁;
  • 对于Android Framework层: Binder是各种Manager(ActivityManager, WindowManager等)和对于ManagerService的桥梁;
  • 对于Android App层: Binder是客户端和服务端通信的桥梁, 服务端包括统一进程下的和需要使用AIDL的不同进程下的服务. bindService的时候, 服务端会返回一个包含服务端方法及数据的Binder对象, 通过这个Binder对象, 客户端就能与之通信了.

Binder如何工作

Binder的数据传输 就是发送端把binder_transaction节点,插入到目标进程或其子线程的todo队列中,等目标进程或线程不断循环地从todo队列中取出数据并进行相应的操作.

在Binder驱动层,每个接收端进程都有一个todo队列,用于保存发送端进程发送过来的binder请求. 线程在空闲时进入可中断的休眠状态,当自己的todo队列或所属进程的todo队列有新的请求到来时便会唤醒,执行新的请求事务, 执行完毕后再次休眠.

再引入一张图, 并添加了注释


App层的两种服务里的binder使用

App层的Service有同进程下的Service和不同进程的Service两种, 新开进程的Service需要在AndroidManifest.xml里的Service声明中添加 android:process=":remote", 也可以添加服务名称.

同进程下的Service

Client 端

public class FragSocketPresenter extends BasePresenter<IFragSocketView> {
    private MyService.ServiceBinder normalBinder

    private ServiceConnection normalConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            normalBinder = (MyService.ServiceBinder) service;
            normalBinder.setSocketStateListener(normalListener);
        }

        @Override
        // 此方法只有在service异常时才调用
        public void onServiceDisconnected(ComponentName name) {
            normalBinder = null;
        }
    };

    public void startConnect() {
        if (getView() != null) {
            Intent intent = new Intent(getView().getContext(), MyService.class);
            intent.putExtra("IP", getView().getIp());
            getView().getContext().bindService(intent, normalConnection, Context.BIND_AUTO_CREATE);
        }
    }
}

服务端

public class MyService extends Service {
    private String ip = "";

    @Override
    public IBinder onBind(Intent intent) {
        ip = intent.getStringExtra("IP");
        return new ServiceBinder();
    }

    public class ServiceBinder extends Binder {
        public void setSocketStateListener(SocketStateListener normalListener) {
            ...
        }
    }
}

基于AIDL的跨进程服务

篇幅有限, 写到另一篇里了 AndroidStudio下使用 AIDL 构建跨进程 Service( 详细代码贴图 ), 填补网上的大多数坑


参考: 源码大神 Gityuan 的各类文章
http://gityuan.com/2015/10/31/binder-prepare/
https://www.zhihu.com/question/39440766/answer/89210950?from=profile_answer_card

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!