1、前言

最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。

Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:

  • Android Jetpack组件总览
  • Android Jetpack 组件之 Lifecycle使用
  • Android Jetpack 组件之 Lifecycle源码
  • Android Jetpack组件之ViewModel使用
  • Android Jetpack组件之 LiveData使用-源码
  • Android Jetpack组件之 Paging使用-源码
  • Android Jetpack组件之 Room使用-源码
  • Android Jetpack组件之Navigation使用-源码
  • Android Jetpack组件之WorkManger使用介绍
  • Android Jetpack组件App Startup简析
  • Android Jetpack组件之Hilt使用

本系列文章是各处copy过来的,个人感觉所有的开发者都应该尽早的熟悉Jetpack组件,相信一定会被它的魅力所吸引,最近也在完成一个使用以上所有组件实现的项目,作为对Jetpack组件的项目实践,下面来分析一下每个组件对项目开发的帮助。

2、为什么需要App startup

App Startup是一个可以用于加速App启动速度的一个库。很多人一听到可以加速App的启动速度?那这是好东西啊,迫不及待地想要将这个库引入到自己的项目当中,结果研究了半天,发现越看越不明白,怎么学着学着还和ContentProvider扯上关系了?

所以,在学习App Startup的用法之前,首先我们需要搞清楚的是,App Startup具体是用来解决什么问题的。

比如,LitePal是一个Android数据库框架。这个框架可以帮助大家自动管理表的创建与升级,并提供方便的数据库操作API。

而用过LitePal的朋友一定知道,LitePal有提供一个initialize()接口,在进行所有的数据库操作之前,我们需要在自己的Application当中去调用这个接口进行初始化:

class MyApplication : Application() {override fun onCreate() {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,并让它继承自ContentProvider,然后我们在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。

3、使用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()}}

假设我们的应用依赖了另一个叫做ExampleLogger的库,这个库依赖于LitePalInitializer。这也就意味着,初始化这个库必须先确保Initializer的实例已经被初始化了才可以。那么如何做呢?我们看下面代码:

// Initializes ExampleLogger ,
class ExampleLoggerInitializer : Initializer<ExampleLogger> {override fun create(context: Context): ExampleLogger {// LitePal.initialize(context)return ExampleLogger(WorkManager.getInstance(context))}override fun dependencies(): List<Class<out Initializer<*>>> {// Defines a dependency on Wor kManagerInitializer so it can be// initialized after WorkManager is initialized.return listOf(LitePalInitializer::class.java)}
}

定义好了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。

4、拓展

其实到这里为止,App Startup库的知识就已经讲完了,最后再介绍一个不太常用的知识点吧:延迟初始化。

现在我们已经知道,所有的Initializer都会在App启动的时候自动执行初始化操作。但是如果我作为LitePal库的用户,就是不希望它在启动的时候自动初始化,而是想要在特定的时机手动初始化,这要怎么办呢?

首先,你得通过分析LitePal源码的方式,找到LitePal用于初始化的Initializer的全路径类名是什么,比如上述例子当中的com.example.LitePalInitializer(注意这里我只是为了讲解这个知识点而举的例子,实际上LitePal还并没有接入App Startup)。

然后,在你的项目的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"tools:node="remove" /></provider></application>

区别就在于,这里在LitePalInitializer的meta-data当中加入了一个tools:node="remove"的标记。

这个标记用于告诉manifest merger tool,在最后打包成APK时,将所有android:name是com.example.LitePalInitializer的meta-data节点全部删除。

这样,LitePal库在自己的AndroidManifest.xml中配置的Initializer也会被删除,既然删除了,App Startup在启动的时候肯定就无法初始化它了。

而在之后手动去初始化LitePal的代码也极其简单,如下所示:

AppInitializer.getInstance(this).initializeComponent(LitePalInitializer::class.java)

将LitePalInitializer传入到initializeComponent()方法当中即可,App Startup库会按照同样的标准去调用其create()方法来执行初始化操作。

到这里为止,App Startup的功能基本就全部讲解完了。

最后如果让我总结一下的话,这个库的整体用法非常简单,但是可能并不适合所有人去使用。如果你是一个库开发者,并且使用了ContentProvider的方式来进行初始化操作,那么你应该接入App Startup,这样可以让接入你的库的App降低启动耗时。而如果你是一个App开发者,我认为使用ContentProvider来进行初始化操作的概率很低,所以可能App Startup对你来说用处并不大。

当然,考虑到业务逻辑分离的代码结构,App的开发者也可以考虑将一些原来放在Application中的初始化代码,移动到一个Initializer中去单独执行,或许可以让你的代码结构变得更加合理与清晰。

5、源码分析

App Startup包中代码并不多,只有五个类:

其中最核心的类就是InitializationProvider,它是继承了ContentProvider,这样我们就懂了,在onCreate()方法中,可以看到它其实是调用了AppInitializer这个类中的discoverAndInitialize()方法,我们简单看下这个代码:

public final class InitializationProvider extends ContentProvider {@Overridepublic boolean onCreate() {Context context = getContext();if (context != null) {AppInitializer.getInstance(context).discoverAndInitialize();} else {throw new StartupException("Context cannot be null");}return true;}----}

代码很简单,就是解析出来metadata中的数据,然后遍历metadata拿到配置的初始化器,然后调用每个初始化器的初始化方法,也就是doInitialize()方法。接下来再看下这个方法:

    @NonNull@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})<T> T doInitialize(@NonNull Class<? extends Initializer<?>> component,@NonNull Set<Class<?>> initializing) {synchronized (sLock) {boolean isTracingEnabled = Trace.isEnabled();try {if (isTracingEnabled) {// Use the simpleName here because section names would get too big otherwise.Trace.beginSection(component.getSimpleName());}if (initializing.contains(component)) {String message = String.format("Cannot initialize %s. Cycle detected.", component.getName());throw new IllegalStateException(message);}Object result;if (!mInitialized.containsKey(component)) {initializing.add(component);try {Object instance = component.getDeclaredConstructor().newInstance();Initializer<?> initializer = (Initializer<?>) instance;List<Class<? extends Initializer<?>>> dependencies =initializer.dependencies();if (!dependencies.isEmpty()) {for (Class<? extends Initializer<?>> clazz : dependencies) {if (!mInitialized.containsKey(clazz)) {doInitialize(clazz, initializing);}}}if (StartupLogger.DEBUG) {StartupLogger.i(String.format("Initializing %s", component.getName()));}result = initializer.create(mContext);if (StartupLogger.DEBUG) {StartupLogger.i(String.format("Initialized %s", component.getName()));}initializing.remove(component);mInitialized.put(component, result);} catch (Throwable throwable) {throw new StartupException(throwable);}} else {result = mInitialized.get(component);}return (T) result;} finally {Trace.endSection();}}}

可以看到在执行初始化的时候,先判断了是否有依赖项,有的话先执行依赖项的初始化。

Android Jetpack组件App Startup简析相关推荐

  1. Android Jetpack组件之Hilt使用

    前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. And ...

  2. Android Jetpack组件之WorkManger使用介绍

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  3. Android Jetpack组件之Navigation使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  4. Android Jetpack组件之 Room使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  5. Android Jetpack组件之 Paging使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  6. Android Jetpack组件之 LiveData使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  7. Android Jetpack组件之ViewModel使用

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  8. Android Jetpack 组件之 Lifecycle源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  9. Android Jetpack 组件之 Lifecycle使用

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

最新文章

  1. Lesson 8.1Lesson 8.2 决策树的核心思想与建模流程CART分类树的建模流程与sklearn评估器参数详解
  2. Javascript弹出对话框 确定取消转到不同页面
  3. Angular 项目 tsconfig.json 里定义的 out-tsc 还有作用吗?
  4. mysql maxconnections 最大值,MySQL性能优化之max_connections配置参数浅析
  5. 可以直接进行运算么_WORD办公技巧:如何直接在WORD中进行加法、乘法运算?
  6. getminimum_Java Calendar getMinimum()方法与示例
  7. SAP License:BSEG与FAGLFLEXA (A)表数据不一致问题
  8. 关于人工智能的一些思考~
  9. Android-StepsView
  10. mysql如何修改字段名字_mysql如何修改字段名称
  11. cada0图纸框_a0标准图框|autocad a0标准图纸框模板下载免费版 - 欧普软件下载
  12. VoLTE业务端到端流程:IMS侧信令流程
  13. android studio 自定义应用主题,Android studio 设置主题
  14. sourceForge, wikipedia与异形
  15. 计算机毕业设计Python+uniapp鲸落图书商城小程序LW(小程序+源码+LW)
  16. Android 应用程序开发
  17. 手把手教你汉化Windows Phone 7程序(xap包汉化)
  18. 互联网运营平台指标体系_分享 :滴滴数据仓库指标体系建设实践
  19. elasticsearch安装与配置
  20. 远程命令执行漏洞与远程代码执行漏洞33333

热门文章

  1. java.net import_import java.net.URL;是什么类?
  2. python生成器和装饰器_python三大法器:生成器、装饰器、迭代器
  3. HALCON查找圆心C++实现
  4. rabbitmq实战:高效部署分布式消息队列_一文看懂消息队列中间件--AMQ及部署介绍...
  5. PHP-FPM,Nginx,FastCGI 三者之间的关系
  6. c# 可选参数与命名实参
  7. idea搭建简单spring-boot项目
  8. 《精通Python网络爬虫:核心技术、框架与项目实战》——1.3 网络爬虫的组成...
  9. 专访WebRTC标准之父Daniel C. Burnett
  10. ViewPager+Fragment实现TabHost(可复用的类)