帮你解锁 Android 性能优化五大误区和两大疑点!

news2024/11/8 18:08:55

近年来,社区充斥着关于 Android 性能优化的各种误区,本文本着误区终结者的精神,使用具体的性能检测工具,结合真实案例仔细分析这些情况,并对比它们的测试结果,也会聚焦 Android 开发者平时在编码过程的实际场景,用实际数据告诉你在实际编码之前请,一定要进行必要的性能检测

误区 1:Kotlin 比 Java 更消耗性能

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<ToyClass> 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<A> pool[] = new Pool<>[50];

void foo() {
   A a = pool.acquire();
   …
   pool.release(a);
}

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

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

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

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

image

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

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

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

  • 提高内存占用量
  • 使对象存活变长
  • 需要非常完善的对象池机制

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

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

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

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

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

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

疑点

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

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

如今的 APK 文件越来越大,因为大型应用通常会超出 Android 限定的方法数量,从而使用 Multidex 方案打破传统的 dex 规范。

问题是,多少方法可以称之为多?而且如果应用包含大量 dex 是否对性能产生影响?很多时候我们也并不是因为应用太大,而是为了根据功能拆分 dex 文件来方便团队开发而使用 Multidex。

为了测试多个 dex 文件对性能的影响,我们使用了计算器应用,默认情况下,它只包含单个 dex 文件,我们可以根据其程序包边界将其拆分为五个 dex 文件,来根据功能部件模拟拆分。

首先,测试启动应用的性能,结果如下:

因此,拆分 dex 文件对此处并没有影响,对于其他应用,可能会因为某些因素而产生轻微的开销:应用程序的大小以及拆分方式。但是,只要合理地分割 dex 文件并且不添加成百个 dex 文件,对启动时间的影响应该不大。

接下来是 APK 的大小和内存消耗:

如图所示,APK 大小和应用的运行时内存占用量都略有增加,这是因为将应用程序拆分为多个 dex 文件时,每个 dex 文件都会有一些符号表和缓存表中的重复数据。

但是,我们可以通过减少 dex 文件之间的依赖关系来最大限度地避免这种情况,在这个案例中,并没有将 dex 包量化,我们可以使用 R8 和 D8 之类的工具合理分析项目结构并使用最小化的依赖关系,这些工具可以自动拆分 dex 文件,并帮助我们避免常见的错误,最大程度地减少依赖关系,如创建的 dex 文件数量不会超过指定的数量,并且不会将所有启动类都放置在主文件中。但是,如果我们对 dex 文件进行自定义拆分,请确保合理分析。

疑点 2:无用代码

使用 ART 这样的即时编译器的好处之一就是可以在运行时分析代码,并对其进行优化。有一种说法是,如果解释器 / JIT 系统没有对代码进行概要分析,就可能不会执行该代码。为了验证这一理论,我们检查了 Google 应用生成的 ART 配置文件,发现许多代码并没有被 JIT 做概要分析,这就表明许多代码实际上从未在设备上执行过。

有几种类型的代码可能无法剖析:

  • 错误处理代码,希望它不会执行太多。
  • 兼容性代码,并非在所有设备上都执行的代码,尤其是 Android 5 以上版本的设备。
  • 不常用功能的代码。

但是,从结果分布来看,应用程序中还是会存在很多不必要的代码。R8 可以帮助我们快速,简便,免费地删除不必要的代码,来缩小这部分的开销。如果不这么做,我们也可以将应用打包成 Android App Bundle,这种格式只会使用特定设备所需的代码和资源来运行应用。

总结

本文,我们分析了 Android 性能优化的五个误区,但某些情况下数据的结果还并不清晰,我们需要做的就是在优化和修改代码之前尽量做好性能测试。

目前,已经有很多工具可以帮助我们分析评估如何优化应用了,如 Android Studio 中的 profilers,它也提供了电池和网络的监测功能。也可以用一些工具做更深入的探究,如 Perfetto 和 Systrace,这些工具会提供更加详细的功能,例如在应用启动或执行过程中发生的具体情况。

Jetpack Benchmark 摒弃了监测和基准测试的所有复杂操作,官方强烈建议我们在持续集成系统中使用它来跟踪性能,并查看应用在添加功能的行为,最后需要注意的一点是,不要在 debug 模式下分析应用性能。

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的学习路线以及核心笔记(还该底层逻辑):https://qr18.cn/FVlo89 大家可以进行参考学习:

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

《Android 性能监控框架》:https://qr18.cn/FVlo89

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/671928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【CV 向】OpenCV 图形绘制指南

文章目录 引言1. 创建画布2. 绘制线段3. 绘制矩形4. 绘制圆5. 绘制椭圆6. 绘制多边形7. 绘制字体结论 引言 Python OpenCV 是一个功能强大的计算机视觉库&#xff0c;除了图像处理和计算机视觉任务外&#xff0c;它还提供了丰富的功能来绘制各种图形。无论是在计算机视觉应用中…

基于ChatGPT的端到端语音聊天机器人项目实战(三)

企业级ChatGPT开发入门实战 第1课 基于ChatGPT的端到端语音聊天机器人项目实战 Gavin老师:NLP_Matrix_Space 1.4 使用FastAPI构建语音聊天机器人后端实战 在后端代码(backend)中调用了OpenAI API及其他的服务,如图1-10所示。 图1- 10 后端代码调用OpenAI API openai_requ…

一条SQL语句的前世今生

文章目录 MySQL 基础架构分析语句分析查询语句更新语句 总结 本篇文章会分析下一个 SQL 语句在 MySQL 中的执行流程&#xff0c;包括 SQL 的查询在 MySQL 内部会怎么流转&#xff0c;SQL 语句的更新是怎么完成的。 MySQL 基础架构分析 下图是 MySQL 的一个简要架构图&#xff…

Move Dev Meetup杭州线下交流会圆满结束

北京时间6月17日下午2点到6点&#xff0c;由MoveFuns DAO 、MoveBit、ComingChat、杭州时戳微蓝信息咨询有限公司和WEB3CN联合举办的Move开发者线下交流会圆满结束。本次交流会吸引了110余名Move开发者报名&#xff0c;其中超过60位参会者成功来到现场参与了线下交流。 第一位分…

Python在不同领域中的应用

Python 是一种功能强大且易于使用的编程语言&#xff0c;因此在各个领域都有广泛的应用。以下是 Python 在不同领域中的应用&#xff1a; 数据科学&#xff1a;Python 是数据科学家和机器学习专家的首选工具之一。它有成熟的数据分析库和工具包&#xff0c;如 Pandas、NumPy、S…

逻辑回归模型的简介及python代码实现

文章目录 1 逻辑回归简介1.1 回归步骤1.2 逻辑回归与多重线性回归 2 逻辑回归模型算法原理2.1 逻辑回归模型的数学原理2.2 逻辑回归模型的代码实现2.3 逻辑回归模型的深入理解2.4 多分类逻辑回归模型演示 总结 1 逻辑回归简介 逻辑回归是分类当中极为常用的手段&#xff0c;它…

(六)关于Linux中服务器磁盘爆满问题的几个解决方案

文章目录 一、现象二、现象产生的原因三、问题的定位过程1.检查业务文件存放位置是否合理2.检查服务器系统盘空间是否分配过小3.对服务器磁盘占用情况进行分析4.若以上方案都解决不了内存占用过高问题&#xff0c;则考虑是否是服务器中存在其他容器产生了一些文件&#xff0c;比…

【使用Hystrix实现服务容错和熔断】—— 每天一点小知识

&#x1f4a7; 使用 H y s t r i x 实现服务容错和熔断 \color{#FF1493}{使用Hystrix实现服务容错和熔断} 使用Hystrix实现服务容错和熔断&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390…

进程、线程、协程?go中的协程原理详解 - 协程是什么?为什么要有协程?

1. 为什么要有协程&#xff1f; 我们使用工厂来表示计算机&#xff0c;而内存空间就相当于工厂的地皮&#xff0c;那么我们可以认为进程就是工厂中的厂房&#xff0c;它占据了工厂的地皮。&#xff08;进程是分配资源的最小单位&#xff09; 线程是什么呢&#xff1f;我们继续…

电商运营个人简历范文

电商运营个人简历篇1 个人信息 某某x 性 别&#xff1a; 男 婚姻状况&#xff1a; 已婚 出生年月&#xff1a; 1981年1月 民 族&#xff1a; 汉族 身 高&#xff1a; 175cm 最高学历&#xff1a; 大专 体 重&#xff1a; 70公斤 计算机能力&#xff1a; 高级程序员 技术职称&am…

MySQL的IF(exp1, exp2, exp3)、IFNULL(exp1, exp2)、ISNULL(exp)函数的用法

本章主要是讲解一下mysql的常用方法if()和ifnull()的使用 1、ISNULL(exp) 该函数的作用是判断表达式exp是否为null&#xff0c;如果是null&#xff0c;则返回1&#xff0c;否则返回0 案例代码&#xff1a;以上两条语句的查询结果为分别为1和0 select ISNULL(null) from dual…

管理类联考——逻辑——技巧篇——形式逻辑——考官出题套路

文章目录 考官出题套路一&#xff1a;代入逻辑推命题真假1. 考官出题套路特征&#xff1a;2. 核心考点&#xff1a; 考官出题套路二&#xff1a;判断逻辑真假1. 考点概述&#xff1a;2. 快速秒杀选项技巧&#xff1a; 考官出题套路三&#xff1a;推矛盾和“两难推理”1. 考点概…

一句话木马(绝对入门,实战环境安装+测试)主要是记录我的第一次一句话木马过程,复现40分钟左右。

首先介绍一下&#xff0c;搭建在本地的靶场&#xff0c;可以做渗透和注入的测试&#xff0c;真是好东西。 安装教程我推荐一篇博客&#xff0c;真的写的很好&#xff0c;我全程坐下来非常流畅非常流畅&#xff0c;csdn少见的高质量博客 DVWA下载安装_PisaYu的博客-CSDN博客 膜…

MFC第五天 Unicode软件开发 MFC框架构成与封装类原理

文章目录 Unicode软件开发以Unicode为字符集的记事本软件开发 MFC框架构成与封装类原理示例代码如下&#xff1a; Unicode软件开发 Unicode软件开发时需要遵循以下规则&#xff1a;使用中可尽量使用自适应版本。 Unicode软件开发&#xff1a; a)微软的软件工程现在默认使用Uni…

【MYSQL篇】mysql中相关锁和MVCC详解

文章目录 前言MVCC1、第一个事务2、第二个事务3、第三个事务4、第四个事务5、第五个事务 InnoDB 常见的几种锁机制共享锁排它锁意向锁记录锁间隙锁临键锁 小结 前言 数据库的锁是在多线程高并发的情况下用来保证数据稳定性和一致性的一种机制。MySQL 根据底层存储引擎的不同&a…

OpenGL

需要继承的两个类 #include <QOpenGLWidget> #include <QOpenGLFunctions_3_3_Core> class OpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions_3_3_Core { Q_OBJECT public: explicit OpenGLWidget(QWidget *parent nullptr); virtual vo…

如何看待调查称半数年轻人存款不足10万?

文章目录 一、目前的存款在哪一个区间&#xff1f;你觉得存款难吗&#xff1f;2.1 自己的状态2.2 对理财的看法和态度 二、谈谈我为存款做出过哪些努力&#xff1f;三、除了个人因素外&#xff0c;有哪些因素影响到了年轻人的存款能力和存款意愿&#xff1f;四、要攒够多少存款…

【数据库原理与实践】CS系的实验期末考题(20222023)

2022&#xff1a; 学校管理数据库涉及四个关联表结构&#xff1a; 学生表 Student(Sno,Sname,Sdate,Ssex) &#xff0c;其中Sno学生编号&#xff0c;Sname学生姓名&#xff0c;Sdate出生年月&#xff0c;Ssex学生性别 。 课程表 Course(Cno,Cname,Tno) &#xff0c;其中Cno课…

(写自己语言的练手级应用)JSON(JavaScript Object Notation) 产生式(BNF)

写自己的开发语言时&#xff0c;很多人都会拿JSON当第一个练习对象 开源net json FJSON 解析工具https://dbrwe.blog.csdn.net/article/details/107611540?spm1001.2014.3001.5502 <json> :: <object> | <array> <object> :: "{" [ <me…

【OJ比赛日历】快周末了,不来一场比赛吗? #06.23-06.29 #13场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-06-23&#xff08;周五&#xff09; #5场比赛2023-06-24…