关于android:Android-应用启动性能-延迟初始化

55次阅读

共计 5635 个字符,预计需要花费 15 分钟才能阅读完成。

在 上一篇文章 中,我展现了 content provider (它呈现在利用合并后的 manifest 文件) 是如何在利用启动的时候主动加载第三方库以及模块的。

在这篇文章中,我会介绍如何应用 AndroidX 的 利用启动 (App Startup) 库来进一步管制那些库该在何时以及以何种形式被加载。兴许,我是说兴许,咱们也会顺便发现该如何缩短利用的启动工夫。

应用利用启动库主动初始化

应用利用启动库 (App Startup) 最简略的形式是利用它的 content provider 在后盾初始化其余库。您既能够指定利用启动库该如何初始化其余的库,也能够从合并后的 manifest 文件中移除其余库的 content provider。防止应用多个 content provider 执行启动工作,而是将资源用于加载利用启动库,而后再加载其余内容。

您能够通过如下三步实现上述操作,首先在您工程的 build.gradle 文件中增加利用启动库作为依赖,其次为每一个须要初始化的库创立一个 Initializer,最初在您工程的 Manifest.xml 文件中增加相干信息。

让咱们再看一遍我在 第一篇文章 中应用的 WorkManager 示例。为了通过利用启动库加载 WorkManager,我先在利用的 build.gradle 文件中增加了利用启动库:

// 查看最新的版本号 https://developer.android.google.cn/jetpack/androidx/releases/startup
def startup_version = "1.0.0"
implementation“androidx.startup:startup-runtime:$startup_version”

而后,基于利用启动库提供的 Initializer 接口,我创立了一个 Initializer:

class MyWorkManagerInitializer : Initializer<WorkManager> {override fun create(context: Context): WorkManager {val configuration = Configuration.Builder().build()
        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // 没有其余依赖库
        return emptyList()}
}

每一个 Initializer 有两个办法须要复写: create()dependencies()dependencies() 被用来指定多个依赖库的初始化程序。在这个示例中我并不需要这个性能,因为我只须要解决 WorkManager。如果您须要在利用中应用多个库,请查看 利用启动使用手册 中对于应用 dependencies() 的详情。

对于 create() 办法,我模拟了 WorkManager’s content provider 中的实现。

顺便说一下,其实这个办法在应用利用启动库的时候很罕用。一个库的 content provider 负责了其初始化的实现,所以您通常都能够参考那个类中的代码来手动实现它。有些库可能比拟麻烦,因为它们应用了暗藏的或者外部的 API,然而好在 WorkManager 并不是,所以我能够这么做,心愿该办法也实用于您的状况。

最初,我在 Manifest.xml 文件的 <application> 代码块中增加了两个 provider 的标签。第一个如下所示:

<provider
    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 标签:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data     android:name="com.example.startuplibtest.MyWorkManagerInitializer"
    android:value="androidx.startup" />
</provider>

InitializationProvider 标签和通过增加利用启动库到 build.gradle 文件中主动生成的标签基本相同 (您能够通过查看合并后的 manifest 文件来验证 — 详情请查看 第一篇文章),然而它们有两个很重要的不同点:

tools:node="merge"

这个参数次要用于 Android Studio 所负责的 manifest 合并操作。它通知工具在最终合并的 manifest 文件中合并这个标签的多个实例。在这个例子中,它会合并由库依赖主动生成的 <provider> 到这个版本的 provider,这样在最终合并的 manifest 文件中只会有这一个标签实例。

另一行蕴含了这个 meta-data:

<meta-data  android:name="com.example.startuplibtest.MyWorkManagerInitializer"
    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 的局部。

<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 文件。您能够看到多少 <provider> 标签?
  • 您是否利用利用启动库从合并的 manifest 文件中移除一些甚至所有这些 content provider,并察看它如何影响启动工夫?您是否在实现这个的同时不影响运行时行为呢?(值得注意的是: 您须要保障在利用开始依赖相干库的性能之前,确保初始化它们。)

最初,纵情享受性能测试和优化。我会持续找寻更多剖析和优化利用的性能方法,如果发现什么有价值的货色我会公布相干的内容。

正文完
 0