Android显示之应用界面绘制

越到上层,跟业务关联越直接。代码就越繁杂。Android上层显示的代码正是如此。此外,java语言本身繁复的特点(比C语言多了满屏的try-catch,比C++少了析构处理的优雅简洁,和更高级的语言scala、python等就别比了),更加剧了这一现象。

直接去看代码,往往会看得一头雾水,知其然而不知其所以然。在这时候,就要把代码扔掉。细致去理清须要实现什么,怎么实现,画一幅架构设计图出来,然后再跟代码去对照。

Android这部分代码并非圣经,有非常多待商榷的地方。心中要有主见,批判性地看。

因为中间各种事耽搁。加上懒。一直没空写长篇博文。间隔了非常长一段时间,请读者先回想显示概述与下层显示:

http://blog.csdn.net/jxt1234and2010/article/details/44164691

另外,因为Android显示还是有不少人写的。某些模块有写得比較好的文章我就直接上链接。不自己写了,见谅。

下层显示关键词:SurfaceFlinger

上层显示关键词:View

初步章节安排:

1、界面绘制

2、布局计算

3、硬件加速下层实现

4、典型控件

5、资源管理

UI引擎设计原则

易用性

用户是非常懒的,事实上程序猿也一样。 让应用开发人员直接使用OpenGL去开发界面,无异于让他们赤手空拳打坦克。即便是使用图形引擎的接口,也已经相当繁琐了。

最理想的情形,是由编辑器搞定界面,所见即所得。配配參数就ok。如Unity3D。

一般都会提供足够多的默认控件,但假设应用有更绚丽的效果要求,也会提供接口实现。

高效性

作为UI引擎,掌控着足够完整的渲染流程,优化空间是相当大的。相对而言。难度也更大。

这个高效反映在双方面,一是图形引擎的高效,一是脏区域识别的高效。

图形引擎的高效

第一个重要的点是下层图形引擎的选用

图形引擎的高效反映在两个方面:单体性能和复合性能。单体性能即渲染单个物体的性能,复合性能则是指在多个物体一起渲染的性能(多个物体一起渲染,有一些优化手段。比方作遮挡推断,消除非必要渲染。又比方作区域分划。多线程绘制各区域上的物体)。

图形引擎能够基于CPU渲染,也能够基于GPU渲染。

就一般的UI渲染而言,CPU图形引擎优化得足够,倒也能满足要求,不会比GPU引擎差多少。

识别脏区域

与游戏界面的实时变换不同,对普通应用UI界面的渲染而言。大部分情况下一个页面的大部分面积处于不变状态。变化的区域又称脏区域。如何尽可能多地识别不变的部分,并作渲染规避,是UI引擎须要完毕的非常重要的工作。

比較理想的UI引擎的设计结构例如以下图:



应用开发人员能够在三个层次上去实现UI效果。

从上往下。自由度越来越高。开发难度也会越来越大。

Android的设计



Android并没有开发新的界面语言。而是採用xml+java的形式。由xml文件确定大致布局,java代码中做控制和微调。

Android没有明白的UI解析引擎,UI解析反映在View、Layout等类的实现中。

应用开发人员使用View的API(UI接口)、Canvas的API(引擎API)进行开发。

View

Android的控件和布局管理都抽象为View。

部分View用于布局解析(各种Layout),部分View用于管理(复合View),部分View是实际的控件(TextView、ImageView、WebView等)。

详细的渲染流程全然取决于应用所选择的View的子类。

全部View组成一个树,布局时逐层创建树节点,渲染时逐级渲染。当调用invalidate刷新View时。由下往上逐层上报dirty区域。

详细可看这篇文章。写得比較清楚:

http://blog.csdn.net/xu_fu/article/details/7829721

一个View不管其渲染流程如何,都必须保证其绘制内容固定在屏幕的指定范围。这是Android上层显示的设计原则。对于使用系统的图形引擎的应用。这能够通过在大图层上划分一块区域,设置裁剪范围而实现。但假设不使用系统图形引擎,就仅仅好新建一个图层,并将主图层相应位置挖洞。

在View的invalidate函数中。将须要重绘的View作标志。

并将其区域与上一级View的脏区域作合并,终于反映到ViewRootImpl的mDirty中来。

invalidate顺着View树脉络,一层一层往上刷新。



invalidate之后,该View即须要绘制。即是dirty的。

Canvas

Canvas是Android系统提供的图形引擎API,因为早期Android的图形渲染由Skia完毕,Canvas接口也与Skia的API非常像。

绝大部分控件使用Canvas的API进行界面渲染,如TextView、ImageView及用户自己定义,重载onDraw(Canvas canvas)的View。

比較特殊的是WebView。它不使用Canvas的API渲染,而是由Canvas获取Surface信息后,走web引擎渲染。

绘制主线

触发

众所周知,ViewRootImpl类的performTraversals方法。是全部界面布局、绘制的入口。但这种方法是怎么触发的呢?

在应用初起、View更新(触发invalidate)、动画、创建新Surface等情形下,会通过 scheduleTraversals 方法,向 Choreographer 类注冊一个回调,Choreographer 类是用来接受vsync信号的,这样,在LCD发出vsync信号之后(也即新一帧开启),该回调被运行,即doTraversal -> performTraversals。

详情參见:

http://blog.csdn.net/farmer_cc/article/details/18619429

注:

1、performTraversals的调用是应用级的。也就是说,有可能会有多个应用去调这个函数。

主流程



1、计算总大小,创建一个Surface用于存储渲染结果。

2、进行布局測量。算出每一个View的范围。

3、进行layout,实例化全部子View。

4、一切就绪,运行渲染。

详细的看这篇文章吧:

http://blog.csdn.net/aaa2832/article/details/7849400

由这条绘制主线我们能够看出,跟View相关的一切操作,布局,初始化,渲染,全部在一个线程(事实上是主线程)完毕,假设在这个过程中,其它线程改动了View的属性值,便会造成布局计算后的结果与后面实际渲染的需求不一致。

Android里面对此的解决方式是限制,即众所周知的仅仅能在主线程更新UI。

渲染流程

软件渲染

drawSoftware

简洁明快的流程:

1、调 surface.lockCanvas,取得渲染入口Canvas。

2、从顶层View開始,按树递归调用View的draw方法。在draw方法中。全部View中的onDraw实现被调用。

3、调 surface.unlockCanvasAndPost

第1步相应的下层逻辑还是有点复杂的:

(1)dequeueBuffer获取一块新GraphicBuffer。

(2)将新GraphicBuffer锁定(lock),指明为CPU所訪问。

(3)优化:假设存在上一帧所渲染的GraphicBuffer,且长宽与当前窗体一致,那么复制上一帧非dirty区域的内容到新一帧。假设不存在。将dirty区域设为全屏(即全部区域都要渲染)。

(4)将GraphicBuffer映射为一个SkBitmap,相应创建一个SkCanvas与之绑定,SkCanvas设置裁剪区域为第(3)步得到的dirty区域。

(5)SkCanvas包装为上层的Canvas传回。

第3步相应的下层逻辑就是 queueBuffer。

请注意,不是仅仅须要绘制dirty的View的,因为View有可能会重叠,发生透明度混合,重叠部分影响到非dirty的View时,也应该绘制。Android并没有计算哪些View须要重绘。就笼统地让全部View运行onDraw方法。

软件渲染流程中,布局、渲染、事件响应全部集中在主线程,比較easy造成堵塞。

硬件渲染

为何要有硬件渲染这套流程。而不是仅改造图形引擎为用gpu的呢?

这是因为直接按软件渲染那套流程走下来,是不适合用gpu渲染的,强行换用OpenGL实现,效率会低得可怜。

硬件加速中draw的实如今ThreadedRenderer.java之中(这是5.0的,不同版本号可能有不同,重点看原理)。

1、把创建好的Surface扔给硬件加速的Renderer,供其初始化(eglCreateWindowSurface要用)。

2、更新显示列表(updateRootDisplayList):创建一个记录命令的Canvas,将View中对Canvas的draw操作变成记录命令,非dirty的View不须要又一次记录。

3、运行渲染(nSyncAndDrawFrame)。这一步是放渲染线程里面发一个任务,让其做一次绘制。一般不须要等渲染线程绘制完毕。

详细实如今 DrawTask的drawFrame函数。兴许章节详述:

frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp

从设计而言,硬件加速的渲染流程要比软件渲染流程好一些。显示列表的存在,给复合优化带来可能。即使不用gpu加速,也都有优势。

关于硬件加速几个常见问题和误区:

1、为何开启硬件加速要额外的内存?

非常多文章里面将其误觉得是开启OpenGL所须要的额外内存。事实上不然。OpenGL上下文的内存消耗不会达到MB级,这个额外内存是hwui引擎所须要的缓存。大头是字体。

详细大小能够通过设置系统属性改动。通过 adb shell getprop,可查看相关的属性(ro.hwui开头)。



hwui内部机理是将文字解析到一个大的texture上,渲染详细文本时计算相应文字范围。取此texture中的一部分。

因此有一个宽/高的设置,不像skia里面是一维的大小。

关于为什么要有字体缓存。能够看一下这篇文章:

http://mobile.51cto.com/abased-442805.htm

另外。在Android系统内存不足时,会去部分回收这个Cache。

2、显示列表机制是否显著提升了UI渲染性能?

显著提升渲染性能靠的是GPU,显示列表机制是将GPU用上的一种方法。

因为Android早期API全部基于CPU渲染,因此在UI渲染时全部资源(最主要还是图像Bitmap)都在CPU所能訪问的内存中。GPU渲染时,必须要把相应的资源拷贝到显存中。这一个复制的过程。自然不希望每一帧时都做一遍。

保存全部命令及相应资源到一个显示列表上。然后回放,是一个可取的方案,其最大的优点是应用开发人员仍然能够按原先的API进行开发,仅仅须要打开一个开关就能用到硬件加速。

3、硬件加速能否够使全部的界面绘制都用上GPU?

答案是否。

请看以下的“非主线渲染”。

非主线渲染

就View层级设计而言。Android希望一个应用仅仅有一个图层,并在这个图层上布局全部的控件,并且应用不用感知这个图层的内存所在。最多调Canvas接口就可以,系统帮忙搞定图形渲染、Buffer循环、送显合成等繁琐事务。

但非常可惜,这样的方案不能满足全部需求:

1、对视频、照相等应用而言,它们须要直接訪问物理内存(主要是硬件解码器和ISP等须要),把它们的显示放到一个图层的部分区域,不太现实。

2、全部UI操作和绘制集中在主线程,即使是硬件加速,也须要在主线程创建显示列表,做动画时,easy堵塞事件响应。

3、这样的方案下,应用开发人员无法自己定义渲染流程,直接使用OpenGL等图形API进行开发。这样意味着使用不了游戏引擎。

SurfaceView应运而生,它的原理,就是打洞覆盖:另起一个图层(即新建一个Surface),并把主图层的相应区域置为透明,然后渲染就发生在新图层中,终于显示效果自然是依赖SurfaceFlinger的叠加。

使用方法參考:

http://blog.csdn.net/ithomer/article/details/7280968

当中,SurfaceHolder往下会相应着一个Buffer循环队列,这个是物理共享内存的抽象,因此能够做为视频、相机预览流的指定输入。

网上的教程中。SurfaceView的使用方法都在还有一个线程中,先lockCanvas。调用Canvas的接口绘制画面之后,调unlockCanvasAndPost。这样的方式,便是典型的调CPU引擎-Skia渲染的方式。

虽然应用开发人员能够用SurfaceView直接开发基于OpenGL渲染的程序(SurfaceHolder能够用于创建OpenGL上下文)。Google还是非常仁慈地提供了GLSurfaceView,这个类帮开发人员创建好了上下文和相应的渲染线程,开发人员能够直接在回调函数中使用OpenGL,简单非常多。

请注意:

1、SurfaceView不会自己主动起一个单独的线程去渲染,仅仅是这个View上面的渲染能够在随意线程完毕。开发人员执意在主线程去渲染这个View。也是能够的。就像曾经QQ某一版的引导页一样,CPU差一点的机器滑都滑不动(净给我们这些做系统优化的出难题)。

2、SurfaceView虽然能够把渲染流程移到还有一个线程运行,但它的存在同一时候添加了SurfaceFlinger的合成负担(图层数添加),不要以为这就是一个非常高效的View。假设是出于提升性能的目的而使用。请细致权衡一下得失。

3、硬件加速属性不影响SurfaceView的渲染方式,lockCanvas必定得到用CPU绘制的Canvas。要在SurfaceView中用上GPU渲染,仅仅好自已建上下文或用GLSurfaceView,接入3D引擎。

补充,2015.8.14之后。Android提供了一个lockHardwareCanvas方法。用此方法能够得到硬件加速的Canvas。Android 6.0上已经能够使用。这但是个大福音。

4、SurfaceView系列的渲染流程不在performTraversals主线中,因此一般也不受vsync限制(当然,能够设计流程使之受限),也不会像主线渲染必须由invalidate触发。只是,假设渲染太快,在下层显示的窗体管理模块,能够使之堵塞在申请Buffer的步骤上。

Android的设计吐槽

Android的发展也有些年头了。图形显示部分更是一改再改,差点儿面目全非。总算是满足了手机硬件发展的需求,实现了一个比較高效。对开发人员相对友好的界面绘制系统,相对于其它系统来说,事实上也算优秀了。然而,作为一个逐渐演进的复杂系统,背负着不少历史的包袱。总会有各种各样的不合理。这里就来吐槽一下:

1、主线程单一管理界面

个人觉得的最大槽点,没有之中的一个。全部UI操作集中到一个线程后无法并行,而measure/layout/draw都是耗时大户。

在应用启动、屏幕旋转、列表滑动等场景。屡屡出现性能问题。ART模式开启。加快了java代码运行效率后,好了一些。但指标仍然不好看。

2、脏区域识别之后并没有充分地优化

软件渲染时。虽然限制了渲染区域,但全部View的onDraw方法一个不丢的运行了一遍。

硬件渲染时,避免了没刷新的View调onDraw方法更新显示列表。但显示列表中的命令仍然一个不落的在全屏幕上运行了一遍。

一个比較easy想到的优化方案就是为主流程中的View建立一个R-Tree索引,invalidate这一接口改动为能够传入一个矩形范围R,更新时。利用R-Tree索引找出包括R的全部叶子View,令这些View在R范围重绘一次就可以。

这个槽点事实上影响倒不是非常大,大部分情况下View不多。且假设出现性能问题。基本上都是一半以上的屏幕刷新。

3、图层分配方案比較浪费内存和内存传输带宽(DDR带宽)

下图是对小米平板上相机应用 dumpsys SurfaceFlinger的一个结果



由上图能够看出。SurfaceView的Layer(相机的预览Surface)和com.android.camera的Layer(主渲染流程的Surface)是一样大的,都差点儿相同占了全屏。

但实际上。com.android.camera仅仅有几个图标,这个Layer绝大部分是透明的。考虑到TrippleBuffer机制,按透明部分约为1024*2048的大小算。就浪费了1024*2048*4*3=24M的内存。

并且在SurfaceFlinger作合成时,透明部分也要參与。按最省内存传输带宽的在线合成(仅仅须要一读)方式,预览帧按30fps算。透明部分所须要的DDR带宽就是8M*30/s = 240M/s。

一般手机上的DDR带宽才800M/s(高端手机应该有1600),这就占用了差点儿1/3。

Android图形显示系统——上层显示1:界面绘制大纲的更多相关文章

  1. Android 12(S) 图形显示系统 - 解读Gralloc架构及GraphicBuffer创建/传递/释放(十四)

    必读: Android 12(S) 图形显示系统 - 开篇 一.前言 在前面的文章中,已经出现过 GraphicBuffer 的身影,GraphicBuffer 是Android图形显示系统中的一个重 ...

  2. Android 12(S) 图形显示系统 - SurfaceFlinger的启动和消息队列处理机制(四)

    1 前言 SurfaceFlinger作为Android图形显示系统处理逻辑的核心单元,我们有必要去了解其是如何启动,初始化及进行消息处理的.这篇文章我们就来简单分析SurfaceFlinger这个B ...

  3. Android 12(S) 图形显示系统 - 示例应用(二)

    1 前言 为了更深刻的理解Android图形系统抽象的概念和BufferQueue的工作机制,这篇文章我们将从Native Level入手,基于Android图形系统API写作一个简单的图形处理小程序 ...

  4. Android 12(S) 图形显示系统 - 基本概念(一)

    1 前言 Android图形系统是系统框架中一个非常重要的子系统,与其它子系统一样,Android 框架提供了各种用于 2D 和 3D 图形渲染的 API供开发者使用来创建绚丽多彩的应用APP.图形渲 ...

  5. 第六篇 ANDROID窗口系统机制之显示机制

    第六篇 ANDROID窗口系统机制之显示机制 ANDROID的显示系统是整个框架中最复杂的系统之一,涉及包括窗口管理服务.VIEW视图系统.SurfaceFlinger本地服务.硬件加速等.窗口管理服 ...

  6. Android 12(S) 图形显示系统 - createSurface的流程(五)

    题外话 刚刚开始着笔写作这篇文章时,正好看电视在采访一位92岁的考古学家,在他的日记中有这样一句话,写在这里与君共勉"不要等待幸运的降临,要去努力的掌握知识".如此朴实的一句话,此 ...

  7. Android 12(S) 图形显示系统 - BufferQueue的工作流程(八)

    题外话 最近总有一个感觉:在不断学习中,越发的感觉自己的无知,自己是不是要从"愚昧之巅"掉到"绝望之谷"了,哈哈哈 邓宁-克鲁格效应 一.前言 前面的文章中已经 ...

  8. Android 12(S) 图形显示系统 - BufferQueue的工作流程(九)

    题外话 Covid-19疫情的强烈反弹,小区里检测出了无症状感染者.小区封闭管理,我也不得不居家办公了.既然这么大把的时间可以光明正大的宅家里,自然要好好利用,八个字 == 努力工作,好好学习 一.前 ...

  9. Android 仿PhotoShop调色板应用(三) 主体界面绘制

    版权声明:本文为博主原创文章,未经博主允许不得转载. Android 仿PhotoShop调色板应用(三) 主体界面绘制    关于PhotoShop调色板应用的实现我总结了两个最核心的部分:   1 ...

随机推荐

  1. 简单的css缩放动画,仿腾讯新闻的分享按钮和美团app底部的图标样式

    最近看到一些好看的hover的图形缩放效果.然后自己就写了下,发现这2种效果都不错.如果伙伴们更好的实现方式可以在下面留言哦~ 还有美团的效果,我就不展示了,喜欢的可以去app应用上看看. 这两种效果 ...

  2. es6之iterator,for...of

    遍历器(Iterator)是一种统一的接口机制,来处理所有不同的数据结构. JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和 ...

  3. [转帖]关于flask-login中各种API使用实例

    原贴:http://www.cnblogs.com/alima/p/5796298.html 简介:简单的集成flask,WTForms,包括跨站请求伪造(CSRF),文件上传和验证码. 一.安装(I ...

  4. 微信小程序组件解读和分析:二、scroll-view可滚动视图区域

    scroll-view可滚动视图区域组件说明: 可滚动视图区域. 组件用法:纵向滚动用法 Tip: 使用竖向滚动时,需要给一个固定高度,通过 WXSS 设置 height,否则无法滚动. 当滚动到顶部 ...

  5. java_IO_2

    1.字节流  InputStream(抽象类) package ioStudy; import java.io.File; import java.io.FileInputStream; import ...

  6. ArrayAccess(数组式访问)

    实现该接口后,可以像访问数组一样访问对象. 接口摘要: ArrayAccess { abstract public boolean offsetExists ( mixed $offset ) abs ...

  7. 代码分析工具splint安装介绍

    官网 http://www.splint.org/ splint能干什么? splint是一个静态检查C语言代码安全弱点和编写错误的开源程序.(不支持C++) splint会进行多种常规检查,包括 空 ...

  8. 字符串--P1308 统计单词数

    题目描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给 ...

  9. CCF201604-1 折点计数 java(100分)

    试题编号: 201604-1 试题名称: 折点计数 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 给定n个整数表示一个商店连续n天的销售量.如果某天之前销售量在增长,而后一天 ...

  10. 网络基础——OSI参考模型

    一.OSI/ISO/IOS傻傻分不清楚 ISO 国际标准化组织(International Organization for Standardization)简称ISO,是一个全球性的非政府组织,是国 ...