场景

UI

  • 绘制
  • 刷新

启动

  • 冷启动
  • 温启动
  • 热启动

跳转

  • 页面间切换
  • 前后台优化

响应

  • 按键
  • 系统事件
  • 滑动

根本原因

界面绘制

  1. 绘制层级太深
  2. 页面复杂:控件个数 > 80。
  3. 刷新不合理

数据处理

  1. 在UI主线程处理数据
  2. 数据处理占用CPU高,导致主线程拿不到时间片
  3. 内存增加导致GC频繁而引起的卡顿

Android系统显示原理

渲染简述

Android应用把经过测量,布局,绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到显示屏幕上,通过Android的刷新机制来刷新绘制。

  • 应用层负责绘制
  • 系统层负责渲染
  • C/S架构
  • SurfaceFlinger(C++)
    • Java API
    • C++底层具体实现

应用层

  1. Measure
    • 深度优先:先遍历子节点,再遍历兄弟节点。
    • 广度优先:先遍历兄弟节点,在遍历子节点。
  2. Layout:深度优先
  3. Draw
    • 软件绘制
    • 硬件加速(GPU)
      • 比CPU耗电
      • 兼容问题
      • 内存大

系统层

  1. 主要工作
    1. 响应客户端事件,创建Layer与客户端的Surface建立联系
    2. 接收客户端数据及属性,修改Layer属性,如尺寸,颜色,透明度等
    3. 将创建的Layer内容刷新到屏幕上
    4. 维持Layer的序列,并对Layer最终输出做出裁剪计算
  2. SharedClient - SharedBufferStack  1 : 31  匿名共享内存
  3. SharedBufferStack 包含了N个缓冲区  < 4.1 N = 2 > 4.1 N =3
  4. FPS 60 16ms
  5. 刷新机制
    1. 双缓冲
    2. VSYNC
    3. Choreographer
      • Callback_input:优先级最高,与输入事件有关
      • Callback_animation:第二优先级,与动画有关
      • Callback_traversal:最低优先级,与UI控件绘制有关

性能分析工具

  • Systrace
  • TraceView
  • Profile GPU Rendering
    • 蓝色:测量绘制的时间
    • 红色:执行的时间 Display List
    • 橙色:处理时间
    • 紫色:将资源转移到渲染线程的时间
    • shell dumpsys gfxinfo com_xxx_xxx

布局优化

工具

  1. Hierarchy View
  2. Layout Inspactor

避免过度绘制

  1. 定义:布局优化主要就是避免Overdraw。
  2. 什么是Overdraw?Overdraw就是过度绘制,是指在一帧的时间内(16.67ms)像素被绘制了多次,理论上一个像素每次只绘制一次是最优的,但是由于重叠的布局导致一些像素会被多次绘制,而每次绘制都会对应到CPU的一组绘图命令和GPU的一些操作,当这个操作耗时超过16.67ms时,就会出现掉帧现象,也就是我们所说的卡顿,所以对重叠不可见元素的重复绘制会产生额外的开销,需要尽量减少Overdraw的发生。
  3. 查看Overdraw
    1. 在开发者选项-调试GPU过度绘制(Show GPU Overdraw)
    2. 没有颜色: 意味着没有overdraw。像素只画了一次。
    3. 蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)
    4. 绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。
    5. 浅红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。
    6. 暗红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。
  4. 如何优化
    • 合理选择控件容器
      • LinearLayout
      • TableLayout
      • FrameLayout
      • RelativeLayout
      • ConstraintLayout
    • 去掉window的默认背景
    • 去掉其他不必要的背景
    • ClipRect & QuickReject:为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗。但是不幸的是,对于那些过于复杂的自定义的View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。除了clipRect方法之外,我们还可以使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。
    • ViewStub
    • Merge
    • 善用draw9patch
    • 慎用Alpha
    • 避免“OverDesign”

启动优化

启动方式

  • 冷启动:当启动应用时,后台没有该应用的进程,这时系统会首先会创建一个新的进程分配给该应用,这种启动方式就是冷启动。
  • 热启动:当启动应用时,后台已有该应用的进程,比如按下home键,这种在已有进程的情况下,这种启动会从已有的进程中来启动应用,这种启动方式叫热启动。
  • 温启动:当启动应用时,后台已有该应用的进程,但是启动的入口Activity被干掉了,比如按了back键,应用虽然退出了,但是该应用的进程是依然会保留在后台,这种启动方式叫温启动。

启动命令

  1. 如何对比启动时间
    1. adb shell am start -W packagename/MainActivity
    2. adb shell am start -S -R 10 -W packagename/.MainActivity
      • -S表示每次启动前先强行停止
      • -R表示重复测试次数
    3. 参数:
      1. ThisTime:最后一个启动的Activity的启动耗时;
      2. TotalTime:新应用启动的耗时,包括新进程的启动和Activity的启动;
      3. WaitTime(5.0之前没有):ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)。
    4. 系统日志统计:过滤displayed输出的启动日志。
  2. WaitTime 是 startActivityAndWait 这个方法的调用耗时
  3. ThisTime 是指调用过程中最后一个 Activity 启动时间到这个 Activity 的 startActivityAndWait 调用结束。
  4. TotalTime 是指调用过程中第一个 Activity 的启动时间到最后一个 Activity 的startActivityAndWait 结束 如果过程中只有一个 Activity ,则 TotalTime 等于 ThisTime。

性能检查项

  1. Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量、布局、绘制显示在界面上
  2. 任务分优先级
    1. 优先级为1 启动加载
    2. 优先级为2 首页渲染完成后 开始加载
    3. 优先级为3 首页渲染后 延迟加载
  3. traceview
    1. 找出单个方法执行时间长的
    2. 找出执行次数多的
  4. 优化思路总结
    1. UI渲染,去除重复绘制
    2. 根据优先级划分初始化工作
    3. SharedPreference优化
    4. 网络错误优化,使用ViewStub
    5. Multidex优化
    6. 检查BaseActivity

任务

  1. 加载启动App
  2. App启动之后立即展示出一个空白的Window
  3. 创建App的进程
  4. 创建App对象
  5. 启动Main Thread
  6. 创建启动的Activity对象
  7. 加载View
  8. 布置屏幕
  9. 进行第一次绘制
  10. 完成第一次绘制后会把Main Activity替换已经展示的Background Window

启动加速的优化方向

  1. 利用提前展示出来的Window,快速展示出一个画面,给用户快速反馈的体验。
    • 设置背景图Theme:程序启动快,界面先显示背景图,然后再刷新其他界面控件。给人刷新不同步感觉。
    • 把样式设置为透明:给人程序启动慢感觉,界面一次性刷出来,刷新同步。
  2. 避免在启动时做密集沉重的初始化
    • 四个维度:
      • 必要且耗时:启动初始化,考虑用线程来初始化。
        • MultiDex初始化
        • Tinker初始化
      • 必要不耗时:首页绘制
      • 非必要耗时:数据上报,插件初始化
      • 非必要不耗时:直接去掉,在需要用的时候再加载。比如:其他第三方组件的初始化
    • 思考方向:
      • 分步加载:以大化小,优先级高的放前。
      • 异步加载:耗时多的异步化。
      • 延期加载:非必要的数据延时加载。
  3. 定位问题
    • 避免IO操作
    • 序列化、反序列化
    • 网络操作
    • 布局嵌套

通用启动优化方案

  1. 利用主题快速显示界面。
  2. 异步初始化组件。
  3. 通过梳理业务逻辑,延迟初始化组件、操作。
  4. 正确使用线程:开启线程池比单独开启一个线程耗资源。
  5. 去掉无用代码、重复逻辑等

如何查找优化点

  1. 开发阶段使用BlockCanary 或者ANRWatchDog等第三方监控sdk。
  2. Method Tracing
  3. Systrace
  4. nimbledroid
  5. Lint

常见问题

  1. 部分的数据库,I/O操作发生在MainActivity主线程。
  2. Application中创建了线程池。
  3. Application中做了大量的初始化操作。
  4. MainActivity网络请求密集:
    1. 是否有必要。
    2. 接口是否可以合并。
  5. 工作线程使用没设置优先级:AyncTask- thread_priority_background、AsyncQueryHandler - thread_priority_default、线程池。
  6. 信息没缓存,重复获取同样信息。
  7. 不合理的业务流程:高级与低级区别。
  8. 废弃的老代码。

刷新机制

合理的刷新需要注意的几点:

  1. 尽量减少刷新次数:控制刷新频率,进度条,变化没1%,完全没必要刷新。
  2. 避免没有必要的刷新:
    1. 数据没有变化,需要刷新的控件在不可见区域。
    2. 一个View从不可见到可见,一定要刷新一次。
  3. 尽量避免后台有高CPU的线程运行。
  4. 缩小刷新区域:
    1. invalidate(Rect dirty)。
    2. invalidate(int left,int top,int right,int bottom)。
    3. RecyclerView。

提升动画性能

  1. 帧动画:消耗资源最多
  2. 补间动画
  3. 属性动画
  4. 硬件加速
    • Application级别:<application android:hardwareAccelerated=”true” …/>
    • Activity级别:<activity android:haredwareAccelerated=”false/true” …/>
    • Window级别:getWindow().setFlags(WindowManager.LayoutParams.FLAG_HAREDWARE_ACCELERATED);
    • View级别:View.setLayerType(View.LAYER_TYPE_SOFTWARE,null);
      • LAYER_TYPE_NONE
      • LAYER_TYPE_HARDWARE:绘制为硬件纹理。
      • LAYER_TYPE_SOFTWARE:此View通过软件渲染为一个Bitmap。
  5. 动画流程总结:
    1. 将要执行动画的View的LayerType设置为LAYER_TYPE_HARDWARE
    2. 计算动画View的属性等信息,更新View的属性
    3. 若动画结束,将LayerType设置为NONE
  6. 需要注意的点:
    1. 在软件渲染时,可以重用bitmap的方式来节省内存,但开启了硬件加速,这个方案就不起作用。
    2. 开启硬件加速 需要额外的内存,加速的UI切换到后台时,产生的内存有可能不释放。
    3. 在UI中的过渡绘制时,硬件加速会比较容易发生问题。

卡顿监控方案