资深程序员带你解锁 Android 性能优化五大误区和两大疑点!(附333页性能优化PDF宝典)

推荐学习资料

  • Android进阶学习全套手册

  • Android对标阿里P7学习视频

  • BAT TMD大厂Android高频面试题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Google 云端硬盘团队目前已将其应用程序从 Java 全面替换为 Kotlin,重构范围涉及 170 多个文件,超过 16,000 行代码,包含 40 多个编译产物,在团队监控的指标中,第一要素是启动时间,测试结果如下:

如图所示,使用 kotlin 并没有对性能造成实质的影响,而且在整个基准测试过程中,Google 团队也都没有观察到明显的性能差异,即使编译时间和编译后的代码大小略有增加,但都保持在 2% 之内,完全可以忽略不计。而得益于 kotlin 简洁的语法,团队的代码行却减少了大约 25%,也变得更易读和易维护。

还比较值得一提的是,使用 kotlin 时,我们也可以使用像 R8 这样的代码缩减工具,对代码进行进一步的优化。

误区二:Getters 和 Setters 方法更耗时

因为担心性能下降,有些开发者会选择在类中直接使用 public 修饰字段,而不去写 getter 和 setter 方法,如下面这段代码,这里的 getFoo() 方法就是变量 foo 的 getter 函数:

public class ToyClass {

public int foo;

public int getFoo() { return foo; }

}

ToyClass tc = new ToyClass();

直接使用 tc.foo 获取变量显然已经破坏了面向对象的封装性,而在性能方面,我们在配备 Android 10 的 Pixel 3 上使用 Jetpack Benchmark 对 tc.getFoo () 与 tc.foo 两个方法进行了基准测试,该库提供了预热代码的功能,最终的稳定测试结果如下:

getter 方法的性能与直接 access 变量的性能也并没有多大差别,结果并不奇怪,因为 Android RunTime (ART) 内联了代码中所有的 getter 方法,因此,在 JIT 或 AOT 编译后执行的代码是相同的,正因如此,在 kotlin 中即使我们默认需要使用 getter 或 setter 获得变量,性能也并不会有所下降,如果使用 Java,除非特殊需要,否则就不应该使用这种方式破坏代码的封装性。

误区三:Lambda 比内部类慢

Lambda(尤其是在引入 Stream API 的情况下)是一种非常方便的语法,可实现非常简洁的代码。如下这段代码,对对象数组的内部字段值求和,这里,使用了 Stream API 搭配 map-reduce 操作:

ArrayList array = build();

int sum = array.stream().map(tc -> tc.foo).reduce(0, (a, b) -> a + b);

第一个 lambda 会将对象转换为整数,第二个 lambda 会将产生的两个值相加。

下面代码中,我们再将 lambda 表达式换成内部类:

ToyClassToInteger toyClassToInteger = new ToyClassToInteger();

SumOp sumOp = new SumOp();

int sum = array.stream().map(toyClassToInteger).reduce(0, sumOp);

这里,有两个内部类:一个是 toyClassToInteger,它可以将对象转换为整数,第二个 SumOp 用来做求和运算。

从语法上看,第一个带有 lambda 的示例显然更优雅,也更易读。那么,性能差异又如何呢?我们再次在 Pixel 3 上使用了 Jetpack Benchmark,也没有发现性能差异:

从图中可以看到,我们还定义了单独的外部 (top-level) 类一起来做比较,发现性能都没有什么差异,原因就是 lambda 表达式最终也会被转换为匿名内部类。因此,为了代码的简洁易读,在这种场景下 lambda 表达式就是第一选择。

误区四:对象分配开销过大,应该使用对象池

Android 内置了最先进的内存分配和垃圾回收机制,如下图所示,几乎每个版本的更新都在对象分配方面做各式各样的更新。

各个版本之间的垃圾收集性能都有显著的改善,如今,垃圾收集对应用程序的流畅已经几乎没有影响了。下图展示了 Google 官方在 Android 10 中对具有分代并发收集的对象收集所做的改进,新版本的 Android 11 中也有明显的改进。

在 GC 基准测试(例如 H2)中,吞吐量大幅提高了 170% 以上,而在实际应用(如 Google Sheets)中,吞吐量也提高了 68%。

如果认为垃圾收集效率低下并且内存分配负担很重,那么就相当于认为创建的垃圾越少,垃圾收集工作就越少,因此,代替每次使用时都创建新对象,我们可以维护一个经常使用的类型的对象池,然后从池中获取已创建的对象,如下:

Pool pool[] = new Pool<>[50];

void foo() {

A a = pool.acquire();

pool.release(a);

}

这里省略了代码细节,大体就是就是定义了一个 pool,从 pool 中获取对象,然后最终释放。

要测试这种场景,我们使用微基准测试 (microbenchmark):从池中测试分配对象的开销,以及 CPU 的开销,来确定垃圾回收是否会影响应用程序的性能。

在这种情况下,我们依然可以在装有 Android 10 的 Pixel 2 XL 上循环运行了数千次分配对象的代码,因为对于小型或大型对象,性能可能会有所不同,我们还通过添加不同的字段来模拟不同的对象大小,最终的开销结果如下:

用于垃圾回收的 CPU 开销的结果如下:

从图中可以看出,标准分配和池化对象之间的差异也很小,但是,当涉及到较大对象的垃圾回收时,池解决方案略微高一点。

这个结果并不意外,因为池化对象会增加应用的内存占用量,此时,应用突然占用了太多的内存,即使由于池化对象减少了垃圾回收调用的数量,每个垃圾回收调用的成本也更高,因为垃圾收集器必须遍历更多的内存才能确定哪些对象需要被收集,哪些对象需要保留。

那么,对象是否应该被池化,这还是主要取决于应用的需求。如果不考虑到代码复杂性,池化对象有如下缺点:

  • 提高内存占用量

  • 使对象存活变长

  • 需要非常完善的对象池机制

但是,池的方法对于大并且耗时的对象分配可能确实是有效的,关键是要记住在选择方案之前进行充分的测试。

误区五:debug 模式下进行性能分析

在 debug 的同时对应用进行性能分析非常方便,毕竟,我们通常也是在 debug 模式下进行编码的,并且,即使 debug 应用中的性能分析不准确,也可以更快地进行迭代修改提高效率,然后事实是并没有

为了验证这一误解,我们分析了 Activity 相关的常见操作过程过的测试结果,如下图:

在某些测试(例如反序列化)中,debug 与否对性能没有影响,但是,有些结果却有 50% 甚至以上的差别,我们甚至发现结果速度可能会慢 100% 的例子,这是因为 runtime 在 debug 模式下时对代码几乎没有优化,因此与用户在生产设备上运行的代码有很大不同。

在 debug 模式下进行性能分析的结果是可能会误导优化方向,导致浪费时间来优化不需要优化的内容。

疑点

现在,我们需要有意识的逃避上述提到的五大误区,下面我们再来看一下一些日常开发中不太明显,但我们经常会有的疑惑的问题,事实结果可能也与我们想的大相径庭。

疑点 1:Multidex:是否影响应用性能?

面试复习路线,梳理知识,提升储备

自己的知识准备得怎么样,这直接决定了你能否顺利通过一面和二面,所以在面试前来一个知识梳理,看需不需要提升自己的知识储备是很有必要的。

关于知识梳理,这里再分享一下我面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

  • 架构师筑基必备技能
  • Android高级UI与FrameWork源码
  • 360°全方面性能调优
  • 解读开源框架设计思想
  • NDK模块开发
  • 微信小程序
  • Hybrid 开发与Flutter

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结:

Android开发七大模块核心知识笔记

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值