性能优化-绘制优化

前言

卡顿场景可分为以下四类:

  1. UI绘制:绘制、刷新
  2. 应用启动:安装启动、冷启动、热启动
  3. 页面跳转:页面间切换、前后台切换
  4. 事件响应:按键、系统事件、滑动

这四种卡顿场景的根本原因又可以分为两大类:

  1. 界面绘制:主要原因是绘制的层级深、页面复杂、刷新不合理
  2. 数据处理:导致这种卡顿场景的原因是数据处理量太大,一般分为三种情况:
  • 一是数据处理在UI线程(这种应该避免)。
  • 二是数据处理占用CPU高,导致主线程拿不到时间片。
  • 三是内存增加导致GC频繁,从而引起卡顿。

Android系统显示原理

Android的显示过程可以简单概括为:Android应用程序把经过测量、布局、绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到屏幕上,通过Android的刷新机制来刷新数据。

绘制原理

应用层

在Android的每个view绘制中又三个核心步骤:Mesasure、Layout、Draw。通过Measure和Layout来确定当前需要绘制的view所在的大小和位置,通过绘制(Draw)到surface。

Measure和Layout都是递归来获取view的大小和位置,并且以深度作为优先级,因此层级越深,元素越多,耗时也就越长

系统层

应用层和系统层是两个不同进程,在Android的显示系统,使用匿名共享内存:SharedClient,每个应用和SurfaceFlinger之间都会创建一个SharedClient。在每个SharedClient中,最多可以创建31个ShardBufferStack,每个Surface都对应一个ShardBufferStack,也就是一个window。 一个SharedClient对应一个Android应用程序,意味着一个Android应用程序最多可以包含31个窗口

显示整体流程分为三个模块:应用层绘制到缓存区,SurfaceFlinger把缓存区数据渲染到屏幕,由于是两个不同的进程,所以使用Android的匿名共享内存SharedClient缓存需要显示的数据来达到目的。

知道绘制原理后,那么绘制一个单元多长时间才是合理的?
——在理想情况下,60FPS(Frames Per Second 每秒传递的帧数)就感觉不到卡,这意味着每个绘制时长应该在16ms以内。

Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染。若每次都成功就能达到流畅画面的60FPS。若某个操作耗时较久,系统在得到VSYNC信号时就无法正常渲染,这样就会发生丢帧现象。

==卡顿的根本原因==

影响绘制的根本原因有以下两方面:

  1. ==绘制任务太重==,绘制一帧内容耗时太长。
  2. ==主线程太忙==,导致VSync信号来时还没有准备好数据导致丢帧。

性能分析工具

性能问题不容易复现,在分析性能问题时需要借助相应的调试工具,比如查看Layout层次的Hierarchy View、Android系统自带的 Profile GPU卡顿检测工具和静态代码检查工具Lint,以及性能分析常用的TraceView和SysTrace等。

卡顿检测工具

Profile GPU Rendering是Android4.1系统开始提供的开发辅助工具,可在开发者选项中打开(华为手机是:GPU呈现模式分析按钮)

特点:

  • 是一个图形检测工具,实时反应当前绘制的耗时。
  • 提供一个标准耗时,高于标准耗时,表示当前一帧丢失。


    image.png

各种颜色含义


image.png

技巧:
在实际开发中,图形不便做数据分析,可通过:adb shell dumpsys gfxinfo com.##.##(包名)把具体的耗时输出到日志中来分析。

对大部分应用来说丢失几帧影响不大,只需保证大部分在警戒线下即可。通过Profile GPU Rendering发现有问题对页面后,可通过另一个工具Hierarchy Viewer来查看布局层次和每个view所花时间具体定位。

TraceView

TraceView是AndroidSDK自带的工具,用来分析函数调用过程,可以分析到应用具体每一个方法的执行时间

  1. 使用方法

在使用TraceView分析问题之前需要得到一个*.trace的文件,然后通过TraceView来分析。trace文件的获取方法有两种:

  • 通过Android Studio的Android Device Monitor,单击Start Method Profile按钮开始监控,操作要监控的界面,完成后stop就会跳到TraceView视图。
  • 代码中加入调试语句保存trace文件:android.os.Debug类中提供类相应的方法,调用代码如下:
//在开始监控的地方,保存在"/sdcard/trace_name.trace"
Debug.startMethodTracing("trace_name");
//...
//stop trace
Debug.stopMethodTracing();
  1. TraceView 视图说明
    TraceView视图分两个部分,上半部分为时间片面板,下半部分为分析面板。
  • 时间片面板:X轴表示时间消耗,Y表示各个线程,每个线程中的不同方法用不同颜色表示,颜色越宽表示该方法占用CPU时间越长。
  • 分析面板:主要关注Calls + Recur Calls/Total(该方法调用次数+递归次数)和Cpu Time / Call(该方法耗时)这两个值,也就是关注调用次数多和耗时久的方法,然后优化这些方法的逻辑。

SysTrace UI性能分析

Systrace是Android4.1以上版本提供的性能数据采样和分析工具。功能包括跟踪系统的I/O操作、内核工作队列、CPU负载等。能直观查看CPU周期消耗的具体时间,用不同颜色来突出问题严重性,并提供解决建议。

注意:由于Systrace从系统角度返回一些信息,并不能定位到具体耗时的方法,要具体分析原因要借助TraceView

  1. Systrace 使用方法
  • 在DDMS上使用:
    (1)打开Android Device Monitor,连接手机准备好需抓取界面;
    (2)单击Systrace进入抓取前的设置,选择需跟踪内容;
    (3)手机操作需跟踪过程;
    (4)到设定时间后,生成Trace文件,使用Chrome打开文件。
  • 使用命令行
cd android-sdk/platform-tools/systrace
python systrace.py --time=10 -o mytrace.html sched gfx view wm

具体命令查看官方文档

  • 应用中获取:在应用中加入Trace跟踪需要注意两点:

(1)Trace嵌套时,endSection()方法只会结束离它最近的一个beginSection()。所以要保证endSection和beginSection调用次数匹配。

(2)Trace的begin和end必须在同一线程中执行。

public void ProcessPeople{
  Trace.beginSection("ProcessPeople");
  try{
      Trace.beginSection("Process One");
      try{
          //code
      }finally{
          Trace.endSection(); //end Process One
      }
      Trace.beginSection("Process Two");
      try{
          //code
      }finally{
          Trace.endSection(); //end Process Two
      }
  }finally{
      Trace.endSection(); //end ProcessPeople
  }
}
  1. 分析Systrace报告

通过前面方法获取到的trace.html文件,需要用Chrome打开,其中和UI绘制关系紧密的是Alerts和Frame两个数据。

  • Alerts:标记了性能有问题的点,可以看到问题的详细描述
  • Frame:每个应用都有一行专门显示frame,它将任何它认为性能有问题的东西都高亮警告并提升怎么优化。

布局优化

布局是否合理主要影响的是页面测量时间的多少,如果层级太深,每增加一层则会增加更多的页面显示时间。

常用布局优化工具

1. Hierarchy View

Hierarchy View是Android SDK自带的调试工具,用来检查Layout嵌套及绘制时间,以可视化的布局角度获取Layout布局设计和各种属性信息。

使用:在Android Studio中打开Android Device Monitor菜单,直接打开Hierarchy View

  • 查看层级图:在window窗口页,选择需要查看的组件,双击或单击Load View Hierarchy按钮即可。
  • 查看某个view的耗时:在快捷键工具栏单击Obtain layout times for tree rooted at selected node。

一个应用界面非常多,如果一个个用Hierarchy View分析效率低,可以用另一个工具Lint,用于检查所有页面的层级,并把深度高于N的界面输出,然后在用Hierarchy View仔细分析

2. 布局层级检查

Android Lint是ADT 16之后引入的代码检查工具,通过代码静态检查,可以发现潜在的代码问题,并给出优化建议。

使用前可在File -> Setting -> Inspections -> Android Lint中配置扫描规则和缺陷级别。

在Android studio中启动Lint:从菜单栏选择Analyze -> Inspect Code,进去后选择扫描范围扫描。

布局优化方法

通过减少Layout层级,减少测量、绘制时间,提高复用性三方方面来优化布局,,优化的目的是减少层级,让布局扁平化,以提高绘制的时间,提高布局的复用性。

1. 减少层级

减少层级的两个常用方案:

  • 合理使用RelativeLayout和LinearLayout
  • 合理使用Merge

合理使用RelativeLayout和LinearLayout

RelativeLayout相对LinearLayout能够减少布局层级,但也存在性能低的问题,原因是RelativeLayout会对子view做两次测量,因为依赖关系可能和布局中view顺序不同,在确定子view位置时,需先给所有子view做一次排序。

布局原则:

  • 尽量使用RelativeLayout和LinearLayout
  • 在层级相同时,使用LinearLayout
  • 如果用LinearLayout会使层级变多,则应该用RelativeLayout。

合理使用Merge

Merge是合并的意思,可以有效优化某些符合条件的多余层级。使用场景如下:

  • 在自定义view中使用,父元素尽量是FrameLayout或者LinearLayout。
  • 在Activity中整体布局,根元素需要是FrameLayout。

Merge使用要求:

  • Merge只能用在布局XML文件的根元素。
  • 使用Merge加载布局时,必须指定一个ViewGroup作为其父元素,并且设置加载的attachToRoot参数为true。
  • 不能在ViewStub中使用Merge元素。(原因是ViewStub的inflate方法中根本没有attachToRoot的设置)
2. 提高显示速度

有时需要某个布局在一开始不显示,在某个条件下才显示,可以通过visable属性来控制,但这样效率非常低,因为虽然布局隐藏来,但还在布局中,仍会解析这些布局。可以使用ViewStub控件来解决这个场景并提高效率。

ViewStub是一个轻量级的View,它是一个看不见的,并不占布局位置,占用资源非常小的视图对象。

使用ViewStub注意的点:

  • ViewStub只能加载一次,之后ViewStub对象会被置空。也就是布局被加载后就不能再用ViewStub来控制它的显示隐藏。
  • ViewStub只能用来加载一个布局文件,而不是某个具体的View。
  • ViewStub不能嵌套Merge标签。

ViewStub主要使用场景:

  • 在程序运行期间,某个布局被加载后,状态就不会有变化。
  • 想要控制一个布局文件的隐藏/显示,而不是某个view
3. 布局复用

开发过程中可以将一些公共的布局抽离出来作为一个布局文件,然后在需要使用的地方通过<include/>标签来实现引入。

对布局优化的总结

  • 尽量使用RelativeLayout和LinearLayout
  • 尽可能少用wrap_content,会增加布局Measure时的计算成本,已知道宽高固定值时不用wrap_content。
  • 将复用组件抽取出来并通过<include/>标签使用
  • 使用<ViewStub/>标签加载按需显示的布局
  • 使用<Merge/>标签减少布局嵌套层级
  • 删除控件中无用属性

避免过度绘制

过度绘制的主要原因

  • XML布局:控件有重叠且都有设置背景。
  • View自绘:View.OnDraw里面同一个区域被绘制多次。

过度绘制检测工具

通过手机设置中开发者选项,打开Show GPU Overdraw选项(调试 GPU 过度绘制),打开后会有不同的颜色区域表示不同的过度绘制次数。不同颜色含义如下:

  • 无色:没有过度绘制,每个像素只绘制来1次
  • 蓝色:过度绘制 1 次(大片蓝是可以接受的)
  • 绿色:过度绘制 2 次
  • 粉色:过度绘制 3 次(不要超过1/4)
  • 红色:过度绘制 4 次或更多次(需优化)

我们的目标是减少红色Overdraw,看到更多蓝色或无色区域。

如何避免过度绘制

  1. 布局上的优化
  • 移除XML中非必需的背景,或根据条件设置
  • 移除Window默认背景
  • 按需显示占位背景图片

在Android自带的一些主题时activity往往会设置一个默认的背景,这个背景有DecorView持有。当自定义布局有一个全屏背景时,DecorView的背景此时对我们来说是无用的,但会产生一次Overdraw,因此可以移除

protect void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    this.getWindow().setBackgroundDrawaable(null);
}
  1. 自定义View优化

自定义view能减少layout的层级,但在实际绘制时容易出现过度绘制。可以通过canvas.clipRect()来帮组系统识别那些可见的区域,然后只在这个区域绘制。canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

启动优化

应用启动流程

启动分两种类型:冷启动和热启动

  • 冷启动:系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化Activity,最后显示在界面。
  • 热启动:会从已有的进程中启动,所以热启动不会再创建和初始化Application,而是直接创建和初始化Activity。

启动 -> Application -> attachBaseContext() -> onCreate() -> Activity生命周期

启动耗时检测

  1. adb shell am:使用adb shell获取应用真实启动时间代码
adb shell am start -W [packageName]/[packageName.AppstartActivity]

执行后得到三个时间

  • ThisTime:一般和TotalTime时间一样,如果启动时加来过度全透明的页面预先处理一些事,这样会比TotalTime小;
  • TotalTime:应用启动时间,包括Application和Activity初始化到界面显示;
  • WaitTime:包括系统影响的耗时。

但这个方法只能得到固定某个阶段耗时,不能知道具体方法耗时。可用代码打点方式来得到具体方法耗时。

  1. 代码打点

启动优化方案

启动主要完成三件事:UI布局、绘制和数据准备,因此优化启动速度也是优化这三个过程。

  1. UI布局优化
  • 减少布局层级
  • 避免过度绘制
  1. 启动加载逻辑优化
    数据按需实现加载逻辑
  • 分步加载:以大化小,优先级高的放前
  • 异步加载:耗时多的异步化
  • 延期加载:非必需的数据延时加载

合理的刷新机制

合理的刷新机制要注意以下几点;

  • 尽量减少刷新的次数
  • 尽量避免后台有高CPU线程运行
  • 缩小刷新区域

减少刷新次数

  1. 控制刷新频率:比如刷新进度调可1%刷新一次,而不是实时刷新
  2. 避免没有必要的刷新:先判断是否需要刷新,比如数据没变化、控件不在可见区域就没必要刷新。

避免后台线程影响

后台线程如果开销很大,占用CPU过高,导致系统频繁GC和CPU时间片资源紧张,有可能会导致页面的卡顿。因此在需要迅速刷新的情况下避免这类线程在高峰工作。

缩小刷新区域

采用局部刷新来节约资源

  • 自定义view时:可以使用两个局部更新数据的方法
invalidate(Rect dirty)
invalidate(int left, int top, int right, int bottom)
  • 容器中的某个Item发生了变化,只需更新这个Item即可。

提升动画性能

从三个纬度来对比动画性能

  • 流畅度:核心,控制每一帧动画在16ms以内完成
  • 内存:避免内存泄漏,减小内存开销
  • 耗电:减小运算量,优化算法,减小CPU占用

优化建议

  • 尽量使用属性动画
  • 适当使用硬件加速

使用硬件加速注意几点:

  • 在软件渲染时,可以使用重用Bitmap的方法来节省内存,但开启硬件加速后不起作用。
  • 开启硬件加速的View在前台运行时,需要耗费额外的内存,加速的UI切换到后台时,产生的额外内存有可能不释放。
  • 当UI中存在过度绘制时,硬件加速容易发生问题。

参考书籍:《Android应用性能优化最佳实践》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容