Ⅰ SurfaceFlinger 原理分析
SurfaceFlinger是android multimedia的一个部分,在Android 的实现中它是一个service,提供系统范围内的surface composer功能,它能够将各种应用程序的2D、3D surface进行组合。
每个应用程序可能对应着一个或者多个图形界面,而每个界面我们就称之为一个surface ,或者说是window ,在上面的图中我们能看到4 个surface ,一个是home 界面,还有就是红、绿、蓝分别代表的3个surface ,而两个button 实际是home surface 里面的内容。我们需要考虑一下情况:
在实际中对这些Surface 进行merge 可以采用两种方式,一种就是采用软件的形式来merge ,还一种就是采用硬件的方式,软件的方式就是我们的 SurfaceFlinger ,而硬件的方式就是 Overlay 。
因为硬件merge 内容相对简单,我们首先来看overlay 。以IMX51 为例子,当IPU 向内核申请FB 的时候它会申请3 个FB ,一个是主屏的,还一个是副屏的,还一个就是Overlay 的。 简单地来说,Overlay就是我们将硬件所能接受的格式数据和控制信息送到这个Overlay FrameBuffer,由硬件驱动来负责merge Overlay buffer和主屏buffer中的内容。
一般来说现在的硬件都只支持一个Overlay,主要用在视频播放以及camera preview上,因为视频内容的不断变化用硬件Merge比用软件Merge要有效率得多,下面就是使用Overlay和不使用Overlay的过程:
surfaceFlinger 只是负责 merge Surface 的控制,比如说计算出两个 Surface 重叠的区域,至于 Surface 需要显示的内容,则通过 skia,opengl 和 pixflinger 来计算。
创建过程
SurfaceFlinger 是一个线程类,它继承了 Thread 类。当创建 SurfaceFlinger 这个服务的时候会启动一个 SurfaceFlinger 监听线程,这个线程会一直等待事件的发生,比如说需要进行 sruface flip ,或者说窗口位置大小发生了变化等,一旦产生这些事件,SurfaceComposerClient 就会通过 IBinder 发出信号,这个线程就会结束等待处理这些事件,处理完成以后会继续等待,如此循环。
SurfaceComposerClient 和 SurfaceFlinger 是通过 SurfaceFlingerSynchro 这个类来同步信号的,其实说穿了就是一个条件变量。监听线程等待条件的值一旦变成 OPEN 就结束等待并将条件置成 CLOSE 然后进行事件处理,处理完成以后再继续等待条件的值变成 OPEN ,而 Client 的Surface 一旦改变就通过 IBinder 通知 SurfaceFlinger 将条件变量的值变成 OPEN ,并唤醒等待的线程,这样就通过线程类和条件变量实现了一个动态处理机制。
窗口状态变化的处理是一个很复杂的过程,SurfaceFlinger 只是执行 Windows Manager 的指令,由 Windows manager 来决定什么是偶改变大小、位置、透明度、以及如何调整layer 之间的顺序, SurfaceFlinger 仅仅只是执行它的指令。
普通的Android控件,例如TextView、Button和CheckBox等,它们都是将自己的UI绘制在宿主窗口的绘图表面之上,这意味着它们的UI是在应用程序的主线程中进行绘制的。由于应用程序的主线程除了要绘制UI之外,还需要及时地响应用户输入,否则系统就会认为应用程序没有响应了。而对于一些游戏画面,或者摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘制。这时候就必须要给那些需要复杂而高效UI的视图生成一个独立的绘图表面,以及使用一个独立的线程来绘制这些视图的UI。
SurfaceFlinger服务运行在Android系统的System进程中,它负责管理Android系统的帧缓冲区(Frame Buffer)。Android应用程序为了能够将自己的UI绘制在系统的帧缓冲区上,它们就必须要与SurfaceFlinger服务进行通信。
在APP端执行draw的时候,数据很明显是要绘制到APP的进程空间,但是视图窗口要经过SurfaceFlinger图层混排才会生成最终的帧,而SurfaceFlinger又运行在另一个独立的服务进程,那么View视图的数据是如何在两个进程间传递的呢,普通的Binder通信肯定不行,因为Binder不太适合这种数据量较大的通信,那么View数据的通信采用的是什么IPC手段呢?答案就是共享内存,更精确的说是匿名共享内存。共享内存是linux自带的一种IPC机制,Android直接使用了该模型,不过做出了自己的改进,进而形成了Android的匿名共享内存(Anonymous Shared Memory-Ashmem)。通过Ashmem,APP进程同SurfaceFlinger共用一块内存,如此,就不需要进行数据拷贝,APP端绘制完毕,通知SurfaceFlinger端合成,再输出到硬件进行显示即可。
在每一个Android应用程序与SurfaceFlinger服务之间的连接上加上一块用来传递UI元数据的匿名共享内存,这个共享内存就是 SharedClient
在每一个SharedClient里面,有至多31个SharedBufferStack。SharedBufferStack就是Android应用程序和SurfaceFlinger 的缓冲区堆栈。用来缓冲 UI 元数据。
一般我们就绘制UI的时候,都会采用一种称为“双缓冲”的技术。双缓冲意味着要使用两个缓冲区,其中一个称为Front Buffer,另外一个称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。这下就可以理解SharedBufferStack的含义了吧?SurfaceFlinger服务只不过是将传统的“双缓冲”技术升华和抽象为了一个SharedBufferStack。可别小看了这个升华和抽象,有了SharedBufferStack之后,SurfaceFlinger 服务就可以使用N个缓冲区技术来绘制UI了。N值的取值范围为2到16。例如,在Android 2.3中,N的值等于2,而在Android 4.1中,据说就等于3了。
在SurfaceFlinger服务中,每一个SharedBufferStack都对应一个Surface,即一个窗口。这样,我们就可以知道为什么每一个SharedClient里面包含的是一系列SharedBufferStack而不是单个SharedBufferStack: 一个SharedClient对应一个Android应用程序,而一个Android应用程序可能包含有多个窗口 ,即Surface。从这里也可以看出,一个Android应用程序至多可以包含31个Surface。
SharedBufferStack中的 缓冲区只是用来描述UI元数据的 ,这意味着它们不包含真正的UI数据。 真正的UI数据保存在GraphicBuffer中 ,后面我们再描述GaphicBuffer。因此,为了完整地描述一个UI,SharedBufferStack中的每一个已经使用了的缓冲区都对应有一个GraphicBuffer,用来描述真正的UI数据。当SurfaceFlinger服务缓制Buffer-1和Buffer-2的时候,就会找到与它们所对应的GraphicBuffer,这样就可以将对应的UI绘制出来了。
当Android应用程序需要 更新一个Surface 的时候,它就会找到与它所对应的SharedBufferStack,并且从它的空闲缓冲区列表的尾部取出一个空闲的Buffer。我们假设这个取出来的空闲Buffer的编号为index。接下来Android应用程序就请求SurfaceFlinger服务为这个编号为index的 Buffer分配一个图形缓冲区GraphicBuffer 。
SurfaceFlinger 服务分配好图形缓冲区 GraphicBuffer 之后,会将它的编号设置为 index,然后再将这个图形缓冲区 GraphicBuffer 返回给 Android 应用程序访问。Android应用程序得到了 SurfaceFlinger 服务返回的图形缓冲区 GraphicBuffer 之后,就在里面 写入UI数据 。写完之后,就将与它所对应的缓冲区,即编号为 index 的 Buffer,插入到对应的 SharedBufferStack 的已经使用了的 缓冲区列表的头部 去。这一步完成了之后,Android 应用程序就通知 SurfaceFlinger 服务去绘制那些保存在已经使用了的缓冲区所描述的图形缓冲区GraphicBuffer了。用上面例子来说,SurfaceFlinger服务需要绘制的是编号为1和2的Buffer所对应的图形缓冲区GraphicBuffer。由于SurfaceFlinger服务知道编号为1和2的 Buffer 所对应的图形缓冲区 GraphicBuffer 在哪里,因此,Android 应用程序只需要告诉 SurfaceFlinger 服务要绘制的 Buffer 的编号就OK了。 当一个已经被使用了的Buffer被绘制了之后,它就重新变成一个空闲的 Buffer 了 。
SharedBufferStack 是在 Android 应用程序和 SurfaceFlinger 服务之间共享的,但是,Android 应用程序和 SurfaceFlinger 服务使用 SharedBufferStack 的方式是不一样的,具体来说,就是 Android 应用程序关心的是它里面的空闲缓冲区列表,而 SurfaceFlinger 服务关心的是它里面的已经使用了的缓冲区列表。从SurfaceFlinger服务的角度来看,保存在 SharedBufferStack中 的已经使用了的缓冲区其实就是在排队等待渲染。
为了方便 SharedBufferStack 在 Android 应用程序和 SurfaceFlinger 服务中的访问,Android 系统分别使用 SharedBufferClient 和 SharedBufferServer 来描述 SharedBufferStack ,其中,SharedBufferClient 用来在Android 应用程序这一侧访问 SharedBufferStack 的空闲缓冲区列表,而 SharedBufferServer 用来在SurfaceFlinger 服务这一侧访问 SharedBufferStack 的排队缓冲区列表。
只要 SharedBufferStack 中的 available 的 buffer 的数量大于0, SharedBufferClient 就会将指针 tail 往前移一步,并且减少 available 的值,以便可以获得一个空闲的 Buffer。当 Android 应用程序往这个空闲的 Buffer 写入好数据之后,它就会通过 SharedBufferClient 来将它添加到 SharedBufferStack 中的排队缓冲区列表的尾部去,即指针 queue_head 的下一个位置上。
当 Android 应用程序通知 SurfaceFlinger 服务更新UI的时候,只要对应的 SharedBufferStack 中的 queued 的缓冲区的数量大于0,SharedBufferServer 就会将指针 head 的下一个Buffer绘制出来,并且将指针 head 向前移一步,以及将 queued 的值减1。
参考:
https://blog.csdn.net/luoshengyang/article/details/7846923
Ⅱ android 进程间的通信(IPC)方式有哪些
Android是基于linux内核的。所以linux支持的IPC,android都用到了。比如命名管道,共享内存。 除此外,android还使用了一套自己独特的IPC方式 binder. 主要用于2个进程间的远程调用。但是这里就牵扯远程调用如何传递参数,如何回传结果。 这需要调用者对数据进行打包和解包,是一个繁琐的过程。为此,android引入了aidl(android interface description launguage). 开发人员定义好aidl,android会根据aidl的描述生产stub代码,帮助调用者对数据打包,解包。开发人员所要做的事是继承stub代码,实现stub代码中的函数。这些函数是你在aidl中定义的。
Ⅲ 如何偷Android的内存
MemoryFile是android在最开始就引入的一套框架,其内部实际上是封装了android特有的内存共享机制Ashmem匿名共享内存,简单来说,Ashmem在Android内核中是被注册成一个特殊的字符设备,Ashmem驱动通过在内核的一个自定义slab缓冲区中初始化一段内存区域,然后通过mmap把申请的内存映射到用户的进程空间中(通过tmpfs),这样子就可以在用户进程中使用这里申请的内存了,另外,Ashmem的一个特性就是可以在系统内存不足的时候,回收掉被标记为”unpin”的内存,这个后面会讲到,另外,MemoryFile也可以通过Binder跨进程调用来让两个进程共享一段内存区域。由于整个申请内存的过程并不再Java层上,可以很明显的看出使用MemoryFile申请的内存实际上是并不会占用Java堆内存的。
MemoryFile暴露出来的用户接口可以说跟他的名字一样,基本上跟我们平时的文件的读写基本一致,也可以使用InputStream和OutputStream来对其进行读写等操作:
1
2
3
4
MemoryFile memoryFile = new MemoryFile(null, inputStream.available());
memoryFile.allowPurging(false);
OutputStream outputStream = memoryFile.getOutputStream();
outputStream.write(1024);
上面可以看到allowPurging这个调用,这个就是之前说的”pin”和”unpin”,在设置了allowPurging为false之后,这个MemoryFile对应的Ashmem就会被标记成”pin”,那么即使在android系统内存不足的时候,也不会对这段内存进行回收。另外,由于Ashmem默认都是”unpin”的,因此申请的内存在某个时间点内都可能会被回收掉,这个时候是不可以再读写了
Tricks
MemoryFile是一个非常trickly的东西,由于并不占用Java堆内存,我们可以将一些对象用MemoryFile来保存起来避免GC,另外,这里可能android上有个BUG:
在4.4及其以上的系统中,如果在应用中使用了MemoryFile,那么在mpsys meminfo的时候,可以看到多了一项Ashmem的值:
可以看出来虽然MemoryFile申请的内存不计入Java堆也不计入Native堆中,但是占用了Ashmem的内存,这个实际上是算入了app当前占用的内存当中
但是在4.4以下的机器中时,使用MemoryFile申请的内存居然是不算入app的内存中的:
而且这里我也算过,也是不算入Native Heap中的,另外,这个时候去系统设置里面看进程的内存占用,也可以看出来其实并没有计入Ashmem的内存的
这个应该是android的一个BUG,但是我搜了一下并没有搜到对应的issue,搞不好这里也可能是一个feature
而在大名鼎鼎的Fresco当中,他们也有用到这个bug来避免在decode bitmap的时候,将文件的字节读到Java堆中,使用了MemoryFile,并利用了这个BUG然这部分内存不算入app中,这里分别对应了Fresco中的GingerbreadPurgeableDecoder和KitKatPurgeableDecoder,Fresco在decode图片的时候会在4.4和4.4以下的系统中分别使用这两个不同的decoder
从这个地方可以看出来,使用MemoryFile,在4.4以下的系统当中,可以帮我们的app额外”偷”一些内存,并且可以不计入app的内存当中
Summary
这里主要是简单介绍了MemoryFile的基本原理和用法,并且阐述了一个MemoryFile中一个可以帮助开发者”偷”内存的地方,这个是一个非常trickly的方法,虽然4.4以下使用这块的内存并不计入进程当中,但是并不推荐大量使用,因为当设置了allowPurging为false的时候,这个对应的Ashmem内存区域是被”pin”了,那么在android系统内存不足的时候,是不能够把这段内存区域回收的,如果长时间没有释放的话,这样子相当于无端端占用了大量手机内存而又无法回收,那对系统的稳定性肯定会造成影响
Ⅳ 为什么Android要采用Binder作为IPC机制
1)从性能的角度
数据拷贝次数:Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,但共享内存方式一次内存拷贝都不需要;从性能角度看,Binder性能仅次于共享内存。
(2)从稳定性的角度
Binder是基于C/S架构的,简单解释下C/S架构,是指客户端(Client)和服务端(Server)组成的架构,Client端有什么需求,直接发送给Server端去完成,架构清晰明朗,Server端与Client端相对独立,稳定性较好;而共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题;从这稳定性角度看,Binder架构优越于共享内存。
仅仅从以上两点,各有优劣,还不足以支撑google去采用binder的IPC机制,那么更重要的原因是:
(3)从安全的角度
传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Android作为一个开放的开源体系,拥有非常多的开发平台,App来源甚广,因此手机的安全显得额外重要;对于普通用户,绝不希望从App商店下载偷窥隐射数据、后台造成手机耗电等等问题,传统Linux IPC无任何保护措施,完全由上层协议来确保。
Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志,前面提到C/S架构,Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略,判断UID/PID是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用户选择是否运行。Android 6.0,也称为Android M,在6.0之前的系统是在App第一次安装时,会将整个App所涉及的所有权限一次询问,只要留意看会发现很多App根本用不上通信录和短信,但在这一次性权限权限时会包含进去,让用户拒绝不得,因为拒绝后App无法正常使用,而一旦授权后,应用便可以胡作非为。
针对这个问题,google在Android M做了调整,不再是安装时一并询问所有权限,而是在App运行过程中,需要哪个权限再弹框询问用户是否给相应的权限,对权限做了更细地控制,让用户有了更多的可控性,但同时也带来了另一个用户诟病的地方,那也就是权限询问的弹框的次数大幅度增多。对于Android M平台上,有些App开发者可能会写出让手机异常频繁弹框的App,企图直到用户授权为止,这对用户来说是不能忍的,用户最后吐槽的可不光是App,还有Android系统以及手机厂商,有些用户可能就跳果粉了,这还需要广大Android开发者以及手机厂商共同努力,共同打造安全与体验俱佳的Android手机。
Android中权限控制策略有SELinux等多方面手段,下面列举从Binder的一个角度的权限控制:
Android源码的Binder权限是如何控制? -Gityuan的回答
传统IPC只能由用户在数据包里填入UID/PID;另外,可靠的身份标记只有由IPC机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。从安全角度,Binder的安全性更高。
说到这,可能有人要反驳,Android就算用了Binder架构,而现如今Android手机的各种流氓软件,不就是干着这种偷窥隐射,后台偷偷跑流量的事吗?没错,确实存在,但这不能说Binder的安全性不好,因为Android系统仍然是掌握主控权,可以控制这类App的流氓行为,只是对于该采用何种策略来控制,在这方面android的确存在很多有待进步的空间,这也是google以及各大手机厂商一直努力改善的地方之一。在Android 6.0,google对于app的权限问题作为较多的努力,大大收紧的应用权限;另外,在Google举办的Android Bootcamp 2016大会中,google也表示在Android 7.0 (也叫Android N)的权限隐私方面会进一步加强加固,比如SELinux,Memory safe language(还在research中)等等,在今年的5月18日至5月20日,google将推出Android N。
(4)从语言层面的角度
大家多知道Linux是基于C语言(面向过程的语言),而Android是基于Java语言(面向对象的语句),而对于Binder恰恰也符合面向对象的思想,将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。可以从一个进程传给其它进程,让大家都能访问同一Server,就像将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。从语言层面,Binder更适合基于面向对象语言的Android系统,对于Linux系统可能会有点“水土不服”。
另外,Binder是为Android这类系统而生,而并非Linux社区没有想到Binder IPC机制的存在,对于Linux社区的广大开发人员,我还是表示深深佩服,让世界有了如此精湛而美妙的开源系统。也并非Linux现有的IPC机制不够好,相反地,经过这么多优秀工程师的不断打磨,依然非常优秀,每种Linux的IPC机制都有存在的价值,同时在Android系统中也依然采用了大量Linux现有的IPC机制,根据每类IPC的原理特性,因时制宜,不同场景特性往往会采用其下最适宜的。比如在Android OS中的Zygote进程的IPC采用的是Socket(套接字)机制,Android中的Kill Process采用的signal(信号)机制等等。而Binder更多则用在system_server进程与上层App层的IPC交互。
(5) 从公司战略的角度
总所周知,Linux内核是开源的系统,所开放源代码许可协议GPL保护,该协议具有“病毒式感染”的能力,怎么理解这句话呢?受GPL保护的Linux Kernel是运行在内核空间,对于上层的任何类库、服务、应用等运行在用户空间,一旦进行SysCall(系统调用),调用到底层Kernel,那么也必须遵循GPL协议。
而Android 之父 Andy Rubin对于GPL显然是不能接受的,为此,Google巧妙地将GPL协议控制在内核空间,将用户空间的协议采用Apache-2.0协议(允许基于Android的开发商不向社区反馈源码),同时在GPL协议与Apache-2.0之间的Lib库中采用BSD证授权方法,有效隔断了GPL的传染性,仍有较大争议,但至少目前缓解Android,让GPL止步于内核空间,这是Google在GPL Linux下 开源与商业化共存的一个成功典范。
Ⅳ Android跨进程通信-mmap函数
通过mmap或者内存共享的Linux IPC机制
直接世猛将同一段内存映射到数据发送进程和数据接收进程的用户空间,这样数据发送进程只需要将数据拷贝到共享的内存区域,数据接收进程就可以直接使用数据了。
mmap是一个很重要的函数,它可以实现共享内存,但并不像SystemV和Posix的共享内存存粹的只用于共享内存,桥返饥mmap()的设计,主要是用来做文件的映射的,它提供了我们一种新的访问文件的方案。
mmap函数的使用非常简单,我们来看一下
常规文件操作为了提高读写效率和保护磁盘,使用了 页缓存机制 ,这种机制会造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于 页缓存处在内核空间 ,不能被用户进程直接寻址,所以还需要 将页缓存中数据页再次拷贝到内存 对应的用户空间中。
常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制,这种机制会造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。
而 使用mmap操作文件中,由于不需要经过内核空间的数据缓存,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用 。
mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程,因此mmap效率很高。
mmap()使用非常频繁,看过Android系统源码的人,肯定看到过大量的地方使用mmap()函数,比如上面提到的 匿名共享内存的使用就使用到了mmap来映射/dev/ashmem里的文件 。
这里我再介绍一种mmap()在Android系统上的使用场景, mmap的设计目的就是为了让文件的访问更有效率 ,所以当APK进行安装时,为了更高效的读取APK包里面的文件,同样也用到了mmap函数。
Dalvik在安装应敏返用时,需要加载dex文件,然后进行odex优化处理,优化函数为dvmContinueOptimization,我们看一下他的大致实现。
可以看到,dvmContinueOptimization函数中对dex文件的加载便用了mmap内存映射函数。