UI 绘制优化_ui绘制_Deemons的博客-程序员秘密

技术标签: Android 性能优化  绘制优化  

wladislaw-sokolowskij-584523-unsplash_meitu_1

Google 在15年初发布了 Android性能优化典范,里面详细谈及了Android 渲染机制。



渲染机制

大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。

Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,那么整个过程如果保证在16ms以内就能达到一个流畅的画面。

如果你的某个操作花费时间超时,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象,产生卡顿。

我们通常都会提到60fps与16ms,并且将60fps作为App性能的衡量标准,是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。

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

作为开发人员,为了保证画面的流畅性,就必须保证稳定的帧率,即保证在16ms 内完成该完成界面渲染。

有很多原因可以导致丢帧,也许是因为你的 layout 太过复杂,无法在16ms内完成渲染,有可能是因为你的 UI 上有层叠太多的绘制单元,还有可能是因为动画执行的次数过多。

接下来,针对这些卡顿的原因,我们慢慢来优化。


1.布局嵌套

Android 的布局文件的加载是 LayoutInflater 利用 pull 解析方式来解析,然后根据节点名通过反射的方式创建出View 对象实例;

子View的位置受父View的影响,如RelativeLayout 需要measure两次才能完成,而嵌套、相互嵌套、深层嵌套等的发生会使measure次数呈指数级增长,所费时间呈线性增长;

因此,随着控件数量越多、布局嵌套层次越深,测量和布局花费的时间几乎是线性增长。

所有,我们要尽可能的减少布局嵌套以及控件个数,保持 view 的树形结构尽量扁平,同时移除所有不需要渲染的view。

这就推荐使用 Hierarchy Viewer 这个可视化工具,在 Android studio 中以此打开 Tools > Android > Android Device Monitor ,然后再打开 Window > Open Perspective > Hierarchy View,就可以显示布局的层次结构。

hierarch view

详细的使用方法,请 Google。

借助工具,我们可以方便的查看页面的布局情况,同时配合使用布局标签 merge、viewstub (Merge 减少嵌套层次、ViewStub 延迟初始化)来尽可能减少布局嵌套,从而达到优化布局的目的。


2.过渡绘制

一个像素点,仅仅绘制一次是最优的方式,而由于布局的重叠,有可能会导致一块区域被多次绘制,这就是过度绘制(Overdraw)。

Android 系统提供了检测 Overdraw 的调试工具,在手机中打开设置——开发者选项——调试GPU过度绘制——显示过度绘制区域。

overdraw

打开工具后,会发现界面多了很多色块,不必担心,这些颜色都有不同的含义:

原色 – 没有过度绘制 – 这部分的像素点只在屏幕上绘制了一次。

蓝色 – 1次过度绘制– 这部分的像素点只在屏幕上绘制了两次。

绿色 – 2次过度绘制 – 这部分的像素点只在屏幕上绘制了三次。

粉色 – 3次过度绘制 – 这部分的像素点只在屏幕上绘制了四次。

红色 – 4次过度绘制 – 这部分的像素点只在屏幕上绘制了五次。

这样通过工具就可以查看哪些地方出现了过度绘制。

解决的方法也很简单,就是移除 View 中不必要的Background 。

可以借助工具,再对照着自身的布局文件,很容易找到并移除不必要的背景。

特别注意还有 window 也是有Background ,这个背景很容易被忽视。移除方式如下:

getWindow().setBackgroundDrawable(null);

除了布局文件,在自定义控件中也容易产生 Overdraw,这时候就需要 使用clipRect() 裁剪画布,从而避免 Overdraw。

具体实例,请查看鸿洋的这篇博客 Android UI性能优化实战 识别绘制中的性能问题


3.UI 线程复杂运算

Android 为了提升画面的流畅性,特意使用一个线程来绘制图像,这个线程就是所谓的主线程,也可以称为 UI线程,而且因为刷新 UI 时有做线程校验,所有也只能是 UI 线程来刷新 UI(SurfaceView、TextureView除外)。
当我们在 UI 线程里做一些复杂的耗时操作时,占用了 UI 线程的资源,很容易使得 UI 线程来不及去绘制画面,从而导致丢帧,产生卡顿。

所有解决的方法是:

首先检测出耗时的地方,然后优化代码提升执行效率,如果无法优化就将其放过在子线程中执行。

检测耗时的工具,有前文 App 启动优化 中已经介绍过的TraceView、Hugo ,现在介绍另一种 StrictMode(严苛模式)

StrictMode 就是用来指定一系列策略(policy),对相应规则(rule)进行检查并且做出反应。这些策略大致包括Android 的编码规范,例如监控在主线程(UI线程)中的操作等等。StrictMode 有不同的策略 (ThreadPolicy 和 VmPolicy ),每种策略又用不同的规则(rule),每种规则又对应不同的方法,一旦规则被违反,这些对应的方法就会被用来做出反应。

先来一段实例代码

public void onCreate() {
     if (BuildConfig.DEBUG) {
         // 针对线程的相关策略
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads() //侦测磁盘读
                 .detectDiskWrites() //侦测磁盘写
                 .detectNetwork()   //侦测网络操作
                 .detectCustomSlowCalls()   //侦测自定义的耗时操作
             //  .detectAll()       //侦测一切潜在违规
                 .penaltyLog()      //违规时,打印日志
                 .build());

         // 针对VM的相关策略
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()      //违规时,打印日志
                 .penaltyDeath()    //违规时,直接使应用崩溃
                 .build());
     }
     super.onCreate();
 }

注意,StrictMode 不要在线上版本中使用。如果想了解更多使用方式,请 Google。


4.频繁的 GC

这里直接引用 Google 发布 Android 性能优化典范 - 开源中国社区 中的原文解释:

虽然 Android 有自动管理内存的机制,但是对内存的不恰当使用仍然容易引起严重的性能问题。在同一帧里面创建过多的对象是件需要特别引起注意的事情。

Android系统里面有一个Generational Heap Memory的模型,系统会根据内存中不同 的内存数据类型分别执行不同的GC操作。例如,最近刚分配的对象会放在Young Generation区域,这个区域的对象通常都是会快速被创建并且很快被销毁回收的,同时这个区域的GC操作速度也是比Old Generation区域的GC操作速度更快的。

img

除了速度差异之外,执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行。

通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了。

导致GC频繁执行有两个原因:

  • Memory Churn内存抖动,内存抖动是因为大量的对象被创建又在短时间内马上被释放。
  • 瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加 Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。

解决上面的问题有简洁直观方法,如果你在Memory Monitor 里面查看到短时间发生了多次内存的涨跌,这意味着很有可能发生了内存抖动。

img

同时我们还可以通过Allocation Tracker来查看在短时间内,同一个栈中不断进出的相同对象。这是内存抖动的典型信号之一。

当你大致定位问题之后,接下去的问题修复也就显得相对直接简单了。例如,你需要避免在for循环里面分配对象占用内存,需要尝试把对象的创建移到循 环体之外,自定义View中的onDraw方法也需要引起注意,每次屏幕发生绘制以及动画执行过程中,onDraw方法都会被调用到,避免在onDraw 方法里面执行复杂的操作,避免创建对象。对于那些无法避免需要创建对象的情况,我们可以考虑对象池模型,通过对象池来解决频繁创建与销毁的问题,但是这里 需要注意结束使用之后,需要手动释放对象池中的对象。


参考

Android性能优化(二)之布局优化面面观

UI性能优化实战 识别绘制中的性能问题

性能优化之布局优化

Android App优化之消除卡顿

Google 发布 Android 性能优化典范 - 开源中国社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Deemons/article/details/80377354

智能推荐

[ 数据集 ] MINIST 数据集介绍_minist数据集_Horizon Max的博客-程序员秘密

[ 数据集 ] MINIST 数据集介绍MINIST``Size:`` 28×28 灰度手写数字图像;``Num:`` 训练集 60000 和 测试集 10000,一共70000张图片;``Classes:`` 0,1,2,3,4,5,6,7,8,9;具体介绍了数据集的读取与可视化操作等...

AlertDialog的复选对话框_MX-乐享科技的博客-程序员秘密

string.xml代码:    AlertDialog3    Hello world!    Settings    显示多选列表对话框    确定    多选列表对话框array.xml代码:            游泳        打篮球        写java程序    main.xm

Electron和Vue_carter_yu的博客-程序员秘密

文章参考:XCel 项目总结 - Electron 与 Vue 的性能优化再抄一遍就没意思了,自己去看原文吧~~~PDF防失联

图神经网络,如何变深?_智源社区的博客-程序员秘密

撰文:侯振宇编辑:贾 伟现实世界中很多很多任务可以描述为图(Graph)问题,比如社交网络,蛋白质结构,交通路网数据,图是一个很值得研究的领域。近年来,随着深度学习的发展,研究人员借鉴...

聊一聊程序员的自我修养_weixin_30466039的博客-程序员秘密

首先要谈的是,今天的话题所聊的程序员包含哪些人?在中国,写程序,不仅仅是一种兴趣,更多的时候,还是一种普通职业和谋生工具大公司有厉害的程序员,优秀的架构师,但大量的小公司也有很多普通的程序员。在我这些年的工作经历中,也越来越深刻的感受到普通程序员的影响和力量。对于高阶程序员,所谓八仙过海各有神通,各有各的成就,各有各的修养,但程序员在达成较高的水平之前,有一些“自我修养”,是最基础的,是普...

c++第二次试验1_songjian_m的博客-程序员秘密

1.问题及代码/* * 文件名称:1111* 作 者:宋剑 * 完成日期:2016 年 3月 24 日 * 版 本 号:v1.0 * 对任务及求解方法的描述部分: * 输入描述:无 * 问题描述:我的第二个C++程序,熟悉程序的编辑、运行过程 * 程序输出:标准体重以及体重情况 * 问题分析:略 * 算法设计:略 */ #inc

随便推点

Java后端使用socketio,实现小程序答题pk功能_socket io后台使用方式_程序员万京游的博客-程序员秘密

在使用socket.io跟前端通信过程中,出现了一系列问题,现做下记录。一、功能需求在小程序端,用户可相互邀请,进入房间后进行答题PK。实现方法是,用户点击邀请好友,建立连接,查询当前是否有房间,有房间发送消息给两人,匹配成功,开始pk。没有房间新建房间返回,等待20秒,等待别人匹配。代码如下,先看配置,在application.yml配置文件中增加如下配置配置类package com.cwn.wethink.remy.handler;import com.corun...

正在或即将被使用的Go依赖包管理方法:Go Modules,Go 1.13的标准特性_李佶澳的博客-程序员秘密

公众号原文地址:https://mp.weixin.qq.com/s/SGGV3tWEg5AAJ7I_FcK0cg目录 目录 说明 初始化 依赖包的默认导入 依赖包的特定版本导入 查看已添加依赖 依赖包的存放管理 依赖包的版本切换 删除未使用依赖包 引用项目中的 package 引用不同版...

Cesium入门9 - Loading and Styling Entities - 加载和样式化实体_Cesium中文网的博客-程序员秘密

Cesium入门9 - Loading and Styling Entities - 加载和样式化实体Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/现在我们已经为我们的应用程序设置了Viewer配置、imagery和terrain的阶段,我们可以添加我们的应用程序的主要焦点——geocache数据。为了便...

关于一次mysql的列属性not null的坑爹排查_行走的code的博客-程序员秘密

背景:       先说下背景情况,最近在工作中遇到了一个坑爹的事情,有个数据从A库到B库之后,某个字段数据不一致,原来的数据是值null,到新库之后变成了‘’, 这就奇怪了?于是查看了一下表结构,在A库源库的表结构的定义为了 xxx  varchar(32) default null ,而在目标库的表结构为 xxx varchar(32) not null default ''类型,按理说如

javascript利用crypto.js进行 AES加密解密_宁静之峰的博客-程序员秘密

#举例var testData = [];for(var i=0;i<128*1024;i++){testData.push(i)}//转换为 Uint8var buffer = new Uint8Array(testData);//将 Uint8 转换成 WordArrayvar WordArray = CryptoJS.enc.u8array.parse(buffer);...

试题 基础练习 字母图形_Vastsa的博客-程序员秘密

资源限制时间限制:1.0s 内存限制:256.0MB问题描述利用字母可以组成一些美丽的图形,下面给出了一个例子:ABCDEFGBABCDEFCBABCDEDCBABCDEDCBABC这是一个5行7列的图形,请找出这个图形的规律,并输出一个n行m列的图形。输入格式输入一行,包含两个整数n和m,分别表示你要输出的图形的行数的列数。输出格式输出n行,每个m个字...

推荐文章

热门文章

相关标签