Binder 机制
-
Binder是什么
-
概述:
- Android中的血管,Android中四大组件与AMS进行通信时,需要借助Binder
-
属于IPC机制一种:任何类继承自Binder之后就拥有IPC能力
-
Client与Server两端通信
-
-
从驱动角度:Binder是一个虚拟物理设备驱动
- Binder没有对应的实际物理设备(例如网卡,显卡)
- android底层是Linux,一切皆文件
-
从应用层角度:Binder是一个发起通信的Java类
- 在使用的时候,一般是写一个AIDL,此时在build目录下会生成一个接口文件(用于客户端与服务端通信,相当于一个接口协议)
-
-
Binder应用层分析:
-
整体来说,分为客户端与服务端
- 两端的自动生成的文件中的类结构的三个东西是一样的
-
首先写一个AIDL接口
-
系统在Build目录下会使用AIDL工具生产一个同名接口文件,作为IPC协议
-
生成同名文件结构
-
Default:默认实现
-
Stub:抽象类
- 继承自Binder,从而拥有IPC能力
- 开放给另一端进行完善,实现IPC通信
-
Proxy
-
自定义的接口方法
-
-
-
多进程的使用及其优势
-
为什么需要多进程:
-
概述:成熟的应用一般是多进程
- 虚拟机分配给各个进程的运行内存有限制,LMK优先回收对系统资源占用较多的进程
-
突破进程内存限制
-
因为单个进程的内存是有限,一个进程可以看成是一个盒子
-
盒子大小由手机厂商决定的,例如 384MB
-
查看命令:
- adb shell
- getprop dalvik.vm.heapsize
-
-
-
功能稳定性:独立的通信进程保持长连接的稳定性
- 比如推送功能一般开一个进程处理
-
规避系统内存泄漏:单独的WebView进程阻隔其内存泄漏
-
WebView在正常使用的时候会导致内存泄漏
-
内存泄漏的原因:
- WebView 销毁的时候,其 destroy 和 onDetachedFromWindowInternal 方法最后会调用到 AwContents 中对应的方法,低版本的内存泄漏就发生在这里。
- AwComponentCallbacks 类的实现,发现它是 AwContents 中的一个非静态内部类,因此它会持有外部 AwContents 实例的引用,而 AwContents 持有 WebView 的 Context 上下文,对于 xml 中的 WebView 布局而言,这个上下文就是其所在的 Activity,因此如果在 Activity 生命周期结束后没有调用 unregisterComponentCallbacks 方法解注册的话,便可能会发生内存泄漏
-
解决手段:
- 在 Activity.onDestroy 的时候将 WebView 从 View 树中移除,然后再调用 WebView.destroy 方法:
- 一般单独开一个进程,用完就杀掉这个进程
-
-
-
隔离风险:对于不稳定的功能放入独立的进程,避免导致主进程崩溃
-
多进程导致的问题:
- application会调用多次,一个进程调一次
-
Binder的优势
-
Linux内置了许多IPC机制
- 管道、消息队列、共享内存、信号、信号量、Sockect
-
Binder与传统IPC的对比
-
拷贝次数:
- Binder一次拷贝:就是为了更好用
- 一般认为除共享内存外(0次),其余IPC两次
- 性能:共享内存>Binder>其余IPC
-
功能特点:
-
Binder基于C/S架构,易用性高,结构清晰
-
Android中通过AIDL的调用甚至感知不到IPC
-
具体调用
-
客户端:
-
bindService:传intent
-
ServerConnection:
-
链接成功
- 拿到句柄(就是自定义的AIDL接口)
-
链接失败
-
-
initView中通过句柄.addPerson(new Person())就行了
-
-
-
-
共享内存:控制复杂
-
线程本来就是共享进程内存的;
- 线程安全:死锁
- 信息不共享
-
进程之间还是有共享导致的问题
-
-
Sokect:基于CS架构但其作为通用接口,传输效率低,开销大;
-
从特点:Binder > 共享内存 > 传统IPC
-
-
安全性:
-
Binder:
-
为每个APP分配UID
- Linux底层决定UID为APP唯一标识;
- 做了坏事,就可以找到你;
-
支持实名与匿名
-
实名:谁都可以访问
-
实名服务:系统开放给外界的
- AMS,WMS
-
-
匿名:就像滴滴打车,不能直接找到司机,系统给了一个虚拟号码,整体来说是一个代理;通过系统拿到服务的代理对象,通过这个代理对象找到服务
- 匿名服务:自己的服务一般是匿名,当然自己的服务也可以变成实名服务
-
实名与匿名的区别:有没有在ServerManager中注册
- 注册了:就是实名
-
-
-
共享内存等传统IPC的问题
-
依赖上层协议
-
就像手写录入信息(信息录入全靠程序),导致无法跟踪
-
网络通信角度:
-
根据协议传数据包,由应用层填写再发送到服务端
-
应用层相当于Service,服务端相当于系统;
- Service依赖协议,将信息拿给系统
- 但是信息是Service自己写的,不安全
-
-
-
访问接入点开放--->不安全
- 类似于公交车站点,谁都可以上车,进行访问
- 这样就会导致易于攻击
-
-
-
系统内存划分与寻址:
-
进程内存划分示意图:
-
进程寻址:受机器影响(地址总线长度)
-
示意图:
-
-
寻址细节:
-
32位机器--->总体地址空间4G(有32条地址总线决定的)
- 用户空间:3G,虚拟内存,用户态
- 内核空间:1G,物理内存,内核态
-
常规寻址过程:虚拟内存与物理内存两者通过MMU联系
-
用户空间--->内核空间:copy_from_user
- 用户申请系统调用
- 调用由内存为主
-
拷贝次数:指系统调用的次数
- copy_from_user:用户--->内核
- copy_to_user:内核--->用户
-
-
寻址带来的问题
-
用户态与内核态之间切换:
-
上下文切换:耗资源
- 保存当前运行的状态;
-
涉及到中断并且耗时
-
-
-
数据流动过程:
-
数据在进程1的用户空间中--->进程1的内核空间:copy_from_user
-
进程1的内核空间与进程2的内核空间是共享的
- 所有进程中的内核空间(藏宝图)映射到同一块物理内存(终点)
-
进程2的内核空间到进程1的用户空间:copy_to_user
-
-
Binder优化手段:共享内存性能好但不好用,传统IPC好用但性能不好
-
将进程2的内核空间与进程2的用户空间通过mmap映射到同一块物理内存上;
-
相当于进程1的内核,进程2的内核,进程2的用户指向同一块物理内存
-
当然也可以再次优化成共享内存(mmap),虽然性能好,但是难用
-
为什么是优化这一端?
-
客户端实现太灵活了
- Proxy,直接继承自定义的AIDL接口
-
所以自己搞的AIDL接口,生成了同名接口文件中的Stub继承自Binder(拥有IPC能力),同时Binder是由Google工程师弄的
-
-
-
64位机器:
- 用户空间
- 内核空间
- 无用空间
-
虚拟内存:
- 软件工程师所说的都是虚拟内存
- 变量的地址也是虚拟内存
- 虚拟内存与物理内存之间的映射由MMU管理
-
物理内存:
- 内存条
- 不同进程中的用户空间映射到不同物理内存上
- 所有进程中的内核空间(藏宝图)映射到同一块物理内存(终点)
-
MMAP
-
MemoryMapping是什么?
- 内存映射(MMAP):Linux通过将一块虚拟内存区域与一个磁盘上的对象关联起来,从而初始化这个虚拟内存区域的内容
-
MMAP好处:加快文件读写速度
- 最开始的时候,用户空间是不能直接操作磁盘对象的,需要通过内核程序;
- 引入MMAP,将用户空间(虚拟内存)与磁盘对象(物理内存)直接关联起来
-
虚拟内存--->物理内存
-
MMU:内存管理单元处理
- 硬件实现,用户透明
-
-
MMAP具体使用:实现虚拟内存--->物理内存,需要去创建文件
-
搞一个物理内存:创建指定大小(一页)的虚拟内存块
- 打开文件:获取文件描述符(通过文件路径,Linux中一切皆文件)
- 获得一页内存大小:Linux中内存分页,以页为单位(一般32位机器,4kb一页)
- 将文件设置为一页内存这么大(大小限制问题无所谓)
-
调用MMAP:获得一个虚拟内存块句柄(指针)
- 实现一块一页大小的虚拟内存指向了文件,并返回这个虚拟内存的引用
- 虚拟内存指向物理内存(大小一致的)
- 返回虚拟内存的指针,那么我们通过这个指针写入文件的时候,就直接干到具体的物理内存了;因为磁盘读写与内存读写速度是不一样的;
-
向指针中写入数据就到位了:就跟个快捷方式一样的效果
-
实现细节:
-
整体由C++实现的并且需要开放权限
-
源码体现:
- 内核中会去调用一个bind_mmap
-
还有个MMKV的东西
-
-
为什么要用文件描述符:
- Linux下一切皆文件;
- 需要去指定一块物理内存,那么就需要用文件来处理就行了;
-
使用场景:需要提高文件读写性能
-
MMKV:
-
Binder
-
使用这个MMAP就需要去创建文件,Linux底层
-
-
AIDL:Android IPC
-
AIDL是什么?
-
帮助用户去代理一些规则(IPC规则):主要目的就是简化Binder的使用,
- 实现Binder的使用,就像黄牛
-
但是与Binder是不同的东西
-
-
Android Studio中的内置工具:.aidl
-
通过内置工具根据我们自定义的AIDL接口生成对应的 Java文件
-
在哪里:
- sdk/build-tools/随便整个版本30.0.0/aidl就在这个里面
- 通过 .aidl文件生成对应的java文件
-
APT也是这个样子
-
-
手写AIDL自动生成的代码:
-
结构:一个接口+两个类
-
接口:实现IPC通信协议
-
代理对象Proxy:
- 由客户端使用,实现IPersonManager接口
-
Stub:
- 由服务端使用
-
Default:这个不用管
-
-
Java代码生成细节:
-
Proxy:实现在客户端调用方法后服务端也会调用同名方法
-
asInterface:区分是否为跨进程通信(是,返回代理对象(return new Proxy(Binder));不是,返回服务端对象(return (IPersonManager)))
-
获取代理对象
-
客户端调用bindService
bindService(intent, connection, Context.BIND_AUTO_CREATE); -
系统回调onServiceConnected(iBinder对象)
public void onServiceConnected(ComponentName name, IBinder service) {-
这个IBinder对象是服务端的IBinder对象的代理
- 通过Service Manager与AMS拿到的
-
-
调用Stub.asInterfce(service),返回iPersonManage(服务端的代理对象)
iPersonManager = Stub.asInterface(service); -
在客户端中调用addPerson方法:跑到服务端中的addPerson 方法
iPersonManager.addPerson(new Person("Leo", 3));
-
-
ClientActivity:通过代理对象调用Proxy中的方法:主要帮助进入Binder
-
打包:两个包
-
客户端到服务端的数据:data
-
服务端返回给数据的包(repl)
- (C->S的时候是空的)
- S->C的时候就有东西
-
-
安全检测:判空
-
mRemote.transact(Stub):C->S,进入Binder,Binder在进入服务端
-
处理Binder的过程:
- 客户端到Binder
- Binder到服务端
-
Binder通信机制实现:这个里面就牵涉到Service Manager
-
Service Manager:服务的大管家,所有服务都在这个里面注册,类似于一个电话簿
-
例如,客户端需要拿到AMS服务
- 客户端先告诉管家,要找AMS
- 管家将AMS的代理对象返回给客户端
- 客户端通过AMS的代理对象与AMS进行通信
-
-
-
服务端:进入Stub调用onTransact方法,这个里面就会去调用对应的方法
-
怎么找的?
-
一般情况下,需要去调用另外一个类或者框架的方法,使用全类名,一个String但是占用的位置大,那么进行优化
-
优化手段:因为CS两端生成的AIDL代码都是一样的(协议是一样的),那么我们就可以使用一个代号来标识方法
- stub内部是使用static final int 来玩的
-
通过一个switch-case,根据传入的code来玩
- 如果有返回结果,那么写入reply包
- 没有就不写
-
-
flag为0:同步,mRemote.transact(Stub)
-
在客户端调用,客户端等待服务端的返回,如果说在主线程中会导致ANR
-
一般都是同步调用,也可以设置成异步调用
-
将flags设置为one way
-
-
-
-
比如客户端想要与AMS通信
- AMS 在创建后会在Service Manager中去注册的
- 客户端首先找到Service Manager
- Service Manager找到AMS并将AMS的代理对象返回给客户端
-
为什么要有Service Manager
- 就像电话本一样,没有这个,那么我要去记录所有的服务
- 就像一个中介,它还是一个中介
-
那么客户端是怎么去找到Service Manager的?
-
通过handle = 0的一个句柄,这个是固定的
- 通过注册来的
-
而WMS,AMS的句柄是不定的
- 当AMS创建后向Service Manager中去注册;将自己的名字啊,handle 啊告诉它;
-
-
SystemManager与Service Manager
-
前面那个就是进程:相当于app
-
后面那个是进程里面的服务:相当于activity/service
-
-
-
-
四大组件底层通信机制:Binder
-
概述:客户端调用bindService之后系统是如何回调到onServiceConnected的?
- 怎么跨进程通信的?
-
调用流程思路:
-
示意图:
-
详细流程:
-
客户端调用bindService,进入AMS中执行bindService
-
Servicemanager.getService("activity")
- 这个Activity就是AMS
-
客户端拿到AMS的代理对象IBinder,跨进程
- 调用AMS中的bindService方法,进入AMS
- 四大组件基于AMS
-
AMS中干了什么?
- 如果服务端APP进程未创建,那么AMS就会创建服务端进程
- AMS调用scheduleBindService(启动服务)---》handleBindeService
-
-
handleBindService 中干了什么?跨进程
-
拿到map集合(mService)中的Service对象(s--->RemoteService)
-
在handleCreateService中创建服务
- 通过反射进行创建
- 接着调用service.onCreate()
- 最后将创建的服务放入map集合mService
-
-
调用s.onBind()拿到IBinder对象
-
通过publishService将IBinder对象返回回去
-
-
-
示意图:bindService(intent)---》回调onServiceConnected(iBinder对象),就是这个箭头
-
过程:
- 客户端调用bindService
- ServiceManager.getService(AMS),返回AMS的IBinder对象
-
Intent为什么不能传递大数据
-
现象:
-
Intent传递数据不能超过一MB,Intent底层是Binder通信
-
其实不能超过1MB - 8k,等于也不行;
- 需要预留出打包的空间;
-
-
那么这个Binder在哪里限制的大小?
- 在内存映射的哪里;
- Binder中的驱动功能,对应的驱动文件
-
源码分析:ProcessStart.cpp
-
openDriver(driver) :打开驱动文件,得到文件描述符
- 这个driver就是"/dev/binder"
-
mmap:将这个文件描述符映射到"/dev/binder"上
-
注意此时就会指定大小:BINDER_VM_SIZE;
-
宏定义决定了1MB(1 * 1024 * 1024)-8k(2页的大小)
-
这是同步的大小,异步是同步/2(因为内存不能一致给)
-
为什么设置成这个样子?
- 首先不能设置成无限大
- 其次Google工程师决定的
-
- Binder通信由共享内存做了局限;
- Binder线程池大小:15
-
-
-
源码分析Binder.c
-
mmap就会调用底层的内核代码(binder_map)
-
将服务端的一块区域与内核的一块区域,同时指向同一块物理区域
-
流程:
- 创建一块物理区域
- 将服务端区域(虚拟区域)指向物理区域
- 将内核区域指向物理区域
-
给的大小:
- 最开始不是1MB - 8K
- 最开始注册服务的时候,只给一页(4kb),有需要再给你
- 一页4kb就是最小单位
-
-
比较重要的方法:
-
binder_ioctl:write,read;
- 客户端写服务端读;或者换着来
-
binder_map:MMAP,之前在这个里面规定了异步就是同步的一半,之前是写的很长,后面去看的时候;这个减半操作封装在了一个新的方法里面了
-
-
四大组件:一般都是同步,异步很少;
-
关于1MB - 8k
- 直接写这么大,其实是用不了的,log日志会提示尺寸太大
- 因为数据会打一层包,在传输数据之前要预留一点空间出来;
-