android startAdvertising断开后立马重连了 android startup_编程语言

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

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

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

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

android startAdvertising断开后立马重连了 android startup_编程语言_02

敞开您的心扉 - Content Provider

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

只要一个应用通过任何一种方式声明 content provider 开启,此时就会自动创建并且启动 content provider。

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

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

android startAdvertising断开后立马重连了 android startup_物联网_03

合并 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 文件。

android startAdvertising断开后立马重连了 android startup_编程语言_04

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

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

android startAdvertising断开后立马重连了 android startup_编程语言_05

这个 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。但是这究竟有什么影响呢?

android startAdvertising断开后立马重连了 android startup_编程语言_06

测算启动时间

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

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

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

所以看起来仅仅是添加这个库到我的项目就造成了将近 70 毫秒的启动延迟。在一个真正的应用中,我可能会使用多个库,而在应用启动时它们中很多都有自己的 content provider 需要运行,这就会造成更严重的启动延迟。

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