美文网首页Android开发经验谈Android技术知识Android开发
Android 进程间通信:浅析"Binder 通信的同步及异步

Android 进程间通信:浅析"Binder 通信的同步及异步

作者: 程序老秃子 | 来源:发表于2022-10-10 16:41 被阅读0次

    引言

    在 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 学习手册》,助你早日成为底层原理大师!

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

    相关文章

      网友评论

        本文标题:Android 进程间通信:浅析"Binder 通信的同步及异步

        本文链接:https://www.haomeiwen.com/subject/myuoartx.html