轻巧的Android Go系统对预安装的应用程序的要求有所提高-所使用的大小和内存。 我们面临着满足这些要求的挑战。 我们进行了许多优化,并决定认真更改图形外壳程序Yandex.Luncher的体系结构。 移动应用程序开发团队的负责人Alexander Starchenko分享了这一经验。
-我叫Alexander,来自圣彼得堡,来自开发Yandex.Loncher和Yandex.Phone的团队。 今天,我将告诉您如何在Launcher中优化内存。 首先,我将简要说明什么是启动器。 接下来,我们讨论需要优化内存的原因。 之后,我们将考虑如何正确地测量内存及其组成。 然后让我们继续练习。 我将讨论如何在Launcher中优化内存以及如何彻底解决该问题。 最后,我将讨论如何监控内存的使用以及如何控制内存的使用。

“启动器”或“启动器”-不是那么重要。 Yandex上的我们曾经称他为Launcher,在报告中,我将使用“ Launcher”一词。

另一个要点是:Launcher通过预设广泛分布,也就是说,当您购买新手机时,Yandex.Loncher通常可以成为手机上唯一的应用程序管理器,家庭桌面管理器。
现在,出于为什么我们需要优化内存的原因。 我将从我们的理由开始。 简而言之,这就是Android Go。 现在更长。 在2017年底,谷歌推出了Android Oreo及其特殊版本Android Oreo Go版本。 有什么特别之处? 此版本专为低端,价格低至1 GB的廉价手机而设计。 她还有什么特别之处? 对于此版本Android上预装的应用程序,Google提出了其他要求。 特别是-消耗RAM的要求。 粗略地说,启动后一段时间,应用程序的内存将被删除,启动器的大小不应超过30-50兆字节,具体取决于手机屏幕的大小。 最小的30个,大屏幕的50个。
还应该注意的是,Google继续在这一领域进行开发,并且已经有了Android Pie Go版本。
还有什么其他原因可以优化内存使用率? 首先,您的应用程序不太可能下载。 其次,它将运行得更快,因为它不太可能解决垃圾收集器,并且内存分配的频率会降低。 不会创建额外的对象,不会放大额外的视图,等等。间接地,根据我们的经验判断,这将导致应用程序apk大小减小。 所有这一切将为您提供更多的安装和更好的Google Play评分。
好的,现在我们知道为什么要优化内存了。 让我们看看用什么方法来衡量它以及它的组成。

可能很多人都看过这张照片。 这是从Android Studio个人资料的内存屏幕截图。 该工具在developer.android.com上有足够详细的描述。 也许你们中的许多人已经使用过它们。 谁没有使用-试试。
这里有什么好? 它总是在手边。 在开发过程中使用起来很方便。 但是,它有一些缺点。 并非您的应用程序的所有分配都在此处可见。 例如,下载的字体在此处不可见。 另外,借助此工具,查看哪些类已加载到内存中也不方便,并且您无法在自动模式下使用此工具,也就是说,您无法基于Android Studio配置文件配置某种自动测试。

幻灯片中的链接: 第一 , 第二
自从Android在Eclipse中开发以来,已经存在以下工具,简称为Memory Analyzer,MAT。 它作为独立的应用程序提供,并且与可以从Android Studio保存的内存转储兼容。
为此,您将需要使用小型实用工具,专业的转换器。 它随附有Android Go版本,并具有多个优点。 例如,它可以构建gs根的路径。 它有助于我们准确地了解Launcher加载了哪些类以及何时加载它们。 我们无法使用Android Studio Profiler执行此操作。

下一个工具是dumpsys实用程序,特别是dumpsys meminfo。 在这里,您将看到此命令输出的一部分。 它提供了内存消耗的相当高级的知识。 但是,它具有某些优点。 在自动模式下使用方便。 您可以轻松配置仅调用此命令的测试。 它还会立即显示所有进程的内存。 并显示所有位置。 据我们所知,Google在测试过程中使用了该工具的内存值。
让我们使用输出示例简要描述应用程序内存的组成。 第一个是Java Heap,即Java和Kotlin代码的所有位置。 通常,此部分足够大。 接下来是本机堆。 这是来自本机代码的分配。 即使您没有在应用程序中显式使用本机代码,此处也会出现分配,因为许多Android对象(同一视图)都分配了本机内存。 下一部分是代码。 与代码有关的所有内容都在这里:字节码,字体。 如果使用许多第三方未优化的库,代码也可能会很大。 以下是Java和本机代码的软件堆栈,通常很小。 接下来是图形内存。 这包括表面,纹理,即在CPU和GPU之间散布的内存用于渲染。 接下来是“私人其他”部分。 这包括不属于上述各部分的所有内容,以及系统无法分散在其上的所有内容。 通常,这些是某种本地分配。 接下来是“系统”部分,这是归因于您的应用程序的系统内存的一部分。
最后,我们有TOTAL,这是列出的所有部分的总和。 我们要减少它。

关于内存测量,还有哪些重要的知识要知道? 首先,我们的应用程序无法完全控制所有分配。 也就是说,作为开发人员,我们对要下载的代码没有完全控制。
以下。 应用程序内存可能会跳很多。 在测量过程中,您会发现读数之间存在很大差异。 这可能是由于花费的时间以及各种情况造成的。 在这方面,当我们优化内存,对其进行分析时,在相同条件下执行此操作非常重要。 理想情况下,在同一设备上。 如果您可以选择致电垃圾收集器,那就更好了。
太好了 我们知道为什么需要优化内存,如何正确测量内存以及它的组成。 让我们开始练习,我将告诉您如何在Launcher中优化内存。

一开始就是这种情况。 我们有三个进程,总共分配了大约120兆字节。 这几乎是我们希望收到的四倍。

关于主进程的分配,有很大一部分Java Heap,很多图形,大代码和相当大的Native Heap。

首先,我们非常天真地解决了该问题,并决定从某些资源中遵循Google的一些建议,尝试快速解决该问题。 我们提请注意在编译过程中生成的综合方法。 我们有2000多个。 在几个小时内,我们都删除了它们,删除了内存。

他们在代码部分中获得了大约一或两个兆字节的增益。 太好了
接下来,我们将注意力转向枚举。 如您所知,枚举是一类。 就像Google最终承认的那样,枚举并不是非常有效的存储。 我们将所有枚举都转换为InDef和StringDef。 在这里,您可以反对我ProgArt将在这里提供帮助。 但是实际上,ProgArt不会用原始类型替换所有枚举。 最好自己动手做。 顺便说一下,我们有90多个枚举,很多。

这种优化已经花费了几天的时间,因为大多数操作都是手动完成的,我们在Java堆部分中赢得了大约三到六兆字节。
接下来,我们提请注意集合。 我们使用了相当标准的Java集合,例如HashMap。 我们有150多个,所有这些都是在启动器启动时创建的。 我们将它们替换为SparseArray,SimpleArrayMap和ArrayMap,并开始创建具有预定大小的集合,以便不会分配空插槽。 也就是说,我们将集合的大小传递给构造函数。

这也带来了一定的收益,这种优化也花了我们几天的时间,其中大部分是我们手动完成的。
然后,我们采取了更具体的步骤。 我们看到我们有三个过程。 我们知道,即使Android中的一个空进程也需要大约8-10 MB的内存,这是很多的。
我的同事Arthur Vasilov告诉了有关流程的详细信息。 不久前,在Mosdroid会议上
,他发表了有关Android Go的
报告 。

经过这些优化后,我们得到了什么? 在主要测试设备上,我们观察到内存消耗在80-100兆字节左右,虽然还算不错,但还不够。 我们开始测量其他设备上的内存。 我们发现在速度更快的设备上,内存消耗要大得多。 事实证明,我们有许多不同的挂起初始化。 一段时间后,Launcher启动了一些视图,启动了一些库,等等。

我们做了什么? 首先,我们浏览了所有布局的视图。 删除了所有因可见性而消失的视图。 他们将它们放入单独的布局,并开始以编程方式对它们进行充气。 我们不需要的那些东西,我们通常会停止充气,直到用户需要它们的那一刻。 我们关注图像优化。 我们已停止加载用户目前看不到的图像。 对于Launcher,这些是应用程序完整列表中的应用程序图片图标。 在开放之前,我们不会发货。 这使我们在图形部分获得了非常好的胜利。
我们还检查了内存中的图像缓存。 事实证明,并非所有图像都是最佳的;并非所有与运行Launcher的手机屏幕相对应的图片都存储在内存中。
之后,我们开始分析代码部分,并发现我们从某个地方有很多相当繁重的类。 原来,这些主要是库类。 我们在某些库中发现了一些奇怪的东西。 其中一个库创建了HashMap,并在静态初始化程序中用足够多的对象将其阻塞。

另一个库也将音频文件加载到一个静态块中,该块占用了大约700 KB的内存。

我们不再初始化此类库,仅在用户确实需要这些功能时才开始使用它们。 所有这些优化花费了数周的时间。 我们测试了很多,检查了是否没有引入其他问题。 但是我们也取得了不错的成绩,在本机,堆,代码和Java堆部分中,大约40兆字节中有25兆字节。
但这还不够。 内存消耗仍未降至30兆字节。 似乎我们已经用尽了所有用于简单自动和安全优化的选项。
我们决定考虑彻底的解决方案。 在这里,我们看到了两个选项-创建一个单独的精简应用程序或处理Launcher架构,并切换到模块化架构,从而能够在没有其他模块的情况下制作Launcher程序集。 第一种选择是相当长且昂贵的。 创建此类应用程序很可能会为您带来功能完善的单独应用程序,需要完全支持和开发该应用程序。 另一方面,具有模块化体系结构的选件也很昂贵,很有风险,但仍然更快,因为您已经在使用著名的代码库,因此您已经拥有一组自动单元测试,集成测试和手动测试案件。
应该注意的是,无论选择哪个选项,都将不得不放弃Android Go版本中应用程序的部分功能。 这很正常。 Google在其Go应用程序中也是如此。
结果,实现了模块化体系结构后,我们相当可靠地解决了内存问题,即使在具有小屏幕的设备上也开始通过测试,也就是说,我们将内存消耗降低到30 MB。

关于内存监视,关于如何控制内存使用情况。 首先,我们设置了静态分析器,当我们使用枚举,创建综合方法或使用非优化集合时,也会出现错误。
更加困难。 我们设置了自动集成测试,该测试在模拟器上运行Launcher,并在一段时间后释放了内存消耗。 如果它与以前的版本非常不同,则会触发警告和警报。 然后,我们开始研究问题,并且不发布会增加Launcher内存使用量的更改。
总结一下。 有多种工具可用于监视内存,测量内存以实现快速有效的操作。 最好全部使用它们,因为它们各有利弊。
事实证明,采用模块化体系结构的激进解决方案对我们而言更加可靠,高效。 很遗憾我们没有立即采取措施。 但是我在报告开始时所说的步骤并没有白费。 我们注意到,该应用程序的主版本开始最佳地使用内存,以更快地工作。 谢谢啦