引言
在 Android 应用、Android 系统开发的时候,相信很多人都听过 Binder 的概念,而且无意间就用到了 Binder 机制,例如:写一个应用打开手电筒功能,某个应用启动服务等
这些动作都涉及到一个概念:进程间通信;Android 中的每个应用都是独立的进程,都有自己虚拟内存,两个进程之间不能互相访问数据;所以在 Android 中,应用进程间互相访问数据,我们最为常用的通信方式就是 Binder
然而 Binder 通信实际上是同步而不是异步,但是在实际使用时,是设计成客户端同步而服务端异步
相信大家有看过 Framework 层的各 service 类、 java 源码便会知道:在客户端调用服务端的各种方法时,通常会传递一个 Binder 过来,该 Binder 对象用于服务端做异步回调,而服务端本身会使用 Handler或队列的方式做成异步处理
在 Android 中,系统 service 是作为"管理者"的身份存在的:像 Ams(ActivityManagerService),它并不创建 Activity,创建 Activity 的是客户端的 ActivityThread,但是每当 ActivityThread 想创建一个 Acitivty,就必须告诉 Ams,Ams 会进行调度该 Acitivty 的创建
更简单的一个例子:Toast 和 NotificationManagerService,Toast 负责弹出窗口的创建显示和隐藏,而何时显示跟何时隐藏却是由 NotificationManagerService 决定的
我们知道从 Android 8.0 开始,Binder 机制,被拆分成了 Binder(System 分区 进程间通信)、HwBinder(支持 System/Vendor 分区进程间通信)、VndBinder(Vendor 分区进程间通信)
下面同步及异步任务流程
服务收到同步请求后,会将该同步任务添加至 proc->todo
- 服务器收到异步请求后,会根据 has_async_transaction 的状态:为0则添加至 proc->todo 并置为1;为1则添加至 node->async_todo
- 服务处理完成请求后,会释放 buffer 数据空间即发送 BC_FREE_BUFFER 至内核,内核收到后会根据 node->async_todo 是否为空:不为空则从 node->async_todo 中取出一个任务添加至 thread->todo 队列;为空则将 has_async_transaction 置为0
同步请求和异步请求高并发
- 同步请求的任务全部添加在 proc->todo 中;异步请求则最多只有一个任务在 proc->todo 队列中,其余的全部放在 node->async_todo中缓存 ;然后 proc 依次取出一个任务并分配空闲线程来处理
- 假设 proc 的线程 thread分 配到的任务首先处理结束,在任务处理结束后需要释放 buffer 数据空间,由于是高并发请求则 node->async_todo 肯定不为空,这时候会将缓存在 node->async_todo 异步队列中的任务依次取出一个,再添加到 thread->todo 中
- 任务执行优先从 thread->todo 中取出,取出后 thread->todo 即为空,这时才从 proc->todo 中取任务
这样做的目的是为了防止过多同步任务导致异步任务被饿死(若优先从 proc->todo 中取任务,则 thread->todo 中的异步任务必须等到 proc->todo 中的所有任务执行完成,那在大量同步任务的情况下,该异步任务就没有机会处理而被饿死)
为什么要选择 Binder 呢?
首先 Android 使用得最多也最被认可的还是 Binder机制 ;为什么会选择 Binder 来作为进程之间的通信机制呢?是因为 Binder 更加简洁和快速,消耗的内存资源更小吗?不错,这些也正是 Binder 的优点
当然,也还有很多其他原因,比如传统的进程间通信可能会增加进程的开销,而且有进程过载和安全漏洞等方面的风险,Binder正好能解决和避免这些问题
Binder 主要功能
- 用驱动程序来推进进程间的通信
- 通过共享内存来提高性能
- 为进程请求分配每个进程的线程池
- 针对系统中的对象引入了引用计数和跨进程的对象引用映射( 进程间同步调用)
Binder 通信模型
Binder模型的4类角色:
- Binder驱动
- ServiceManager
- Serve
- Client
Binder 机制的本质上是为了实现 IPC(Inter-Process Communication),即 Client 和 Server 之间的通信
其中 Server,Client,ServiceManager 运行于用户空间,Binder 驱动运行于内核空间
这四个角色的关系和互联网类似:
- Server 是服务器
- Client 是客户终端
- ServiceManager 是域名服务器(DNS)
- 驱动是路由器
异步任务实现方式及实现原理
Thread
创建一个 Thread 是最简单直接的方式,在 Thread 内部去执行耗时的操作,实现方式如下:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
//执行耗时操作
}
});
t.start();
上面仅仅是在 Thread 内部执行耗时的操作,如果在执行玩耗时操作后,需要 UI 来进行更新,那应该如何操作呢?接下来继续看:
Thread + Handler
Android 提供了Handler机制来进行线程之间的通信,可以使用异步方式:Thread + handler 来进行异步任务执行及UI更新,实现方式如下:
new Thread() {
public void run() {
//执行耗时操作.....
//更新UI
mUIHandler.sendMessage(mUIHandler.obtainMessage(UIHandler.MSG_UPDATE_UI,2));
}
}.start();
//UI线程Handler
private static class UIHandler extends Handler {
private final static int MSG_UPDATE_UI = 1;
private final WeakReference mFragment;
private UIHandler(HandlerFragment fragment) {
mFragment = new WeakReference<>(fragment);
}
@Override
public void handleMessage(Message msg) {
HandlerFragment fragment = mFragment.get();
switch (msg.what) {
case MSG_UPDATE_UI:
fragment.updateUI((int)msg.obj);
break;
default:
break;
}
}
}
从以上可以看到,在 Thread 内部执行耗时操作后,然后调用 Handler 来通知主线程更新 UI
这样的话,每次执行耗时请求,都需要new一个Thread,会导致开销过大,可以通过以下方式来进行改进:
子线程内部创建 Looper + Handler
new Thread() {
public void run() {
Looper.prepare();
mNoUIHandler = new NoUIHandler(handlerFragment);
Looper.loop();
}
}.start();
//工作线程Handler来执行耗时操作
private static class NoUIHandler extends Handler {
private final static int MSG_HANDLE = 1;
private final WeakReference mFragment;
private NoUIHandler(HandlerFragment fragment) {
mFragment = new WeakReference<>(fragment);
}
@Override
public void handleMessage(Message msg) {
HandlerFragment fragment = mFragment.get();
switch (msg.what) {
case MSG_HANDLE:
fragment.handleMsg();
break;
default:
break;
}
}
}
private void handleMsg() {
//执行耗时操作......
//通知主线程更新UI
mUIHandler.sendMessage(mUIHandler.obtainMessage(UIHandler.MSG_UPDATE_UI,1));
}
//启动耗时操作
mNoUIHandler.sendEmptyMessage(NoUIHandler.MSG_HANDLE);
通过以下改善,在 Thread 内部创建 Looper,然后创建 Handler,这样的话 Thread 就不会退出,就不需要频繁创建 Thread,此时 Handler 使用的是子线程创建的 looper,从而 Handler 消息处理(耗时操作)就在子线程里面,执行完耗时操作,通知主线程来更新 UI;
这样的话,Thread一直不退出,是不是会造成内存泄露呢?
如果不再使用的话,直接通过 mNoUIHandler.getLooper().quitSafely()来让 Looper.loop()结束循环, Thread 也就退出了
总结
Thread + Handler 深入具有实现简单的优点,但代码规范性较差,不易维护,而且每次操作都会开启一个匿名线程,系统开销较大
本文主要讲解了 Binder 通信实际上是同步而不是异步的主要原理,想要往向更深入学习 Binder 难免需要寻找很多的学习资料辅助,我在这里推荐网上整合的一套《 Android Binder 学习手册》;鉴于出自大佬之手,可以帮助到大家,能够少走些弯路
篇幅原因,就不在这里为大家赘述了,有需要的小伙伴:可点击此处查看获取方式或者简信发送"进阶"即可领取这份《Android Binder 学习手册》,助你早日成为底层原理大师!
最后大家如果觉得手册内容有用的话,可以点赞分享一下哦~
网友评论