Android性能优化系列之 —— UI 优化 (2)

       最近大家经常谈的话题围绕着 " 毕业985 ,工作 996 ,离职 251 ,访问 404 " 。反正争议很大 ,但是我立场很明确 (准备安排的 mater 30 pro 也因为资金问题变得遥遥无期 )。

        Android的性能优化几乎是面试必考题 ,答案的结构很唯一无非就是内存优化 、卡顿优化 、I/O优化 、UI优化 、启动优化 、储存优化 、闪退优化 、网络优化 、耗电优化 、包体积优化等等 。但是面试的时候只要把任意一个结构突突的很透彻 ,当前的Offer已经成了一半了 ;如果可以把常用的内存优化 、卡顿优化  、I/O 优化 、启动优化突突的很透彻 ,Offer已经成了 ;要是在加上一些储存 、闪退 、网络 、包体积的话 ,就可以主动地谈薪资(有一个情况可能不太行 ,就是公司的需求是写代码的 ,对技术要求无的)。

        PS:会一直更新这个系列 。

「 内存优化 」、「 卡顿优化 」、「 I/O优化 」

UI 优化 」、  「 启动优化 」、「 储存优化 」

「 闪退优化 」、「 网络优化 」、「 耗电优化 」
「 包体积优化 」。
 

前言

每个做 UI 的 Android 开发 ,上辈子都是折翼的天使 。

 多年来,有那么一群苦逼的 Android 开发,他们饱受碎片化之苦,面对着各式各样的手机屏幕尺寸和分辨率,还要与“凶残”的产品和 UI 设计师过招,日复一日、年复一年的做着 UI 适配和优化工作,蹉跎着青春的岁月。更加不幸的是,最近两年这个趋势似乎还愈演愈烈:刘海屏、全面屏,还有即将推出的柔性折叠屏,UI 适配将变得越来越复杂。 

—— Android 开发高手课

UI 优化 

UI 优化究竟是指什么呢 ?

1. 效率的提升

       高效地把 UI 的设计图转化成应用界面 ,并且保证 UI 界面在不同尺寸和分辨率的手机上都是一致的 。

2. 性能的提升

      在正确实现复杂 、炫酷的 UI 设计的同时 ,需要保证用户有流畅的体验 。

UI 渲染的背景知识 

1. 屏幕与适配

谈屏幕适配这个问题 ,相信每一个 Android 都会欲言又止 。Android 作为一个系统平台 ,又作为一个被众多厂商支持的平台 。支持的人多就难免有人会有想法 ,所以导致了 Android 碎片化特严重 。

对于碎片化问题 Android 推荐使用 dp 作为尺寸单位来适配 UI 。

 

通过 dp 加上自适应布局可以基本解决屏幕碎片化问题 ,也就是 Android 推荐使用的适配方案 。但是他会存在两个比较大的问题 。

1. 不一致性。因为 dpi 与实际 ppi 的差异性,导致在相同分辨率的手机上,控件的实际大小会有所不同。

2. 效率。设计师的设计稿都是以 px 为单位的,开发人员为了 UI 适配,需要手动通过百分比估算出 dp 值。

除了直接 dp 适配之外 ,目前我们常用的 UI 适配方案有一下几种 。

1. 限制符适配方案。主要有宽高限定符与 smallestWidth 限定符适配方案,具体可以参考

《Android 目前稳定高效的UI适配方案》、 《smallestWidth 限定符适配方案》

2. 今日头条适配方案。通过反射修正系统的 density 值,具体可以参考

《 一种极低成本的Android屏幕适配方式 》《今日头条适配方案》

2. CPU 和 GPU 

除了屏幕,UI 渲染还依赖两个核心的硬件:CPU 与 GPU。UI 组件在绘制到屏幕之前,都需要经过 Rasterization(栅格化)操作,而栅格化操作又是一个非常耗时的操作。GPU(Graphic Processing Unit )也就是图形处理器,它主要用于处理图形运算,可以帮助我们加快栅格化操作。

Android 渲染的演进

跟耗电一样,Android 的 UI 渲染性能也是 Google 长期以来非常重视的,基本每次 Google I/O 都会花很多篇幅讲这一块。每个开发者都希望自己的应用或者游戏可以做到 60 fps 如丝般顺滑,不过相比 iOS 系统,Android 的渲染性能一直被人诟病。

我曾经在一篇文章看过一个生动的比喻,如果把应用程序图形渲染过程当作一次绘画过程,那么绘画过程中 Android 的各个图形组件的作用是:

画笔:Skia 或者 OpenGL。我们可以用 Skia 画笔绘制 2D 图形,也可以用 OpenGL 来绘制 2D/3D 图形。正如前面所说,前者使用 CPU 绘制,后者使用 GPU 绘制。

画纸:Surface。所有的元素都在 Surface 这张画纸上进行绘制和渲染。在 Android 中,Window 是 View 的容器,每个窗口都会关联一个 Surface。而 WindowManager 则负责管理这些窗口,并且把它们的数据传递给 SurfaceFlinger。

画板:Graphic Buffer。Graphic Buffer 缓冲用于应用程序图形的绘制,在 Android 4.1 之前使用的是双缓冲机制;在 Android 4.1 之后,使用的是三缓冲机制。

显示:SurfaceFlinger。它将 WindowManager 提供的所有 Surface,通过硬件合成器 Hardware Composer 合成并输出到显示屏。

UI 渲染测量

1. 测试工具:Profile GPU Rendering 和 Show GPU Overdraw,具体的使用方法你可以参考 检查 GPU 渲染速度和绘制过度

2. 问题定位工具:Systrace 和 Tracer for OpenGL ES,具体使用方法可以参考 Slowrendering 。

(PS :链接需要翻墙 ,所以可以看下面截图信息)

--------------- 截图部分---------------

--------------- 截图部分---------------

UI 优化的常用手段

UI 渲染的阶段流程图,我们的目标是实现 60 fps,这意味着渲染的所有操作都必须在 16 ms(= 1000 ms/60 fps)内完成。

所谓的 UI 优化,就是拆解渲染的各个阶段的耗时,找到瓶颈的地方,再加以优化。接下来我们一起来看看 UI 优化的一些常用的手段。

1. 尽量使用硬件加速 

PS:由于硬件加速不能支持所有的 Canvas API ,所有有些情况是不能使用硬件加速的 。

2. Create View 优化

View 创建的耗时。请不要忘记,View 的创建也是在 UI 线程里,对于一些非常复杂的界面,这部分的耗时不容忽视。

使用代码创建

使用 XML 进行 UI 编写可以说是十分方便,可以在 Android Studio 中实时预览到界面。如果我们要对一个界面进行极致优化,就可以使用代码进行编写界面。(建议只在对性能要求非常高,但修改又不非常频繁的场景才使用这个方式。)

PS :问了一个大佬说的是因为中间少了一步 xml 解析的过程 ,但是讲真的你让我界面使用 Java 去编写 ,先不说性能问题 ,就是效率上我也搁不住并且以后的维护工作 ,简直就是脑壳疼 。

异步创建

那我们能不能在线程提前创建 View,实现 UI 的预加载吗?尝试过的同学都会发现系统会抛出下面这个异常:


java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()      
  at android.os.Handler.<init>(Handler.java:121)

事实上,我们可以通过又一个非常取巧的方式来实现。在使用线程创建 UI 的时候,先把线程的 Looper 的 MessageQueue 替换成 UI 线程 Looper 的 Queue。

不过需要注意的是,在创建完 View 后我们需要把线程的 Looper 恢复成原来的。

PS: 这个操作很硬 ,亮瞎了我的眼 。

View 重用

正常来说,View 会随着 Activity 的销毁而同时销毁。ListView、RecycleView 通过 View 的缓存与重用大大地提升渲染性能。因此我们可以参考它们的思想,实现一套可以在不同 Activity 或者 Fragment 使用的 View 缓存机制。

但是这里需要保证所有进入缓存池的 View 都已经“净身出户”,不会保留之前的状态。微信曾经就因为这个缓存,导致出现不同的用户聊天记录错乱。

PS :微信现在有时间群里的图像也会错乱 ,原来是这个原因 。

3. measure/layout 优化

渲染流程中 measure 和 layout 也是需要 CPU 在主线程执行的,对于这块内容网上有很多优化的文章,一般的常规方法有:

减少 UI 布局层次。例如尽量扁平化,使用 等优化。

优化 layout 的开销。尽量不使用 RelativeLayout 或者基于 weighted LinearLayout,它们 layout 的开销非常巨大。这里我推荐使用 ConstraintLayout 替代 RelativeLayout 或者 weighted LinearLayout。

背景优化。尽量不要重复去设置背景,这里需要注意的是主题背景(theme), theme 默认会是一个纯色背景,如果我们自定义了界面的背景,那么主题的背景我们来说是无用的。但是由于主题背景是设置在 DecorView 中,所以这里会带来重复绘制,也会带来绘制性能损耗。

PS :在开发的过程中常使用到的

过度绘制区域

PS :Android 开发高手课

发布了57 篇原创文章 · 获赞 40 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_37492806/article/details/103364070