Android 进程间通信:浅析“Binder 通信的同步及异步任务流程“

引言

在 Android 应用、Android 系统开发的时候,相信很多人都听过 Binder概念,而且无意间就用到了 Binder 机制,例如:写一个应用打开手电筒功能,某个应用启动服务等

这些动作都涉及到一个概念:进程间通信Android 中的每个应用都是独立进程,都有自己虚拟内存两个进程之间不能互相访问数据;所以在 Android 中,应用进程间互相访问数据,我们最为常用的通信方式就是 Binder

然而 Binder 通信实际上是同步而不是异步,但是在实际使用时,是设计成客户端同步而服务端异步

相信大家有看过 Framework 层的各 service 类java 源码便会知道:在客户端调用服务端的各种方法时,通常会传递一个 Binder 过来,该 Binder 对象用于服务端异步回调,而服务端本身会使用 Handler或队列的方式做成异步处理

在 Android 中,系统 service 是作为 “管理者” 的身份存在的:像 Ams(ActivityManagerService),它并不创建 Activity,创建 Activity 的是客户端的 ActivityThread,但是每当 ActivityThread 想创建一个 Acitivty,就必须告诉 AmsAms 会进行调度该 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 学习手册》,助你早日成为底层原理大师!

最后大家如果觉得手册内容有用的话,可以点赞分享一下哦~

猜你喜欢

转载自blog.csdn.net/m0_70748845/article/details/127247264