1.1 为何写这本书
Android 性能优化是非常重要的一个方向,他的重要性体现在能帮助程序带来更大的价值,以及帮助 Android 开发者增强职业竞争力这两个方向。
在程序的价值提升上,性能优化可以减少程序的稳定性,提升运行速度和流畅性等用户体验,从而可以提高用户满意度,增加用户的留存率,促进业务的增长。对于中大型公司来说,每款程序都会有专门的性能品质团队来负责优化程序的性能,足以见得性能优化对提升程序价值的重要性。
对于 Android 开发者来说,擅长性能优化能增强我们的竞争力,并给我们带来更好职场的表现。在平时的工作中,业务需求很难体现出个人的技术实力差距,但是如果我们在业务需求开发之外,凭借自己性能优化的能力给业务带来了更多的价值,那么自然能获得更多的认可。在面试中,性能优化也是必定会考到的知识,它是开发者技术实力的体现,对于擅长性能优化的开发者来说,在面试中也能脱颖而出,提升面试的成功率。
性能优化的重要性不言而喻,因此网络也有很多性能优化的文章,市面上也有不少性能优化的书籍,但是这些文章和书籍大都仅仅是讲解一个个具体的性能优化案例,当我们看完这些案例后,也仅仅只是知道了在同样的场景下,有哪些做法。但是实际开发中,我们所面对的场景是多样且复杂的,包括多样的业务类型,各种性能的设备。因此,很多时候,我们可能因为无法在网上找到同样场景的优化方案,而不知道从哪儿开始下手,也有可能参考别人的方案进行优化后,却发现效果不佳,也有可能我们兴致勃勃提出了优化方案,却被他人质疑反驳而无法实施落地。
想要做好性能优化,如果仅仅只是通过博客等方式,零碎的学习一些别人的优化方案,是完全不够的,我们需要扎实且能够成体系的掌握来自硬件层、系统层、应用层等多个层面的知识点。所以笔者写作此书,不仅仅是局限于讲解一些具体的性能优化的实战案例,而是会深入讲解做性能优化所需要具备的知识体系,并力求基于这些知识体系和经验案例构建出性能优化的方法论,帮助读者做到融会贯通,举一反三,真正的掌握性能优化。
1.2 如何才能做好性能优化
在讲解本书的主要内容之前,笔者想先讲解一下如何才能做好性能优化,本书后续的内容,都是围绕着如何做好性能优化而展开的,因此我们需要对做好性能优化有一个总体的认知,后续才能更加有方向性和目的性的去进行学习。
1.2.1 性能优化的本质
做任何事情,如果不了解其本质,我们就很难制定出真正有效的方案,所以了解性能优化的本质是一件很重要的事情。那么性能优化的本质是什么呢?笔者认为性能优化的本质是"通过充分且合理的使用设备的硬件资源,来让程序的体验更好,并取得收益"。
1)充分且合理的使用硬件资源
我们都知道性能优化的目的是为了更好的程序体验,但是要怎样做才能获得更好的体验呢?我们会不假思索的想出很多的方案,比如使用预加载、多线程、缓存等等。但这些方案真的能提升程序的体验么?答案是不确定的,有可能有正向效果,但是有可能没效果,甚至还会带来负向效果。为什么会这样呢?我们要基于硬件资源的角度去考虑这个问题。
如果程序当前的 CPU 的使用率已经很高了,我们再使用预加载任务和多线程并发执行任务无疑是雪上添霜的操作,不会带来任何优化,还会导致劣化,只有 CPU 的使用率不充分的时候,我们使用这些优化方案,才能带来较好的优化效果;如果当前的内存占用已经很多了,我们再使用更多的缓存只会导致系统频繁的触发 GC,甚至带来 OOM 崩溃。很多时候,在进行性能优化时效果不佳的原因,是因为在制定优化方案时,并没有基于性能优化的本质去思考,只有当我们基于本质:充分且合理的使用硬件资源,去制定优化方案时,才能肯定的说:优化是有效的。
硬件资源包括 CPU,内存,磁盘,电量等。因为不同的设备的硬件资源是不尽相同,所以当我们基于本质来进行性能优化时,提出的方案就和前面的方案就不一样了。
- CPU:基于如何合理且充分的使用 CPU 来进行性能优化时,我们便需要针对不同性能的 CPU 采取不同的优化方案。对于中、低端机型来说,由于 CPU 性能差,所以 CPU 使用率很容易就过载了,我们此时需要考虑通过降低 CPU 的消耗来合理的使用 CPU 资源,常见的方案如降低线程数量,减少和关闭预加载任务等,并将更多的 CPU 资源给主线程或者核心场景使用。而对于高端机来说,CPU 性能好,我们的优化方案往往是如何将 CPU 资源更充分的发挥出来,所以此时的优化方案和中低端机刚好相反,可以使用更多的线程来并发执行任务、使用更多预加载任务来提升体验。
- 内存:基于如何合理且充分的使用内存资源来进行性能优化时,我们便需要针对内存资源大小设置不一样的缓存大小,对于内存大设备,可以缓存更多的数据,对于内存小的设备,则要减少缓存的数据,缓存的数据大小要始终控制在合理的范围内,这样才能保证即能充分的通过使用内存资源来提高程序的性能表现,但又不会因为内存使用的太多而导致 OOM(Out Of Memory,内存溢出)等稳定性问题。
通过上面的两个例子,我们是不是发现:基于本质来制定的优化方案,是不是更加的有效果了呢?
2)取得收益
制定方案并进行优化,只是性能优化的一部分工作。还有一件同样重要的事情需要我们做,就是如何取得收益?这需要我们做两件事情,一是如何制定指标,二是如何采集指标。
- 制定指标
既然要取得收益,第一步就是制定指标。我们通常会选取通用的性能指标来度量我们优化的效果和收益。比如度量速度的指标:启动速度、页面打开速度等;度量内存的指标:PSS(程序在系统中占用的实际物理内存量),Java 内存占用,Native 内存占用等;度量稳定性的指标:Crash 率,OOM 率等。但是在选取这些指标时,我们还需要再进一步思考这些指标是否真的能够体现收益。
比如进行内存优化,我们很可能会选取了 PSS 这个指标来度量内存优化的收益,经过一系列的优化,我们成功的让 PSS 降了 100 MB,此时我们很可能会认为自己的优化带来了不错的收益。但是实际上 PSS 少 100M 后对程序的表现到底是更好还是更坏,是不能确定的。可能因为我们减少了缓存的数据,所以 PSS 少了 100M,这个时候程序某些页面的打开速度变慢了,这个时候性能体验是变差的;也可能因为减少的这 100 M 的 PSS,让程序的 OOM 率大幅降低了,这个时候性能体验是变好的。
但是如果我们将内存优化的度量指标由 PSS 值改为 OOM 率,GC(Garbage collector,垃圾回收) 次数等指标时,就能明确的度量程序体验的好坏,这些指标的优化就是我们做性能优化的收益。所以我们在制定性能优化的指标时,要真正选取那些能真正体现程序的用户体验和收益的指标。
- 采集指标
当我们制定了指标,第二步就是要采集指标了。采集指标时需要做到准确性、一致性、并能要减少采集对性能损耗。
准确性我们都能理解,就是采集的性能指标是要准确的。那么什么是一致性呢?有很多指标是比较主观的指标,比如页面打开速度,这个指标就涉及到页面打开的结束点的选取,什么情况下算页面打开完成?这不是一个标准的答案,我们可以认为某些关键组件渲染完成算结束点,可以认为大部分 UI 都展示出来了才算结束点,也可以简单的认为第一帧渲染完成了就算结束点。我们需要结合自身程序的特点,选择一个大家都能认可的点作为结束点,并且后续要始终以这个点当做结束点,不能过两个版本就换一下。在性能优化中,指标是一件很重要的事,所以它的基准需要始终保持一致,如果标准总是变化,那么基准也就变得没有意义了,我们就无法明确的对比不同的版本直接到底是优化了还是劣化了。
在指标采集过程中,我们也要关注采集本身对性能的影响。不少指标都是需要进行 IO 操作的,比如内存相关的数据,CPU 相关的数据,读写 IO 本身就是较消耗资源的操作,我们就需要控制好频率,尽量减少对性能的损耗。
1.2.2 性能优化的维度
了解了性能优化的本质,我们已经可以设计出有效的优化方案了,但并不代表我们就能设计出更体系化的优化方案,毕竟性能优化是一个庞大的主题,我们此时可能有一些零碎的想法,但是我们的方案无法成体系和全面,而只有体系且全面的优化方案,才能将优化做到最好,对程序的体验有最大的提升。如图所示,想要打造体系化的优化方案,我们还需要基于应用层,系统层,硬件层这三个维度来进行。
- 应用层
应用层主要指的是我们所开发的程序,针对应用层的优化是开发者开展的最多的优化。在做优化时,我们通常会了解业务逻辑,然后通过多线程,预加载,缓存等手段进行优化,但仅仅是这样,优化效果是很有限的。我们需要基于性能优化的本质来进行思考,也就是如何充分且合理的使用硬件资源。因此,针对应用层的优化通常有两个方向,一是如何让业务更加充分的使用 CPU,缓存等硬件资源提升体验;二是如何管控业务方合理的使用这些资源。
所以想要有更好的效果,我们不仅需要了解和熟悉各个业务逻辑,也要清楚的知道业务对资源的消耗,如每个业务消耗了多少内存资源,每个业务消耗了多少 CPU 资源,每个业务使用了多少个线程等,当我们把这些都摸透之后,才能开始进行优化,对于大型应用,业务多,资源消耗往往都是过载的,我们的优化方案主要是如何分配和管理业务对资源的使用,所以我们可以通过启动框架,预加载框架,降级框架等框架来约束和管控业务方对资源的使用,对于中小型应用,业务并不多,资源消耗往往都是不足的,所以我们可以通过更多的预加载任务,多线程等方案来提升对资源的使用。
- 系统层
系统层指的是 Android 系统和 Linux 系统,针对系统层的优化要比应用层的优化难很多。因为想要针对系统层进行优化,那前提就是要熟悉系统知识,而熟悉 Android 系统和 linux 系统的原理和特性,比熟悉我们自己的程序的业务逻辑要复杂太多了。其次,由于系统层的逻辑我们是无法直接控制的,所以也经常需要一些复杂的技术,比如 Native Hook 技术来修改一些系统的逻辑,来达到优化的目的。
系统层的优化通常都是以减少系统自身导致的资源消耗为主,比如系统在进行 GC 时,频繁切换线程,频繁缺页中断和换页时,都会消耗大量的 CPU 资源,所以我们的优化方案就是如何减少这些系统逻辑的资源消耗,或者降低这些系统逻辑执行时对应用的影响。
虽然针对系统层的优化会比应用层的优化复杂很多,但幸运的是针对系统层的优化一般都是通用的,所以我们完全可以借鉴和复用一些方案方案,比如在启动时进行 GC 抑制,合理的线程池设计。
- 硬件层
针对硬件层的优化主要了解硬件的特性,然后寻找优化点。大部分硬件层的优化方案都是针对 CPU 和缓存这两个硬件特性来展开的。
比如 CPU 由大小核组成,大核运行频率高,小核运行频率低,我们可以将主线程运行在大核上;如果厂商有提供相应的超频的 API 接口,我们也可以在核心场景将 CPU 提频以提高性能。
缓存的架构由多级的高速缓存,内存,磁盘组成,针对缓存这一硬件进行优化时,我们则可以思考如何提升高速缓存的命中率,内存缓存的命中率。
1.2.3 性能优化的难点
到这里我们已经知道要做好性能优化的本质和维度了,但是此时我们也仅仅只是知道前方的路在哪儿,想要到达终点,还需要不断的前行,并克服路上遇到的重重障碍。这条路上的障碍,也就是性能优化的难点,主要体现在这四个方面:知识储备,思考的角度,思考的方式,完整的优化闭环。
- 知识储备
想要做好性能优化,第一个难点是需要掌握完备且体系的知识体系,正如我们在前面了解了性能优化要从应用层,系统层和硬件层三个维度进行,这就意味着我们也要扎实的掌握这三个层次的知识点。
- 应用层
我们越想要在针对应用层进行性能优化中取得好的效果,就越需要对自己所开发的应用有更多的了解。我们需要知道自己所负责的 APP 有哪些线程,都是干嘛的,都是哪些业务使用的,这些线程的 CPU 消耗情况;内存占用多少,都是哪些业务占用的,缓存命中率多少;启动过程中、核心页面打开过程中,都做了哪些事情,IO 阻塞耗时多久,逻辑耗时多久,CPU 使用率是多少......如何我们能像 X 光机器一样,能对应用有全面和透彻的了解,进行性能优化时,自然能庖丁解牛一般顺畅。
- 系统层
系统层的知识点相比于应用层的知识点,就更加庞大和复杂了。如 Linux 的知识包括进行管理和调度、内存管理、虚拟内存、锁、IPC 通信等;Android 系统的知识包括虚拟机、核心服务如 AMS(ActivityManagerService),WMS(WindowManagerService) 等、渲染、一些核心流程,如启动,打开 Activity,安装等等。
针对系统层的性能优化,一定是建立在我们对系统的机制和流程的掌握上的。如果我们不了解 Linux 系统的进程调度机制,就没法充分利用进程优先来帮助我们提升性能;如果我们不熟悉 Android 的虚拟机,那么围绕这虚拟机一些相关的优化,比如 OOM 优化,或者是 GC 优化等都无法很好的开展和落地。
- 硬件层
对于硬件层来说,我们需要熟悉 CPU、缓存等硬件的特性。如果我们能知道 CPU 由几个核组成,哪些是大核,哪些是小核这些知识点,我们就自然会想到是不是可以通过将核心线程绑定在大核运行的方式,来提升性能的优化方案;如果我们能了解存储结构中寄存器,高速缓存,主存的设计,我们也就会自然的思考,是否能针对这一特性来提升性能,比如将核心数据尽量放在高速缓存中等方案,来提升性能。
- 其他
除了上面提到的三个层面的知识,如果想要在性能优化上更加的精进,我们还需要掌握更多的知识,如汇编,编译器、编程语言、逆向等知识。比如用 c++ 写代码比用 Java 写代码运行更快,我们可以通过将一些业务替换成 C++ 来提高性能;比如通过优化编译器的内联,无用代码消除等方案来减少包体积;比如通过逆向的技术,优化系统的逻辑,让程序表现的更好。
可以看到,想要精通性能优化,需要庞大的知识储备,所以性能优化是非常能体现出开发者的技术深度和广度的。不管是面试中,还是工作中,如果我们能擅长性能优化,也一定能给我们加分不少。
- 思考的角度
在处理一件复杂和庞大的事情的时候,我们首先就做好分层和分类,不同的层和类别,都代表着不一样的角度。比如在客户端程序架构时,通常都是用分层架构,不同的层的逻辑和职责都是不一样的;在体系的进行性能优化时,我们可以从应用层,系统层,硬件层分别进行优化,每一层的优化方案也不尽相同。
除了从事情本身所具有不同角度来进行思考,我们还能通过跳出事物自身之外,来获取更多的灵感。比如跳出本设备之外来思考,是否可以用其他的设备帮助我们加速启动。Google Play(谷歌的应用商店)就有类似的优化,Google Play 会上传一些其他机器已经编译好的机器码,然后相同的设备下载这个应用时,也会带着这些编译好的机器码一起下载;还有很常用的服务端渲染技术,也是让服务端线渲染好界面,然后直接暂时静态模块来提升页面打开速度;又或者站在用户的角度去思考,想一想到底什么样的优化对用户感知上是有好处的,比如有时候我们再做启动和页面打开速度优化,给用户一个假的静态页面让用户感知已经打开了,然后再去绑定真实的数据。
做性能优化时,考虑的角度越多越全面,那么我们的优化方案也就越多,越有效果。
- 思考的方式
在处理复杂事情时,我们要将事情划分成不同的角度,有更多的思考角度,也会有更多的方案。但是我们要怎样去划分这些角度呢?我们就是想不出这些角度怎么办呢?其实这些都和我们思考问题的方式有关,通过不同的思考方式,我们能获得不一样的思考角度,最常见的就是自上而下和自下而上两种思考方式。
- 自上而下
自上而下的思考方式是一种从整体到局部的逐步分解,并各个击破的思维方式。在大型应用的性能优化中,自上而下就是一种很常见的方式,大型应用的业务非常多,不同的业务的团队都不一样,所以我们在做性能优化时,需要从整体来考虑如何管控和分配业务对资源的消耗及使用,我们可以设计一些全局的框架来管控业务使用资源,如预加载框架,降级框架;设计一些全局的监控,来度量业务对资源的使用。当我们有了整体的管控和监控后,就可以接着进入到局部的视角,对资源消耗大的业务,追个进行优化。
- 自下而上
自下而上的思考方式刚好相反,是从细节或者底层原理,逐步向上构建整体的解决方案的方式。比如当我们做速度的优化时,直接从影响速度的 CPU ,缓存等进行思考,如何提高 CPU 的利用率和缓存的命中率,并从硬件层,系统层,应用层自下而上一一的进行思考,构建出完整的优化方案。
不同的思考方式最终会让我们设计出不同的优化方案,但并没有说哪种思考方式就更胜一筹,在面对复杂问题时,我们需要尝试使用不同的思考方式来思考并解决这个问题。
- 优化的流程
在实际的性能优化过程中,如何进行优化只是其中的一部分,我们还需要做更多的事情,如图 1-2 所示,包括监控,优化,数据收益获取,防劣化,这些部分形成一个闭环才是完整的优化的流程,在性能优化时我们需要把各个环节都考虑到并且能做好。
-
监控:即监控应用运行过程中各项性能的指标,想要做好监控,除了需要尽量减少监控逻辑对性能损耗,还需要尽量做到对归因的监控。比如内存监控,除了监控应用的内存指标数据外,还可以还要能监控到各个业务的内存使用占比,大集合,大图片,大对象等归因项,这样我们通过监控就能直接定位到问题。完整和优秀的监控方案能让我们更高效的发现和解决异常。
-
优化:会有很多开发者认为性能优化便仅仅是优化,但是实际上优化只是性能优化中的一个环节,并不是性能优化的全部,我们之所以会有这样的误解,往往也是因为我们对性能优化不具备体系化的了解和认知。
-
数据收益获取:这一环节也不是简单的观察指标的变化就够了,我们要学会如何做 A/B 测试,学会关注核心价值的指标。比如做内存优化,并不能一味的追求降低应用内存的 PSS 占用,内存占用的多少,这些指标并不代表真实的用户体验,所以在做内存优化时,我们最好结合内存触顶率,崩溃率,用户留存等等这种体验核心价值的指标,来获取内存优化的收益和价值。
-
防劣化:防劣化也是有很多事情可以做的,如建立完善的线下性能测试,线上监控的报警等。同样拿内存优化举例,我们可以在线下每天通过 Monkey 跑内存泄露并提前治理,这就是防劣化工作。
1.3 本书的特色及主要内容
在前面的一节中,笔者讲解了做好性能优化的本质、维度以及会遇到的难点,而本书的特色,便是围绕着帮读者扫清性能优化过程中的难点,并基于性能优化本质,从多个维度来进行优化实战来展开的。因此本书会体系化的讲解各个性能优化方向所需要的知识体系,这个知识体系涵盖了从硬件层,操作系统层到应用层,在这些知识储备的基础下,笔者也会构建出该性能方向在进行优化时的方法论,这些方法论是我们能够具备多样的思考角度,完善的思考的方式,以及完整的优化流程的深刻体现。最后笔者会通过来自应用层,系统层,硬件层等多个维度在监控,优化,防劣化等环节的具体优化案列,去强化我们对知识体系的巩固以及对方法论的掌握。
本书的内容包含了内存优化、速度和流畅性优化,稳定性优化,包体积优化,以及电量优化、磁盘占用、流量优化等全面的性能优化内容。这些内容大都分为了原理篇和实战篇,原理篇主要讲解基础知识,实战篇会基于基础知识,进一步的去讲解优化案例以及这些案例中用到的技术和原理。
本书中的涉及到 Android 系统的知识点,主要都基于 Android14 来进行讲解,这样可以确保读者了解的知识点都是较新的,但是在做性能优化时,基于兼容性考虑,我们往往要基于各个系统版本来进行,所以本书也会涉及到除 Android 14 以外的其他系统版本的源码讲解,对于本书中通过示例程序讲解的案例,大都也会提供源码,源码详情可以参考下面链接。
本书的内容是个人从过去的工作经验以及业界优秀经验的思考和总结,但技术永无止境,个人水平也相对是有限的,文中难免会有错误,也望读者能够包涵和赐教。
1.4 本书读者对象
本书内容中既有基础的知识体系,也有深入的实战案例,涵盖了从 Java 层到 Native 层,因此本书的内容适合各个阶段的读者阅读,包括以下读者。
- 有丰富的 Android 开发经验,对 Android 的技术想进一步突破开发者
- 有一定工作经验,对性能优化有一定的认识,但想更深入学习性能优化的 Android 开发者
- 刚刚参加工作,想要对 Android 的基础知识进行体系化的学习的 Android 新人
1.5 如何阅读本书
不同的读者可以根据自身领域和水平选读不同的章节。
对于刚参加工作的新人,建议通读本书的知识原理部分,这些知识点能帮助 Android 新人建立体系化的知识体系,有了这些知识,便可以更快速的融入到日常的工作和开发中,至于实战的篇章,可以在之后的开发工作中,慢慢的阅读和实践。
对于有一定工作经验或者本身就是从事性能优化工作的读者,建议通读全书,全方位的了解 Android 各个方向的性能优化以及这些优化所覆盖的知识点。本书中有不少优化案例用到了比较复杂的技术,如 Native Hook 技术,字节码插桩等,这些技术都是Android进阶要掌握的知识点,可以帮助读者在 Android 领域更上一层楼。
对于有丰富经验的Android开发者来说,可以选读本书中的实战项目的章节,本书中不少优化案例都是比较新颖的,通过这些案例,读者也可以收获一些新的思路,在之后的工作中激发更多的灵感。