最近大家经常谈的话题围绕着 " 毕业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 优化究竟是指什么呢 ?
1. 效率的提升
高效地把 UI 的设计图转化成应用界面 ,并且保证 UI 界面在不同尺寸和分辨率的手机上都是一致的 。
2. 性能的提升
在正确实现复杂 、炫酷的 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 的 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 合成并输出到显示屏。
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.(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 开发高手课