Android-你真的懂AIDL的oneway嘛?

AIDL是Android Interface definition language的缩写,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。android提供了很多进程间通信的组件,像Activity、BroadcastReceiver和ContentProvider都可以实现进程间的通信。

为什么还要用AIDL这个东西呢?

有开发过蓝牙或者WIFI应用的朋友肯定都知道,要去操作它必须先获得一个管理类,比如WIFI的管理类是WifiManager,通过getSystemService(Context.WIFI_SERVICE)就可以得到wifi的管理权限,这个服务「Context.WIFI_SERVICE」提供了很多的方法可以让用户去操作WIFI,比如打开wifi可以调用setWifiEnabled(true)方法。

WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);  WifiManager wifiManager = (WifiManager)getSystemService("wifi");

那这个Manager到底做了什么工作呢?

其实这个Manager只是一个管理类,真正干活的另有其人,是一个叫WifiService的系统服务。在Android系统中有很多的Manager,wifi的管理类叫WifiManager,蓝牙的管理类叫BluetoothManager,但是,只要有xxxManager.java,就会有Ixxx.aidl,并且有xxxService.java。

AIDL的作用是什么?

aidl类是实现Manager和Service通信的桥梁。

我们知道了AIDL的作用,如果上面的讲解还不能让你知道AIDL是做什么的,我建议你百度会快一些,可以认为是中间接口,但是中间接口不只是单纯的作为传话筒的作用,比如HAL,你认为他只是传话筒吗?并不是,知道有层次概念后,对你的学习会帮助巨大,当然了,如果你是一个架构师,知道层次的概念后,对你的开发帮助更大。

标题说的oneway是代码里的一个变量判断,通过这个变量判断来决定binder的调用,我转载的这篇文章作者对binder不仅仅是喜爱,更是有深入的研究。

以下是正文,文章通过例子和代码告诉你binder系统是如何判断的,各位兄台,请笑纳~


1 前言

用AIDL的人应该都知道下面代码中start和stop方法定义成oneway代表这个Binder接口是异步调用。

interface IPlayer {oneway void start();//异步,假设执行2秒oneway void stop();//异步,假设执行2秒int getVolume();// 同步,假设执行1秒
}

1.1 什么是异步调用?

举个例子:假如Client端调用IPlayer.start(),而且Server端的start需要执行2秒,由于定义的接口是异步的,Client端可以快速的执行IPlayer.start(),不会被Server端block住2秒。

1.2 什么是同步调用?

举个例子:假如Client端调用IPlayer. getVolume(),而且Server端的getVolume需要执行1秒,由于定义的接口是同步的,Client端在执行IPlayer. getVolume()的时候,会被Server端block住1秒。

1.3 为什么会有同步调用和异步调用?

细心的读者已经发现了,其实一般使用异步调用的时候,Client并不需要得到Server端的执行Binder服务的状态或者返回值,这时候使用异步调用,可以有效的提高Client执行的效率。

2 提问

好像很多人都明白前面讲的意思,我就出几个问题考考大家

假设进程A中有如下两个Binder服务IPlayer1和IPlayer2,这两个服务都有两个异步的接口start和stop。

interface IPlayer1 {oneway void start();//异步,执行2秒oneway void stop();//异步,执行2秒
}
interface IPlayer2 {oneway void start();//异步,执行2秒oneway void stop();//异步,执行2秒
}

2.1 问题1

如果进程B和进程C同一时刻分别调用IPlayer1.start()和IPlayer2.start(),请问进程A能否同时响应这两次Binder调用并执行?

正确答案:可以同时执行。

2.2 问题2

如果进程B和进程C同一时刻分别调用IPlayer1.start()和IPlayer1.start(),请问进程A能否同时响应这两次Binder调用并执行?

正确答案:不能同时执行,需要一个一个排队执行。

2.3 问题3

如果进程B和进程C同一时刻分别调用IPlayer1.start()和IPlayer1.end(),请问进程A能否同时响应这两次Binder调用并执行?

正确答案:不能同时执行,需要一个一个排队执行。

如果回答正确并且知道原因的朋友,这个文章就可以不看了。如果回答错误或者蒙对了不清楚原因的朋友,请继续阅读文章帮你理解这些问题。

3 代码分析

话不多说,先看源码,我们首先来看看oneway的Binder调用在Binder Driver中的逻辑

/*** binder_proc_transaction() - sends a transaction to a process and wakes it up* @t:      transaction to send* @proc:   process to send the transaction to* @thread: thread in @proc to send the transaction to (may be NULL)*/
static bool binder_proc_transaction(struct binder_transaction *t,struct binder_proc *proc,struct binder_thread *thread)
{//找到Server端的对应Binder服务在Binder驱动中对应的对象binder_nodestruct binder_node *node = t->buffer->target_node;//判断这次Binder调用是不是onewaybool oneway = !!(t->flags & TF_ONE_WAY);//初始化为false,用于标记当前Server端的对应Binder服务是否正在执行oneway的方法bool pending_async = false;binder_node_lock(node);//oneway == trueif (oneway) {if (node->has_async_transaction) {//第2次oneway调用执行这里//发现对应Binder服务正在执行oneway的方法,设置pending_async为truepending_async = true;} else {//第1次oneway调用执行这里//发现对应Binder服务没有执行oneway的方法,设置has_async_transaction为1node->has_async_transaction = 1;}}binder_inner_proc_lock(proc);//如果发现Server端已经死亡,就直接返回了,正常不会执行if (proc->is_dead || (thread && thread->is_dead)) {binder_inner_proc_unlock(proc);binder_node_unlock(node);return false;}//oneway的调用thread为空,第1次oneway调用,pending_async为falseif (!thread && !pending_async)//第1次oneway调用会找到一个空闲的Server端线程,用于响应这次oneway调用thread = binder_select_thread_ilocked(proc);if (thread) {//第1次oneway调用,thread不为空,直接把这次Binder work放到thread的工作队列去执行binder_enqueue_thread_work_ilocked(thread, &t->work);} else if (!pending_async) {binder_enqueue_work_ilocked(&t->work, &proc->todo);} else {//第2次oneway调用,thread为空,pending_async为true,//这次Binder work放到Binder Node的async_todo队列中,不会立刻执行binder_enqueue_work_ilocked(&t->work, &node->async_todo);}if (!pending_async)//第1次oneway调用,thread不为空,所以需要唤醒thread执行工作队列中的Binder workbinder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */);binder_inner_proc_unlock(proc);binder_node_unlock(node);return true;
}

对应到我们的三个问题,我们首先有这样子的前提,进程A中有两个Binder Server端IPlayer1和IPlayer2,也就是在Binder驱动中有两个binder node的结构体,并且进程A的Binder线程池处于空闲的状态。还有一点要明确的是,就算进程B和进程C同时发起Binder调用,但是在Binder驱动中还是有先后顺序,因为有一把锁binder_inner_proc_lock(proc)。

问题1解析:

因为进程B和进程C分别调用两个Binder服务,也就是两个binder node,所以进程B和进程C都会走如下的代码,也就是说进程A会有两个线程分别处理进程B的IPlayer1.start()和进程C的IPlayer2.start(),所以答案是同时执行

static bool binder_proc_transaction(struct binder_transaction *t,struct binder_proc *proc,struct binder_thread *thread)
{struct binder_node *node = t->buffer->target_node;bool oneway = !!(t->flags & TF_ONE_WAY);bool pending_async = false;binder_node_lock(node);if (oneway) {//不管是是进程B还是进程C,因为不是同一个binder_node,所以都是走false的逻辑if (node->has_async_transaction) {//不执行} else {node->has_async_transaction = 1;}}binder_inner_proc_lock(proc);if (!thread && !pending_async)//oneway调用会找到一个空闲的Server端线程,用于响应这次oneway调用thread = binder_select_thread_ilocked(proc);if (thread) {//oneway调用,thread不为空,直接把这次Binder work放到thread的工作队列去执行binder_enqueue_thread_work_ilocked(thread, &t->work);} else if (!pending_async) {//不执行} else {//不执行}if (!pending_async)//oneway调用,thread不为空,所以需要唤醒thread执行工作队列中的Binder workbinder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */);binder_inner_proc_unlock(proc);binder_node_unlock(node);return true;
}
问题2解析:

我们假设先处理进程B的IPlayer1.start()的调用,进程B会执行和问题1中描述的代码一样的操作,唤醒进程A中的一个线程,处理这次进程B的IPlayer1.start()调用。

但是进程C的IPlayer1.start()调用逻辑就不一样了,应该是下面这个逻辑,也就是说进程A不会立刻处理进程C的IPlayer1.start()的调用。所以答案就是不能同时执行,需要一个一个排队执行。

static bool binder_proc_transaction(struct binder_transaction *t,struct binder_proc *proc,struct binder_thread *thread)
{struct binder_node *node = t->buffer->target_node;bool oneway = !!(t->flags & TF_ONE_WAY);bool pending_async = false;binder_node_lock(node);//oneway == trueif (oneway) {if (node->has_async_transaction) {//因为是进程C和进程B是同一个binder_node,进程B已经将has_async_transaction设置truepending_async = true;} else {//不执行}}binder_inner_proc_lock(proc);if (thread) {//不执行} else if (!pending_async) {//不执行} else {//这次Binder work放到binder_node的async_todo队列中,不会立刻执行binder_enqueue_work_ilocked(&t->work, &node->async_todo);}binder_inner_proc_unlock(proc);binder_node_unlock(node);return true;
}

那什么时候处理进程C的IPlayer1.start(),看下面代码,简单说就是会在处理完进程B的IPlayer1.start()之后,在释放进程B调用IPlayer1.start()申请的buffer的时候,处理进程C的IPlayer1.start()。

        case BC_FREE_BUFFER: {//准确释放进程B申请的bufferif (buffer->async_transaction && buffer->target_node) {struct binder_node *buf_node;struct binder_work *w;//先拿到这块buffer处理的binder node,也就是IPlayer1对应的binder nodebuf_node = buffer->target_node;binder_node_inner_lock(buf_node);//检查一下buf_node是否有未处理的oneway的binder workw = binder_dequeue_work_head_ilocked(&buf_node->async_todo);if (!w) {//不执行buf_node->has_async_transaction = 0;} else {//如果有未处理完的oneway的binder work,就将binder node保存的async_todo全部添加到进程A的todo。binder_enqueue_work_ilocked(w, &proc->todo);//唤醒一个线程去处理todo中的binder work,也就是进程C的IPlayer1.start()binder_wakeup_proc_ilocked(proc);}binder_node_inner_unlock(buf_node);}//释放进程B申请的buffertrace_binder_transaction_buffer_release(buffer);binder_transaction_buffer_release(proc, buffer, NULL);binder_alloc_free_buf(&proc->alloc, buffer);break;}
问题3解析:

虽然进程B和进程C同一时刻分别调用IPlayer1.start()和IPlayer1.end()两个不同的方法,但是两个进程调用的Server端都是IPlayer1,也就是binder node是同一个,所以答案和问题2一样。

4 思考一个问题

假如一个进程B,在短时间内,例如一秒内,调用1000次进程A的IPlayer1.start()会发生什么。
第1次IPlayer1.start():唤醒进程A的一个线程处理IPlayer1.start(),两秒之后完成
第2-1000次IPlayer1.start():发现IPlayer1对应的binder node正在处理一个oneway的方法,会把所有2~1000次的调用放到binder node的async_todo队列中,等第一次IPlayer1.start()执行完成之后,释放buffer的时候,才能去统一处理这些async_todo中保存的第2-1000次。

那么问题就来了,虽然第2-1000次的调用不会立刻执行,但是已经在进程A中申请了所有的2~1000次IPlayer1.start()所需要的buffer,一个zygote进程A,最大oneway请求的buffer上限为(1MB -8KB)/2 = 508KB,不懂的可以看[007]一次Binder通信最大可以传输多大的数据?这个博客,假设一次IPlayer1.start(),需要申请1KB的buffer,也就意味这在第509次IPlayer1.start()的时候,无法申请到buffer从而导致IPlayer1.start()的Binder调用失败。

在[011]一个看似是系统问题的应用问题的解决过程中解决的就是这个问题。

5 小结

Binder机制是一个非常牛逼的机制,里面有很多小的细节值得我们去深挖,只有完全理解Binder驱动,才能从微观的角度去解决宏观的问题。

点击阅读原文,在PC端获得更好的阅读体验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467979.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Navicat Premium 实用快捷键

ctrlq 打开查询窗口ctrl/ 注释sql语句ctrlshift / 解除注释ctrlr 运行查询窗口的sql语句ctrlshiftr 只运行选中的sql语句F6 打开一个mysql命令行窗口ctrll 删除一行ctrln 打开一个新的查询窗口ctrlw …

妖怪手帐获取服务器信息失败,妖怪手账闪退怎么办 卡在加载页面解决办法

妖怪手账闪退怎么办?很多玩家下载了游戏之后发现一直卡在加载页面,完全进不去怎么办呢?有什么办法可以解决这种情况呢?接下来就跟随小编一起来看看吧!妖怪手账闪退怎么办 卡在加载页面解决办法很多玩家都发现自己下载游…

binder,hwbinder,vndbinder之间的关系

昨天发的那篇技术文之后,这篇文章我觉得可以给大家更加耳目一新,特别是因为其中的例子和白话文。昨天文章如下Android-你真的懂AIDL的oneway嘛?以下是正文1 前言先复制一段来自于android官方文档的文字https://source.android.google.cn/devi…

modbus协议手册_20种PLC元件编号和Modbus编号地址对应表

曾经做过单片机和以下20种PLC的Modbus RTU串口通信,现将这20种PLC输入、输出和寄存器元件与Modbus编号地址对应表分享出来。三菱FX3G-40MR/ES-A、西门子S7-200 CPU226 AC/DC/RLY、欧姆龙CP1H-X40DR-A、松下AFPX-C40R、台达DVP-12SA2、信捷XC5-48、永宏FBs-40MC、产电…

写代码获取全国疫情地图

今天在朋友圈看到一个同学写的python获取全国疫情地图,我觉得挺有意思的,这个分享给大家,如果喜欢python的,可以通过这个作为入门手段。1、安装python看这个链接来安装,非常的小白https://www.liaoxuefeng.com/wiki/10…

八大排序算法的python实现(三)冒泡排序

代码: #coding:utf-8 #author:徐卜灵 #交换排序.冒泡排序 L [1, 3, 2, 32, 5, 4] def Bubble_sort(L):for i in range(len(L)):for j in range(i1,len(L)):if L[i]>L[j]:# temp L[j]# L[j] L[i]# L[i] tempL[i], L[j] L[j], L[i]#交换顺序print L Bubble_so…

泛型类有什么作用_3 分钟带你彻底搞懂 Java 泛型背后的秘密

优质文章,及时送达作者 | 的一幕来源 | www.jianshu.com/p/dd34211f2565这一节主要讲的内容是java中泛型的应用,通过该篇让大家更好地理解泛型,以及面试中经常说的泛型类型擦除是什么概念,今天就带着这几个问题一起看下:举一个简单…

实现Linux系统的回收站

今天偶然看到一个有意思的shell脚本,用脚本实现Linux系统下的回收站,推荐给大家。今天我们利用简单的shell脚本实现Linux系统下的回收站机制。先提供脚本代码[rootqll251 ~]# vim /bin/delete1 #! /bin/bash2 [ ! -d /.recycle ] && mkdir -v /.…

RobotFramework自动化测试框架的基础关键字(五)

1.1.1 Run Keyword If判断的使用 Run Keyword If是一个常用的用来做逻辑判断的关键字,意思是如果满足了某一个判断条件,然后就会执行关键字,我们对list3中放入0,1,2三个元素,然后遍历list3,判断当取到元素为0时…

python按位翻转_Python成为专业人士笔记-位操作符

“专业人士笔记”系列目录:创帆云:Python成为专业人士笔记--强烈建议收藏!每日持续更新!​zhuanlan.zhihu.com按位操作符直接操作二进制的字符串,这些是最基本的操作,并直接由中央处理器所支持。在处理设备…

我看三国有感而发

三国到现在已经过去一千多年了,关羽先生应该还淹没在失去荆州的痛苦中,关羽先生出现的时候充满了一股傲气,这种站着向上仰望30度角的男人非常稀有,应该是几百年才出现一个,而且还要生于乱世,可惜啊可惜&…

pil库修改图片大小_Gvcode库:一个更简单的、华人开源的、自动生成验证码的python库...

1 说明:1.1 gvcode全称:graphic-verification-code。1.2 基本介绍,一秒搞懂。1.3 并对源码进行修改一个小bug,教您如何修改源码,解决bug。2 准备:2.1 官网:https://pypi.org/project/graphic-ve…

iOS获取某个日期后n个月的日期

一、给一个时间,给一个数,正数是以后n个月,负数是前n个月;1 -(NSDate *)getPriousorLaterDateFromDate:(NSDate *)date withMonth:(NSInteger)month2 3 {4 5 NSDateComponents *comps [[NSDateComponents alloc] init];6 …

Android-AB系统OTA升级介绍

什么是OTA升级?OTA是Over-the-Air的简称,OTA升级可以理解为用户正常使用过程中进行升级,OTA 升级旨在升级基础操作系统、系统分区上安装的只读应用和/或时区规则。什么是Android AB系统更新A/B 系统升级,也叫做无缝更新&#xff0…

python输出进度条 tqdm_python-tqdm进度条

Tqdm 进度条可视化模块2018-12-04 14:34:25 使用python Tqdm进度条库让你的python进度可视化 Tqdm在阿拉伯语表示进步,在西班牙语中表示我非常爱你.是一个快速,可扩展的Python进度条, ...Python字符进度条Python字符进度条 看看这个神奇的module from tqdm import trange from …

看完少年的你,想到少年的我

别人喜欢听故事,所以我就讲故事,我可以不是现在的我,但是我还是原来的我,我从一个很小的地方来,我小时候,家里赚钱很难,读书是一件很苦的事情,但是呢,读书又是一件非常光…

伤感网络验证系统_知网查重报告单能造假?验证报告单真伪时,知网只给了这一个办法...

这是论文屋推送的第5篇文章,论文代发寻渠道,就到论文屋。现在的修图功能太强大了有木有?除了专业的一点的AdobePhotoshop之外,任何的一款修图软件甚至手机软件都可以更改一张图的任何地方,最重要的是,技术高…

jQuery: 整理3---操作元素的内容

1.html("内容") ->设置元素的内容&#xff0c;包含html标签&#xff08;非表单元素&#xff09; <div id"html1"></div><div id"html2"></div>$("#html1").html("<h2>上海</h2>") …

安卓系统监听system property值?

预备知识-什么是system propertysystem property是系统属性&#xff0c;以key-value格式保存。可以通过以下方式读取和修改system property的值&#xff1a;1.adbadb shell getprop <key> adb shell setprop <key> <value>2.C/Cint property_get(const char…

为什么全天坐在电脑前会让你精疲力竭

Tips 原文作者&#xff1a;Katie Heaney 原文地址&#xff1a;Why Sitting at Your Computer All Day Can Wipe You Out 像大多数日子一样&#xff0c;我昨天大部分时间坐在我认为是公寓最美丽的一角&#xff0c;一直在笔记本电脑打字。 我提交了一个故事&#xff0c;转述了几个…