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

首先我留神到的是它的名字 —— 利用启动,其表明这个库的性能可能比它字面上的意义更宽泛。这个库并不波及一般的启动 (起码目前如此)。它次要是为了升高由 content provider 初始化导致的对利用启动速度的影响。

眼下您可能和我一样素来没有思考过第三方库都是如何被初始化的。兴许是因为所有这些处理过程都在底层实现。精确地说,您在 build.gradle 文件中增加了一行代码来使一个开发库作为工程的依赖项,功败垂成 (当然您还须要在工程中调用这个库的 API,要不然您为什么要增加它呢?)。

可是有很多库并不是简略地封装好一堆办法以供调用,它们时常还须要首先被初始化,而往往这个初始化还是很耗时的过程。更蹩脚的是,这其中还暗藏陷阱,因为这些库经常在利用启动的时候进行加载和初始化,究其原因是因为其外部应用了 content provider。

敞开您的心扉 - Content Provider

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

只有一个利用通过任何一种形式申明 content provider 开启,此时就会主动创立并且启动 content provider。

应用 content provider 有一个重要但可能并不那么显著的问题,就是利用在申明 content provider 开启后,它会被主动创立并运行。而且须要留神的是,一个利用的启动并不只是通过用户启动,其还能够是通过零碎拜访该利用的服务,又或者是 job scheduler 触发了利用的一个循环作业等等。所有的这些都会触发 content provider 的资源开销以及产生相应的运算作业。当有须要拜访该 content provider 的时候,零碎须要该利用可能处于就绪状态,所以零碎会在利用启动的时候主动运行 content provider。

这些细节对于仅仅调用这些库的开发者都是不可见的,因为具体实现都暗藏在主动生成的代码中。您须要查看 合并后的 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/workdef 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 文件中,我发现申明 WorkManager 依赖减少了很多额定的信息,包含如下的 provider 代码块:

这个 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) 库来实现库的提早加载。