Android开发经验谈Android技术知识Android开发

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

2022-10-10  本文已影响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

同步请求和异步请求高并发

这样做的目的是为了防止过多同步任务导致异步任务被饿死(若优先从 proc->todo 中取任务,则 thread->todo 中的异步任务必须等到 proc->todo 中的所有任务执行完成,那在大量同步任务的情况下,该异步任务就没有机会处理而被饿死)

为什么要选择 Binder 呢?

首先 Android 使用得最多也最被认可的还是 Binder机制 ;为什么会选择 Binder 来作为进程之间的通信机制呢?是因为 Binder 更加简洁和快速,消耗的内存资源更小吗?不错,这些也正是 Binder 的优点

当然,也还有很多其他原因,比如传统的进程间通信可能会增加进程的开销,而且有进程过载和安全漏洞等方面的风险,Binder正好能解决和避免这些问题

Binder 主要功能

Binder 通信模型

Binder模型的4类角色:

Binder 机制的本质上是为了实现 IPC(Inter-Process Communication),即 Client 和 Server 之间的通信

其中 Server,Client,ServiceManager 运行于用户空间,Binder 驱动运行于内核空间

这四个角色的关系和互联网类似:

异步任务实现方式及实现原理

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

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

上一篇下一篇

猜你喜欢

热点阅读