Android系统显示原理

Android 显示过程可以简单概括为:Android 应用程序把经过测量、布局、绘制后的 surface 缓存数据,通过 SurfaceFlinger 把数据渲染到显示屏幕上, 通过 Android 的刷新机制来刷新数据。也就是说应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过刷新机制把数据更新到屏幕上。

Android 的每个 View 绘制中有三个核心步骤:Measure、Layout、Draw。具体实现是从 ViewRootImp 类的performTraversals() 方法开始执行,Measure 和 Layout都是通过递归来获取 View 的大小和位置,并且以深度作为优先级,可以看出层级越深、元素越多、耗时也就越长。

真正把需要显示的数据渲染到屏幕上,是通过系统级进程中的 SurfaceFlinger 服务来实现的,SurfaceFlinger 工作内容如下:

  • 响应客户端事件,创建 Layer 与客户端的 Surface 建立连接
  • 接收客户端数据及属性,修改 Layer 属性,如尺寸、颜色、透明度等
  • 将创建的 Layer 内容刷新到屏幕上
  • 维持 Layer 的序列,并对 Layer 最终输出做出裁剪计算。

既然是两个不同的进程,那么肯定是需要一个跨进程的通信机制来实现数据传递,在 Android 显示系统中,使用了 Android 的匿名共享内存:SharedClient,每一个应用和 SurfaceFlinger 之间都会创建一个SharedClient ,然后在每个 SharedClient 中,最多可以创建 31 个 SharedBufferStack,每个 Surface 都对应一个 SharedBufferStack,也就是一个 Window。

一个 SharedClient 对应一个Android 应用程序,而一个 Android 应用程序可能包含多个窗口,即 Surface 。也就是说SharedClient 包含的是 SharedBufferStack的集合,其中在显示刷新机制中用到了双缓冲和三重缓冲技术。
最后总结起来显示整体流程分为三个模块:应用层绘制到缓存区,SurfaceFlinger 把缓存区数据渲染到屏幕,由于是不同的进程,所以使用 Android 的匿名共享内存 SharedClient 缓存需要显示的数据来达到目的。

卡顿概述

根据Google官方出品的Android性能优化典范,60帧每秒是目前最合适的图像显示速度,事实上绝大多数的Android设备也是按照每秒60帧来刷新的。为了让屏幕的刷新帧率达到60fps,我们需要确保在时间16ms(1000/60Hz)内完成单次刷新的操作(包括measure、layout以及draw),这也是Android系统每隔16ms就会发出一次VSYNC信号触发对UI进行渲染的原因。

如果整个过程在16ms内顺利完成则可以展示出流畅的画面;如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。。

用户容易在UI执行动画或者滑动ListView的时候感知到卡顿不流畅,是因为这里的操作相对复杂,容易发生丢帧的现象,从而感觉卡顿。有很多原因可以导致丢帧,也许是因为你的layout太过复杂,无法在16ms内完成渲染,有可能是因为你的UI上有层叠太多的绘制单元,
还有可能是因为动画执行的次数过多。这些都会导致CPU或者GPU负载过重。
我们可以通过一些工具来定位问题,比如可以使用HierarchyViewer来查找Activity中的布局是否过于复杂,也可以使用手机设置里面的开发者选项,打开Show GPU Overdraw等选项进行观察。你还可以使用TraceView来观察CPU的执行情况,更加快捷的找到性能瓶颈。

布局优化常用手段

  • 调试GPU过度绘制,将Overdraw降低到合理范围内;
  • 减少嵌套层次及控件个数,保持view的树形结构尽量扁平(使用Hierarchy Viewer可以方便的查看),同时移除所有不需要渲染的view;
  • 使用GPU配置渲染工具,定位出问题发生在具体哪个步骤,使用TraceView精准定位代码;
  • 使用标签,Merge减少嵌套层次、ViewStub延迟初始化。

Overdraw

每屏每帧上, 每个像素点应该只被绘制一次, 如果有多次绘制, 就是 Overdraw, 过度绘制了。 常见的就是:绘制了多重背景或者绘制了不可见的UI元素
我们可以通过调试工具来检测Overdraw:设置——开发者选项——调试GPU过度绘制——显示过度绘制区域。

原色 – 没有过度绘制 – 这部分的像素点只在屏幕上绘制了一次。
蓝色 – 1次过度绘制– 这部分的像素点只在屏幕上绘制了两次。
绿色 – 2次过度绘制 – 这部分的像素点只在屏幕上绘制了三次。
粉色 – 3次过度绘制 – 这部分的像素点只在屏幕上绘制了四次。
红色 – 4次过度绘制 – 这部分的像素点只在屏幕上绘制了五次。

在实际项目中,一般认为蓝色即是可以接受的颜色。

  • 去掉每行RelativeLayout的背景色;
  • 去掉每行TextView的背景色;

备注:一个容易忽略的点是我们的Activity使用的Theme可能会默认的加上背景色,不需要的情况下可以去掉。

Hierarchy Viewer

  • Android的布局文件的加载是LayoutInflater利用pull解析方式来解析,然后根据节点名通过反射的方式创建出View对象实例;
  • 同时嵌套子View的位置受父View的影响,类如RelativeLayout、LinearLayout等经常需要measure两次才能完成,而嵌套、相互嵌套、深层嵌套等的发生会使measure次数呈指数级增长,所费时间呈线性增长;

随着控件数量越多、布局嵌套层次越深,展开布局花费的时间几乎是线性增长,性能也就越差

Hierarchy Viewer这个方便可视化的工具,可以得到:树形结构总览、布局view、每一个View(包含子View)绘制所花费的时间及View总个数。

Hierarchy Viewer不能连接真机的问题可以通过ViewServer这个库解决;
点击"Profile Node"获取Measure、Layout、Draw的时间

GPU配置渲染工具

打开设备的GPU配置渲染工具,在屏幕上显示为条形图,可以协助我们定位UI渲染问题


中间有一根绿色的横线,代表16ms,我们需要确保每一帧花费的总时间都低于这条横线,这样才能够避免出现卡顿的问题。
每一条柱状线都包含三部分,蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间。
从Android M版本开始,GPU Profiling工具把渲染操作拆解成如下8个详细的步骤进行显示。

  • Swap Buffers:表示处理任务的时间,也可以说是CPU等待GPU完成任务的时间,线条越高,表示GPU做的事情越
  • Command Issue:表示执行任务的时间,这部分主要是Android进行2D渲染显示列表的时间,为了将内容绘制到屏幕上,Android需要使用Open GL ES的API接口来绘制显示列表,红色线条越高表示需要绘制的视图更多;
  • Sync & Upload:表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者是缩小图片的大小;
  • Draw:表示测量和绘制视图列表所需要的时间,蓝色线条越高表示每一帧需要更新很多视图,或者View的onDraw方法中做了耗时操作;
  • Measure/Layout:表示布局的onMeasure与onLayout所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题;
  • Animation:表示计算执行动画所需要花费的时间,包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者是检查动画执行的过程中是不是触发了读写操作等等;
  • Input Handling:表示系统处理输入事件所耗费的时间,粗略等于对事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作;
  • Misc Time/Vsync Delay:表示在主线程执行了太多的任务,导致UI渲染跟不上vSync的信号而出现掉帧的情况;出现该线条的时候,可以在Log中看到这样的日志:

GPU配置渲染工具虽然可以定位出问题发生在某个步骤,但是并不能定位到具体的某一行;当我们定位到某个步骤之后可以使用工具TraceView进行更加详细的定位。

Use Tags

  • merge标签
    merge可以用来合并布局,减少布局的层级。merge多用于替换顶层FrameLayout或者include布局时,用于消除因为引用布局导致的多余嵌套。
<?xml version="1.0" encoding="utf-8"?>    

merge标签常用于减少布局嵌套层次,但是只能用于根布局

  • ViewStub标签
    推迟创建对象、延迟初始化,不仅可以提高性能,也可以节省内存(初始化对象不被创建)。Android定义了ViewStub类,ViewStub是轻量级且不可见的视图,它没有大小,没有绘制功能,也不参与measure和layout,资源消耗非常低
 
ViewStub viewStub = (ViewStub)view.findViewById(R.id.mask);viewStub.inflate();

App里常见的视图如蒙层、小红点,以及网络错误、没有数据等公共视图,使用频率并不高,如果每一次都参与绘制其实是浪费资源的,都可以借助ViewStub标签进行延迟初始化,仅当使用时才去初始化。

更多相关文章

  1. Flutter和Android中的View区别
  2. 实现三星S3蒲公英水波纹效果(二)——Renderscript准备篇
  3. Android(安卓)应用界面显示流程
  4. Android自定义实现圆形播放进度条
  5. Android:java.lang.OutOfMemoryError: Failed to allocate a 239
  6. 【Android(安卓)开发】: Android(安卓)消息处理机制之三: Handle
  7. 安卓不知道怎么学?看十年码农如何回答这个问题。
  8. Android实现图表绘制和展示
  9. Android绘图之绘制太极图

随机推荐

  1. android 利用adb连接手机调试程序
  2. Android依赖注入:Google Guice on Android
  3. android下的MVP架构
  4. Kotlin~基本语法
  5. Android Studio入门体验
  6. Android 8.0 SystemUI 源码分析(一):图文并
  7. android上用C语言读取fb0实现截屏,并保存
  8. Android Eclipse关联 android API doc文
  9. android - AsyncTask的使用
  10. Android布局优化之Include(一)