APP性能优化——布局优化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Alexwll/article/details/82423125

1、简介

有人对安卓的性能总结为快、稳、省、小,四字真言简单直接;本篇主要聊一下快当中布局优化,也是性能优化中最简单的一部分,可能有人认为布局的修改对性能优化的提升微乎其微,但积少成多性能总是被无数细微的点拖垮的,更何况当你明知道代码当中有可以优化的地方,难道会放着不动吗?

2、安卓系统显示原理

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

  • 从上面可以看出,界面的显示是经过测量、布局、绘制、渲染最后呈现在手机屏幕上,那么只要这四个过程都变快界面的显示自然就变快了
  • 在我们想变快之前首先来了解一下界面为什么会变慢,变卡?只有知道为什么会形成这样的问题,才能更好的解决问题

首先我们来了解一个名词:FPS

FPS 表示每秒传递的帧数。在理想情况下,当APP达到60 FPS 就会很流畅,不会有卡顿和丢帧的感觉,这意味着每个绘制时长应该在16 ms = 1000/60 以内。但是 Android 系统很有可能无法及时完成那些复杂的页面渲染操作。

Android 系统每隔 16ms 发出 VSYNC 信号,触发对 UI 进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需的 60FPS。

  • 丢帧:出现丢帧的原因是因为前一帧的操作花费的时间超过了16ms或更长;

如:某个操作花费的时间是 24ms ,系统在得到 VSYNC 信号时就无法正常进行正常渲染,此时在两侧渲染即32S的时间内,用户看到的是同一帧画面,而在32S结束后显示的是当时GPu渲染的帧,那中间的那一帧就被丢失调了

如上图显示:当每秒传递的帧数为30fps,即每帧的时间为1000/30 = 33ms;此时没两针之间就丢失一帧,所以用户看到的画面时跳跃的,而不是渐变的

3、优化

 3.1 测量、布局的优化

  • 问题原因
  1. 布局层次嵌套过深
  2. 多次测量
  • 优化方向
  1. 简化界面布局结构,缩短测量和布局的执行时间
  2. 减少界面或控件的测量次数

3.1.1布局层级优化

  • 检查布局工具:HierarchyView

在开始优化之前起码要先知道此时代码的布局方式,即布局的嵌套等级,这是就需要使用HierarchyView工具, 它查看Layout的层级图,根据层级图判断那些布局是可以被替换或减少的,以简化布局的嵌套

模拟器使用:直接运行程序到模拟器,再打开DDMS工具中选择HierarchyView,即可按找界面生成布局层级图

真机使用:

  1. 配置环境:在外层build。gradle中添加仓库地址:
maven { url "https://jitpack.io" }

    2、在moudle中引入ViewServer

implementation 'com.github.romainguy:ViewServer:017c01cd512cac3ec054d9eee05fc48c5a9d2de'

    3、在需要检查的Activity中的onCreat和onDestory中添加

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);
    ViewServer.get(this).addWindow(this);
}
@Override
protected void onDestroy() {
    super.onDestroy();
    ViewServer.get(this).removeWindow(this);
}

   4、运行程序到手机上,进入DDMS工具中的HierarchyView中即可查看

  •  ConstraintLayout

首先介绍一个可以大幅度减少界面嵌套的布局 ConstraintLayout,也是谷歌新推出的布局,功能强大使用简单,对于一般的界面基本不需要嵌套,真的时布局中的神兵利器,如果不熟悉或没使用的可以查看之前的文章ConstrainLayout,下面的例子也是以慈不惧为基础

  • include

include的强大指出就是可以实现界面布局的复用,在多个界面都需要同样的布局或控件时,直接使用include导入,如:ToolBar,下面看个简单的例子

创建 include.xml文件,中间是两个按钮

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button9"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <Button
        android:id="@+id/button10"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button9" />
</android.support.constraint.ConstraintLayout>

在需要的地方使用include导入布局

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MergeActivity">

    <include
        layout="@layout/merge"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

这样只要任何需要的地方都可以直接使用include,而避免代码的重复编写,显示结果:

  • merge

我们先看一下上个例子中两个按钮布局,此时的布局结构:

从上图可以看出界面中有两个ConstrainLayout,而中间并没有子布局的出现,额外增加了一层,那有办法去掉中间的ConstrainLayout,直接将子View放到第一个布局中呢?这是就是merge登场的时刻

  1. 作用:当两层布局相同且中间没有子布局时,用于消除多余的一层布局
  2. 使用:我们将上面例子的include.xml文件的跟布局替换成<merge>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
>
......
</merge>

此时的布局层级如下图,减少了一层ConstrainLayout

3.1.2、ViewStub

  1. 有时我们的布局中回存在一些只有在特定的情况下才需要显示的布局,如果我们只是设置visibility时,布局仍然是会在加载的时候进行测量和绘制,这样对布局的加载就会造成额外的开销,影响加载速度,在这样情况下使用ViewStub可以改善这种情况,ViewStub只是先占个位置,对于要显示的时候调用ViewStub.inflate()或ViewStub.visibility用于显示布局。
<ViewStub
    android:id="@+id/stub_import"
    android:inflatedId="@+id/panel_import"
    android:layout="@layout/progress_overlay"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom" />

3.1.3、减少Measure的过程

  1. 减少 wrap_content的使用。wrap_content 会增加布局 measure 时计算成本,在已知宽高为固定值时,不用wrap_content 
  2. 删除控件的无用属性
  3. listView 的Item 中避免使用 weight 

3.2、绘制优化

  • GPU过度绘制

在开发者选项中开启“调试Gpu过度绘制”,界面会根据绘制的次数显示不同的颜色,颜色与过度绘制的标准:

  1. 原色:没有过度绘制
  2. 蓝色:1 次过度绘制
  3. 绿色:2 次过度绘制
  4. 粉色:3 次过度绘制
  5. 红色:4 次及以上过度绘制
  • 优化方法
  1. 布局上的优化。移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片
  2. 对于绘制有重叠的View,使用clipRect和clipPath方法限制View的绘制区域,只有在这个区域内才会被绘制
  • GPU分析

在手机开发者模式下,有一个卡顿检测工具叫做:Profile GPU Rendering,他的功能特点如下:

  1. 一个图形监测工具,能实时反应当前绘制的耗时
  2. 横轴表示时间,纵轴表示每一帧的耗时
  3. 随着时间推移,从左到右的刷新呈现
  4. 提供一个标准的耗时,如果高于标准耗时,就表示当前这一帧丢失
  5. 不要在View的onDraw中 使用循环 和 创建对象,否则内存会波动频繁

通过打开开发者选项–>GPU模式呈现分析–>在屏幕上显示为条形图,此时手机会以条状图显示界面的每个过程的耗时,显示界面如下:

  • 每一条柱状线都包含三部分
  1. 蓝色代表测量回执Display List的时间
  2. 红色代表 OpenGL 渲染 Display List所需要的时间
  3. 黄色代码CPU 等待GPU的处理的时间
  • 有关输出的几点注意事项
  1. 对于每个可见应用,此工具将显示一个图表
  2. 沿水平轴的每个竖条都代表一个帧,每个竖条的高度表示渲染该帧所花的时间(单位:毫秒)
  3. 水平绿线表示 16 毫秒。 要实现每秒 60 帧,代表每个帧的竖条需要保持在此线以下。 当竖条超出此线时,可能会使动画出现暂停
  4. 此工具通过加宽对应的竖条并降低透明度来突出显示超出 16 毫秒阈值的帧
  5. 每个竖条都有与渲染管道中某个阶段对应的彩色区段。 区段数因设备的 API 级别而异
  • 上图中显示每个阶段绘制的时间,每个颜色的代表意义

  1. Misc:额外的工作时间,表示两个帧之间UI线程上的工作;过大原因:将方法回调放在其他线程
  2. Input:表示处理输入事件和和输入回调的时间;过大原因:回调中主线程执行了耗时操作;优化措施:直接优化或开辟线程
  3. Anim:动画每帧运行的时间;过大原因:动画的某些属性更改而正在执行的工作的结果;
  4. Measure/Layout:布局和测量的时间 ;过大原因:大量视图或布局层级解构问题(代码 onLayout()或 onMeasure()方法也会导致问题)
  5. Draw:绘制阶段将视图的渲染操作; 过大原因:自定义视图onDraw()执行大量操作;
  6. upLoad:当前帧期间将位图对象从CPU内存传输到GPU内存所需的时间;过大原因:资源负荷过大(位图接近屏幕大小、大量的缩略图);解决方法:a:确保您的位图分辨率不大于显示大小(1024*1024 显示在 48*48控件上); b:利用prepareToDraw()异步预上传位图
  7. Issue:发布将显示列表绘制到屏幕的时间(包括转换和裁剪);过大原因:过多的绘制和发布;解决方法:降低GPU上工作的复杂性
  8. swap:Android完成将所有显示列表提交给GPU,系统就会发出一个最终命令,驱动程序最终可以将更新的图像呈现给屏幕;问题原因:在CPU发出比GPU消耗命令更快,当通信队列变满时CPU处于阻塞状态;解决方法:降低GPU上工作的复杂性

猜你喜欢

转载自blog.csdn.net/Alexwll/article/details/82423125