在 上一篇文章 中,我展现了 content provider (它出如今应用合并后的 manifest 文件) 是如何在应用启动的时候自动加载第三方库以及模块的。java
在这篇文章中,我会介绍如何使用 AndroidX 的 应用启动 (App Startup) 库来进一步控制那些库该在什么时候以及以何种方式被加载。也许,我是说也许,咱们也会顺便发现该如何缩短应用的启动时间。node
使用应用启动库自动初始化
使用应用启动库 (App Startup) 最简单的方式是利用它的 content provider 在后台初始化其余库。您既能够指定应用启动库该如何初始化其余的库,也能够从合并后的 manifest 文件中移除其余库的 content provider。避免使用多个 content provider 执行启动任务,而是将资源用于加载应用启动库,而后再加载其余内容。android
您能够经过以下三步实现上述操做,首先在您工程的 build.gradle 文件中添加应用启动库做为依赖,其次为每个须要初始化的库建立一个 Initializer,最后在您工程的 Manifest.xml 文件中添加相关信息。性能优化
让咱们再看一遍我在 第一篇文章 中使用的 WorkManager 示例。为了经过应用启动库加载 WorkManager,我先在应用的 build.gradle 文件中添加了应用启动库:app
// 查看最新的版本号 https://developer.android.google.cn/jetpack/androidx/releases/startup
def startup_version = "1.0.0"
implementation “androidx.startup:startup-runtime:$startup_version”
而后,基于应用启动库提供的 Initializer 接口,我建立了一个 Initializer:ide
class MyWorkManagerInitializer : Initializer {
override fun create(context: Context): WorkManager {
val configuration = Configuration.Builder().build()
WorkManager.initialize(context, configuration)
return WorkManager.getInstance(context)
}
override fun dependencies(): List>> {
// 没有其余依赖库
return emptyList()
}
}
每个 Initializer 有两个方法须要复写: create() 和 dependencies()。dependencies() 被用来指定多个依赖库的初始化顺序。在这个示例中我并不须要这个功能,由于我只须要处理 WorkManager。若是您须要在应用中使用多个库,请查看 应用启动使用手册 中关于使用 dependencies() 的详情。工具
顺便说一下,其实这个方法在使用应用启动库的时候很经常使用。一个库的 content provider 负责了其初始化的实现,因此您一般均可以参考那个类中的代码来手动实现它。有些库可能比较麻烦,由于它们使用了隐藏的或者内部的 API,可是好在 WorkManager 并非,因此我能够这么作,但愿该方法也适用于您的状况。测试
最后,我在 Manifest.xml 文件的 代码块中添加了两个 provider 的标签。第一个以下所示:gradle
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove" />
WorkManagerInitializer 标签很重要,由于它表示须要 Android Studio 删除自动生成的 provider,而该 provider 是在 build.gradle 文件中添加 WorkManager 后生成的。若是没有这个特殊的标签,这个库仍然会在应用启动的时候自动初始化,继而在应用启动库尝试初始化它的时候报错,由于它已经被初始化了。
下面是我添加的第二个 provider 标签:
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
android:value="androidx.startup" />
InitializationProvider 标签和经过添加应用启动库到 build.gradle 文件中自动生成的标签基本相同 (您能够经过查看合并后的 manifest 文件来验证 -- 详情请查看 第一篇文章),可是它们有两个很重要的不一样点:
tools:node="merge"
这个参数主要用于 Android Studio 所负责的 manifest 合并操做。它告诉工具在最终合并的 manifest 文件中合并这个标签的多个实例。在这个例子中,它会合并由库依赖自动生成的 到这个版本的 provider,这样在最终合并的 manifest 文件中只会有这一个标签实例。
另外一行包含了这个 meta-data:
android:value="androidx.startup" />
这个 provider 中的 metadata 标签告诉应用启动库如何找到您的 Initializer 代码,这些代码会在应用启动的时候执行来初始化这个库。请注意这致使的区别: 若是您没有使用应用启动库,就会自动执行相关初始化,由于 Android 会在那个库中建立并执行 content provider,以后会自动初始化这个库自己。可是经过应用启动库指定您的 Initializer,以及在合并 manifest 文件中去除 WorkManager 的 provider,至关于告诉 Android 转而使用应用启动库的 content provider 来加载 WorkManager 库。若是经过这个方式初始化多个库,您能够利用应用启动库的这个单独的 content provider 有效地管理这些请求,而不是致使每一个库都建立本身的 content provider。
偷个懒...若是您想的话
当优化应用启动性能的时候,咱们不能改变那些没法控制的代码实现。因此这里的思路并非加速咱们使用库的初始化,而是控制这些库何时以及如何被初始化。尤为是咱们能够决定任一个库是否须要在应用启动的时候被初始化 (要么使用库的默认机制添加 content provider 到合并的 manifest 文件,或者也能够利用应用启动库的 content provider 来集中管理初始化请求),仍是须要稍候再加载它们。
举个例子,或许在您应用的一个特殊的流程中须要某一个包含 content provider 初始化的库,可是这个库并不须要在应用启动的时候当即被加载,又或者在某些状况下它根本不须要被加载。若是是这样的话,为何要由于只在某个特殊代码路径中须要而在应用启动时花时间初始化一个很大的库呢?为何不等到这个库真正被须要的时候再引入相关的初始化开销呢?
这正是应用启动库高明的地方,它能帮您从合并的 manifest 文件中和应用启动的过程当中移除隐藏的 content provider,也能帮您延迟或者更有目的地加载这些库。
使用应用启动库实现延迟初始化
如今咱们已经知道该如何使用应用启动库实现自动加载以及初始化库。接下来让咱们更进一步地来看看,若是您不想在启动的时候初始化,该如何实现延迟初始化。
其实上面的代码已经很接近了,在 build.gradle 文件中您须要一样的启动依赖和其余您想使用的库,也仍是须要特殊的 "移除" provider 标签来去除每一个库自动生成的 content provider。咱们只须要向 manifest 文件添加多一点信息来告诉它一样移除应用启动库的 provider。这样在应用启动的时候就不会有任何 content provider 初始化发生,而彻底由您来决定何时应该触发相关初始化。
为了达到这个目的,我用下面的代码替换了前面使用的 InitializationProvider。上面所展现的代码告诉了系统该如何定位 content provider 中自动初始化您库的代码。由于稍后要手动触发初始化,这一次我要跳过那个部分,而只留下在应用启动的时候去除自动生成的 content provider 的部分。
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
在我作了这个改动后,在合并的 manifest 文件中再也不有任何 content provider 了,因此应用启动库和 WorkManager 都不会在应用启动的时候被自动初始化了。
为了手动初始化这些库,我在应用的其余地方添加了以下代码来实现它:
val initializer = AppInitializer.getInstance(context)
initializer.initializeComponent(MyWorkManagerInitializer::class.java)
AppInitializer 是应用启动库提供的链接全部这些部分的类。您须要使用一个 context 对象来建立 AppInitializer 对象,而后能够向其传递一个您为初始化各类不一样库建立的 Initializer 引用。在这个示例中,我使用的是 MyWorkManagerInitializer,而后就搞定了。
时间就是一切
我作了几回测试 (使用的是我在 测试应用启动性能 文章中提到的计时方法) 来比较几种不一样的启动应用和初始化库的方法。我统计了不带任何库、带 WorkManager (使用默认自动生成的 content provider)、在启动时使用应用启动库自动初始化 WorkManager 以及使用 AppInitializer 延迟初始化 WorkManager 和应用启动库。
须要注意的是,就像咱们在 以前的文章 中讨论的,全部的这些时间计算都是基于锁定的 CPU 主频,因此这些时长都要比在没有锁定 CPU 主频的机器上大不少。它们只在相互之间比较的时候有意义,而并不能表明真实的状况。下面是我发现的:
不带 WorkManager: 1244 ms
带 WorkManager 而且经过 content provider 加载: 1311 ms
带 WorkManager 而且经过 App Startup 加载: 1315 ms
带 WorkManager (延迟加载): 1268 ms
最后,我统计了利用 AppInitalizer 手动初始化 WorkManager 的耗时:
利用 AppInitializer 初始化 WorkManager: 51 ms
这个数据给咱们带来一些启示。首先,在应用启动的时候加载 WorkManager 会给个人应用平均增长 67 毫秒 (1311–1244) 的启动时间。须要注意的是: 加载这个库的常规方式 (使用 content provider) 使用的时间和使用应用启动库的 (1315 – 1244 = 71 ms) 差很少。这是由于应用启动库在单个库的例子中并不会帮咱们节省时间,咱们只不过是转移逻辑到另外一个代码路径中运行。若是使用应用启动库加载多个库,咱们会获得相应的优化效果,可是针对这里的单个库的例子,使用这个方法不会有任何节省时间的优点。
同时延迟初始化 WorkManager 让我能够 "节省" 大约 51 毫秒的时间。
这个差异是否足够明显到您须要担忧呢?答案永远是 "看状况而定"。
51 毫秒占了 1.3 秒总时长的不到 4%,而对于一个真实应用来讲,一般都会比我这个简单的应用更复杂,这个耗时占总启动时间的百分比会更低。这种状况下这个时长可能不值得担忧。可是有时候您可能发现有些库须要太长时间来初始化,更有可能的是,您可能使用了几个自带 content provider 的库,而它们每个都会增长一点您应用的启动时间。若是您能够将上述大部分或者所有工做推迟到一个更为合适的时间点,而且从启动过程当中剥离,或许您会发现应用的启动速度会有显著的提升。
像全部的性能优化项目,您能够作的最重要的事情是分析细节、测量以及决定:
检查您项目合并后的 manifest 文件。您能够看到多少 标签?
您可否利用应用启动库从合并的 manifest 文件中移除一些甚至全部这些 content provider,并观察它如何影响启动时间?您可否在实现这个的同时不影响运行时行为呢?(值得注意的是: 您须要保证在应用开始依赖相关库的功能以前,确保初始化它们。)
最后,尽情享受性能测试和优化。我会继续找寻更多分析和优化应用的性能办法,若是发现什么有价值的东西我会发布相关的内容。