郭霖说 Jetpack 新成员:App-Startup 一篇就懂
super.onCreate()LitePal.initialize(this)}...}
为什么 LitePal 要求先进行初始化呢?因为 Android 的数据库中有需要操作都是需要依赖于 Context 的,在初始化的时候传入一次 Context,LitePal 会在内部将其保存下来,这样所以有其他数据库接口就不需要再传入 Context 参数了,从而让 API 变得更加精简。
这确实是个不错的主意,但是并不是只有 LitePal 想到了这一点,许多库也提供了类似的初始化接口,因此如果你在项目当中引入了非常多的第三方库,那么 Application 中的代码就可能会变成这个样子:
class?MyApplication?:?Application()?{
override?fun?onCreate()?{super.onCreate()LitePal.initialize(this)AAA.initialize(this)BBB.initialize(this)CCC.initialize(this)DDD.initialize(this)EEE.initialize(this)}...}
这样的代码就会显得有些凌乱了对不对?随着你引用的第三方库越来越多,这种情况真的是有可能发生的。
于是,有些更加聪明的库设计者,他们想到了一种非常巧妙的办法来避免显示地调用初始化接口,而是可以自动调用初始化接口,这种办法就是借助 ContentProvider。
ContentProvider 我们都知道是 Android 四大组件之一,它的主要作用是跨应用程序共享数据。比如为什么我们可以读取到电话簿中的联系人、相册中的照片等数据,借助的都是 ContentProvider。
然而这些聪明的库设计者们并没有打算使用 ContentProvider 来跨应用程序共享数据,只是准备使用它进行初始化而已。我们来看如下代码:
class?MyProvider?:?ContentProvider()?{
override?fun?onCreate():?Boolean?{context?.let?{LitePal.initialize(it)}return?true}...}
这里我定义了一个 MyProvider,并让它继承自 Co
ntentProvider,然后我们在 onCreate()方法中调用了 LitePal 的初始化接口。注意在 ContentProvider 中也是可以获取到 Context 的。
当然,继承了 ContentProvider 之后,我们是要重写很多个方法的,只不过其他方法在我们这个场景下完全使用不到,所以你可以在那些方法中直接抛出一个异常,或者进行空实现都是可以的。
另外不要忘记,四大组件是需要在 AndroidManifest.xml 文件中进行注册才可以使用的,因此记得添加如下内容:
<application?...>
<providerandroid:name=".MyProvider"android:authorities="${applicationId}.myProvider"android:exported="false"?/>
</application>
authorities 在这里并没有固定的要求,填写什么值都是可以的,但必须保证这个值在整个手机上是唯一的,所以通常会使用 ${applicationId}作为前缀,以防止和其他应用程序冲突。
那么,自定义的这个 MyProvider 它会在什么时候执行呢?我们来看一下这张流程图:
 
 可以看到,一个应用程序的执行顺序是这个样子的。首先调用 Application 的 attachBaseContext()方法,然后调用 ContentProvider 的 onCreate()方法,接下来调用 Application 的 onCreate()方法。
那么,假如 LitePal 在自己的库当中实现了上述的 MyProvider,会发生什么情况呢?
你会发现 LitePal.initialize()这个接口可以省略了,因为在 MyProvider 当中这个接口会被自动调用,这样在进入 Application 的 onCreate()方法时,LitePal 其实已经初始化过了。
有没有觉得这种设计方式很巧妙?它可以将库的用法进一步简化,不需要你主动去调用初始化接口,而是将这个工作在背后悄悄自动完成了。
那么有哪些库使用了这种设计方式呢?这个真的有很多了,比如说 Facebook 的库,Firebase 的库,还有我们所熟知的 WorkManager,Lifecycles 等等。这些库都没有提供一个像 LitePal 那样的初始化接口,其实就是使用了上述的技巧。
看上去如此巧妙的技术方案,那么它有没有什么缺点呢?
有,缺点就是,ContentProvider 会增加许多额外的耗时。
毕竟 ContentProvider 是 Android 四大组件之一,这个组件相对来说是比较重量级的。也就是说,本来我的初始化操作可能是一个非常轻量级的操作,依赖于 ContentProvider 之后就变成了一个重量级的操作了。
关于 ContentProvider 的耗时,Google 官方也有给出一个测试结果:
 
 这是在一台搭载 Android 10 系统的 Pixel2 手机上测试的情况。可以看到,一个空的 ContentProvider 大约会占用 2ms 的耗时,随着 ContentProvider 的增加,耗时也会跟着一起增加。如果你的应用程序中使用了 50 个 ContentProvider,那么将会占用接近 20ms 的耗时。
注意这还只是空 ContentProvider 的耗时,并没有算上你在 ContentProvider 中执行逻辑的耗时。
这个测试结果告诉我们,虽然刚才所介绍的使用 ContentProvider 来进行初始化的设计方式很巧妙,但是如果每个第三方库都自己创建了一个 ContentProvider,那么最终我们 App 的启动速度就会受到比较大的影响。
有没有办法解决这个问题呢?
有,就是使用我们今天要介绍的主题:App Startup。
我上面花了很长的篇幅来介绍 App Startup 具体是用来解决什么问题的,因为这部分内容才是 App Startup 库的核心,只有了解了它是用来解决什么问题的,才能快速掌握它的用法。不然就会像刚开始说的那样,学着学着怎么学到 ContentProvider 上面去了,一头雾水。
那么 App Startup 是如何解决这个问题的呢?它可以将所有用于初始化的 ContentProvider 合并成一个,从而使 App 的启动速度变得更快。
具体来讲,App Startup 内部也创建了一个 ContentProvider,并提供了一套用于初始化的标准。然后对于其他第三方库来说,你们就不需要再自己创建 ContentProvider 了,都按我的这套标准进行实现就行了,我可以保证你们的库在 App 启动之前都成功进行初始化。
了解了 App Startup 具体是用来解决什么问题的,以及它的实现原理,接下来我们开始学习它的用法,这部分就非常简单了。
首先要使用 App Startup,我们要将这个库引入进来:
dependencies?{implementation?"androidx.startup:startup-runtime:1.0.0-alpha01"}
接下来我们要定义一个用于执行初始化的 Initializer,并实现 App Startup 库的 Initializer 接口,如下所示:
class?LitePalInitializer?:?Initializer<Unit>?{
override?fun?create(context:?Context)?{LitePal.initialize(context)}
override?fun?dependencies():?List<Class<out?Initializer<*>>>?{return?listOf(OtherInitializer::class.java)}
}
实现 Initializer 接口要求重现两个方法,在 create()方法中,我们去进行之前要进行的初始化操作就可以了,create()方法会把我们需要的 Context 参数传递进来。
dependencies()方法表示,当前的 LitePalInitializer 是否还依赖于其他的 Initializer,如果有的话,就在这里进行配置,App Startup 会保证先初始化依赖的 Initializer,然后才会初始化当前的 LitePalInitializer。
当然,绝大多数的情况下,我们的初始化操作都是不会依赖于其他 Initializer 的,所以通常直接返回一个 emptyList()就可以了,如下所示:
class?LitePalInitializer?:?Initializer<Unit>?{
override?fun?create(context:?Context)?{LitePal.initialize(context)}
override?fun?dependencies():?List<Class<out?Initializer<*>>>?{return?emptyList()}
}
定义好了 Initializer 之后,接下来还剩最后一步,将它配置到 AndroidManifest.xml 当中。但是注意,这里的配置是有比较严格的格式要求的,如下所示:
<application?...>
<providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"android:exported="false"tools:node="merge"><meta-dataandroid:name="com.example.LitePalInitializer"android:value="androidx.startup"?/></provider>
</application>
上述配置,我们能修改的地方并不多,只有 meta-data 中的 android:name 部分我们需要指定成我们自定义的 Initializer 的全路径类名,其他部分都是不能修改的,否则 App Startup 库可能会无法正常工作。
没错,App Startup 库的用法就是这么简单,基本我将它总结成了三步走的操作。
- 引入 App Startup 的库。 
- 自定义一个用于初始化的 Initializer。 
- 将自定义 Initializer 配置到 AndroidManifest.xml 当中。 
这样,当 App 启动的时候会自动执行 App Startup 库中内置的 ContentProvider,并在它的 ContentProvider 中会搜寻所有注册的 Initializer,然后逐个调用它们的 create()方法来进行初始化操作。
只用一个 ContentProvider 就可以让所有库都正常初始化,Everyone is happy。
其实到这里为止,App Startup 库的知识就已经讲完了,最后再介绍一个不太常用的知识点吧:延迟初始化。
现在我们已经知道,所有的 Initializer 都会在 App 启动的时候自动执行初始化操作。但是如果我作为 LitePal 库的用户,就是不希望它在启动的时候自动初始化,而是想要在特定的时机手动初始化,这要怎么办呢?
首先,你得通过分析 LitePal 源码的方式,找到 LitePal 用于初始化的 Initializer 的全路径类名是什么,比如上述例子当中的 com.example.LitePalInitializer(注意这里我只是为了讲解这个知识点而举的例子,实际上 LitePal 还并没有接入 App Startup)。











 
    
评论