应用启动性能 | 介绍 App Startup 库

最近我开始尝试使用 AndroidX 的应用启动 (App Startup) 库。在这个库发 布了 1.0 版本 以后,我以为是时候深刻理解一下为何须要、何时以及如何使用这个库。android

首先我注意到的是它的名字 —— 应用启动,其代表这个库的功能可能比它字面上的意义更普遍。这个库并不涉及普通的启动 (起码目前如此)。它主要是为了下降由 content provider 初始化致使的对应用启动速度的影响。git

眼下您可能和我同样历来没有考虑过第三方库都是如何被初始化的。也许是由于全部这些处理过程都在底层完成。准确地说,您在 build.gradle 文件中添加了一行代码来使一个开发库做为工程的依赖项,大功告成 (固然您还须要在工程中调用这个库的 API,要否则您为何要添加它呢?)。github

但是有不少库并非简单地封装好一堆方法以供调用,它们时常还须要首先被初始化,而每每这个初始化仍是很耗时的过程。更糟糕的是,这其中还暗藏陷阱,由于这些库经常在应用启动的时候进行加载和初始化,究其缘由是因为其内部使用了 content provider数据库

敞开您的心扉 - Content Provider

Content provider 是 Android 中在不一样应用之间共享数据的方式。举个例子,手机中的联系人是经过 content provider 来实现数据共享的,这也使得其余应用能够访问用户的联系人数据 (固然,咱们假设用户给予这些应用访问联系人数据的权限)。您也一样能够为其余应用提供访问受权,来使用您应用建立的数据。或许您的应用管理着一个 甜甜圈评分的数据库,而做为如此重要的信息,其余应用可能须要频繁地使用。app

只要一个应用经过任何一种方式声明 content provider 开启,此时就会自动建立而且启动 content provider。编辑器

使用 content provider 有一个重要但可能并不那么明显的问题,就是应用在声明 content provider 开启后,它会被自动建立并运行。并且须要注意的是,一个应用的启动并不仅是经过用户启动,其还能够是经过系统访问该应用的服务,又或者是 job scheduler 触发了应用的一个循环做业等等。全部的这些都会触发 content provider 的资源开销以及产生相应的运算做业。当有须要访问该 content provider 的时候,系统须要该应用可以处于就绪状态,因此系统会在应用启动的时候自动运行 content provider。ide

这些细节对于仅仅调用这些库的开发者都是不可见的,由于具体实现都隐藏在自动生成的代码中。您须要查看 合并后的 manifest 文件 来理解这一切是如何发生的。工具

合并 Manifest

我针对 Android 应用清单的交互操做基本上都发生在工程自生成的 Manifest.xml 文件中,我会经过编辑该文件来添加 activity、服务和权限。可是这个 manifest 文件并非最终提交到系统的那个,这个文件只是提供了关于您应用的信息,这些信息会被 "合并" 到最终的 manifest 文件中。合并后的文件包含了您的 Manifest.xml,以及编译工具挑选的其余信息,包括了您应用使用库的 manifest 文件。也正是这个合并后的 manifest 文件告诉咱们库的 content provider 究竟发生了什么。性能

让咱们来看一个具体的例子。并非全部的库都使用了 content provider (尽管这仍是很常见的),因此咱们要用一个包含 content provider 的库 -- WorkManager。为了在个人工程中使用 WorkManager,我在应用的 build.gradle 文件中添加了以下依赖:测试

// 查看最新的版本号 https://developer.android.google.cn/jetpack/androidx/releases/work
def work_version = "2.5.0"
implementation "androidx.work:work-runtime-ktx:$work_version"

在我同步以及构建了该应用以后,我测算了一下启动时间 (稍后会详细介绍) 来对比添加这一依赖先后启动时间上的差异。我注意到应用在添加依赖后,启动时间比以前多了 70ms,并且这是在尚未调用 WorkManager 任何功能的状况下,我只不过是添加了这个依赖。

我在合并后的 manifest 文件中发现了启动时间延迟的缘由,您能够在查看 Manifest.xml 文件时,经过点击 Android Studio 编辑窗口左下方的 Merged Manifest 标签来查看合并后的 manifest 文件。

编辑窗口下方的标签控制着您所看到的是您应用的 manifest 文件仍是最终合并后的 manifest 文件

编辑窗口下方的标签控制着您所看到的是您应用的 manifest 文件仍是最终合并后的 manifest 文件

在合并后的 manifest 文件中,我发现声明 WorkManager 依赖增长了不少额外的信息,包括以下的 provider 代码块:

这个 provider 存在于添加 WorkManager 依赖后合并的 manifest 文件

这个 provider 存在于添加 WorkManager 依赖后合并的 manifest 文件

我很好奇这个 provider 是从哪里来的,因此我点击了其中的第一行,编辑器直接跳到 WorkManager 的 manifest 文件,其中包含以下代码:

<application>
    <provider
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:authorities="${applicationId}.workmanager-init"
        android:directBootAware="false"
        android:exported="false"
        android:multiprocess="true"
        tools:targetApi="n" />
    <!-- ... and a bunch of other stuff ... -->
</application>

合并的 manifest 文件的工做原理就是合并了组成应用的全部模块的 manifest 文件,固然也包括我刚刚添加的 WorkManager 库的 manifest 文件。由于其包含的 content provider 如今是合并的 manifest 文件的一部分,系统会在应用启动的时候自动地建立并运行该 content provider。

如今我知道我是如何加载这个库以及运行相关的 content provider。可是这究竟有什么影响呢?

测算启动时间

我最近发布了一篇文章 - 测试应用启动性能,其中详细描述了如何测算应用的启动时间。我用了一样的方法来测算在添加 WorkManager 依赖先后的应用启动时间,而且发现 WorkManager 增长了平均 67ms 的应用启动时间。

请注意,正如我在 启动测试 的文章中提到的,我锁定了个人 Pixel 2 的 CPU 时钟频率,因此应用启动时间在其余用户的设备上可能会短一些,而在另一些使用低端设备上会长一些。另外须要注意的是 (我也在那篇文章中提到),我可能并不须要锁定时钟频率,由于系统一般会在应用启动的时候以最高的频率运行。可是锁定时钟频率在性能测试的时候永远都是一个好作法,由于这样咱们才能得到稳定的结果。同时,锁定时钟频率还一般会形成更长的运行时间 (因为更低的频率),这也会帮助咱们下降因为太短运行时间形成的噪音数据。

还有一点须要强调的是,这个启动时间并不全是 content provider 产生的。Content provider 确实会须要必定时间来建立,可是其须要大概 1-2ms 而不是我看到的 67ms。其实这是这个库被加载以及初始化的总时间,外加建立和运行 content provider 的时间来初始化该代码库。

因此看起来仅仅是添加这个库到个人项目就形成了将近 70 毫秒的启动延迟。在一个真正的应用中,我可能会使用多个库,而在应用启动时它们中不少都有本身的 content provider 须要运行,这就会形成更严重的启动延迟。

因此,咱们要作点什么来减轻这个问题的影响呢?敬请关注咱们的后续文章,在下一篇文章中,我将深刻探讨如何利用 AndroidX 的应用启动 (App Startup) 库来实现库的延迟加载。

相关文章
相关标签/搜索