我为何弃用Jetpack的App Startup?

6,982 阅读6分钟

前言

最近Jetpack又添加了新成员App Startup,官方声明这是一个在Android应用启动时,针对初始化组件进行优化的依赖库。本人第一次听到后非常高兴,因为自己负责的项目在启动时需要初始化的东西实在是太多,而且有点杂乱无章,都耦合在一起了。对于可以异步初始化的组件也没有进行异步处理,而对于已经处理过的异步组件它们之间的依赖关系或者多个异步之后的统一逻辑处理也没有一个很好的统一规范。所以针对这种情况早就想找个方案来优化了,这次终于等到了App Startup

但是,当我元气满满的去查看官方文档时,并没有找到预想中的结果。官方文档中只提到了可以通过一个ContentProvider来统一管理需要初始化的组件,同时通过dependencies()方法解决组件间初始化的依赖顺序,然后呢?没了?等等官方你是不是漏了什么?

异步处理呢?虽然我们可以在create()方法中手动创建子线程进行异步任务,但一个异步任务依赖另一个异步任务又该如何处理呢?多个异步任务完成之后,统一逻辑处理又在哪里呢?依赖任务完成后的回调又在哪里?亦或者是依赖任务完成后的通知?

我有点不相信,所以又去查看了App Startup的源码,源码很简单,也就几个文件,最后发现确实只支持上面的那几个功能。

如果你的项目都是同步初始化的话,并且使用到了多个ContentProviderApp Startup可能有一定的优化空间,毕竟统一到了一个ContentProvider中,同时支持了简单的顺序依赖。

值得一提的是,App Startup中只提供了使用反射来获取初始化的组件实例,这对于一些没有过多依赖的初始化项目来说,盲目使用App Startup来优化是否会对启动速度进一步造成影响呢?

所以细想了一下,不禁让我想起了三国时的一个名词:鸡肋。食之无味,弃之可惜。

但最终我还是决定放弃使用它。

放弃之后有点不甘心,可能更多的是它没有解决我当前的项目场景。都分析了这么多,源码都看了,总不能半途而废吧,所以自己咬咬牙再补充一点呗。

所以坚持一下,就有了下面这个库,App Startup的进阶版Android Startup

Android Startup

Android Startup提供一种在应用启动时能够更加简单、高效的方式来初始化组件。开发人员可以使用Android Startup来简化启动序列,并显式地设置初始化顺序与组件之间的依赖关系。 与此同时,Android Startup支持同步与异步等待,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。

由于Android Startup是基于App Startup进行的扩展,所以它的使用方式与App Startup有点类似,该有的功能基本上都有,同时额外还附加其它功能。

下面是一张与google的App Startup功能对比的表格。

指标App StartupAndroid Startup
手动配置
自动配置
依赖支持
闭环处理
线程控制
异步等待
依赖回调
拓扑优化

下面简单介绍一下Android Startup的使用。

添加依赖

将下面的依赖添加到build.gradle文件中:

dependencies {
    implementation 'com.rousetime.android:android-startup:1.0.1'
}

快速使用

android-startup提供了两种使用方式,在使用之前需要先定义初始化的组件。

定义初始化的组件

每一个初始化的组件都需要实现AndroidStartup<T>抽象类,它实现了Startup<T>接口,它主要有以下四个抽象方法:

  • callCreateOnMainThread(): Boolean用来控制create()方法调时所在的线程,返回true代表在主线程执行。

  • waitOnMainThread(): Boolean用来控制当前初始化的组件是否需要在主线程进行等待其完成。如果返回true,将在主线程等待,并且阻塞主线程。

  • create(): T?组件初始化方法,执行需要处理的初始化逻辑,支持返回一个T类型的实例。

  • dependencies(): List<Class<out Startup<*>>>?返回Startup<*>类型的list集合。用来表示当前组件在执行之前需要依赖的组件。

例如,下面定义一个SampleFirstStartup类来实现AndroidStartup<String>抽象类:

class SampleFirstStartup : AndroidStartup<String>() {
 
    override fun callCreateOnMainThread(): Boolean = true
 
    override fun waitOnMainThread(): Boolean = false
 
    override fun create(context: Context): String? {
        // todo something
        return this.javaClass.simpleName
    }
 
    override fun dependencies(): List<Class<out Startup<*>>>? {
        return null
    }
 
}

因为SampleFirstStartup在执行之前不需要依赖其它组件,所以它的dependencies()方法可以返回空,同时它会在主线程中执行。

注意:️虽然waitOnMainThread()返回了false,但由于它是在主线程中执行,而主线程默认是阻塞的,所以callCreateOnMainThread()返回true时,该方法设置将失效。

假设你还需要定义SampleSecondStartup,它依赖于SampleFirstStartup。这意味着在执行SampleSecondStartup之前SampleFirstStartup必须先执行完毕。

class SampleSecondStartup : AndroidStartup<Boolean>() {
 
    override fun callCreateOnMainThread(): Boolean = false
 
    override fun waitOnMainThread(): Boolean = true
 
    override fun create(context: Context): Boolean {
        // 模仿执行耗时
        Thread.sleep(5000)
        return true
    }
 
    override fun dependencies(): List<Class<out Startup<*>>>? {
        return listOf(SampleFirstStartup::class.java)
    }
 
}

dependencies()方法中返回了SampleFirstStartup,所以它能保证SampleFirstStartup优先执行完毕。 它会在子线程中执行,但由于waitOnMainThread()返回了true,所以主线程会阻塞等待直到它执行完毕。

例如,你还定义了SampleThirdStartupSampleFourthStartup

Manifest中自动配置

第一种初始化方法是在Manifest中进行自动配置。

在Android Startup中提供了StartupProvider类,它是一个特殊的content provider,提供自动识别在manifest中配置的初始化组件。 为了让其能够自动识别,需要在StartupProvider中定义<meta-data>标签。其中的name为定义的组件类,value的值对应为android.startup

<provider
    android:name="com.rousetime.android_startup.provider.StartupProvider"
    android:authorities="${applicationId}.android_startup"
    android:exported="false">
 
    <meta-data
         android:name="com.rousetime.sample.startup.SampleFourthStartup"
        android:value="android.startup" />
 
</provider>

你不需要将SampleFirstStartupSampleSecondStartupSampleThirdStartup添加到<meta-data>标签中。这是因为在SampleFourthStartup中,它的dependencies()中依赖了这些组件。StartupProvider会自动识别已经声明的组件中依赖的其它组件。

Application中手动配置

第二种初始化方法是在Application进行手动配置。

手动初始化需要使用到StartupManager.Builder()

例如,如下代码使用StartupManager.Builder()进行初始化配置。

class SampleApplication : Application() {
 
    override fun onCreate() {
        super.onCreate()
        StartupManager.Builder()
            .addStartup(SampleFirstStartup())
            .addStartup(SampleSecondStartup())
            .addStartup(SampleThirdStartup())
            .addStartup(SampleFourthStartup())
            .build(this)
            .start()
            .await()
    }
}

如果你开启了日志输出,然后运行项目之后,将会在控制台中输出经过拓扑排序优化之后的初始化组件的执行顺序。

 D/StartupTrack: TopologySort result: 
    ================================================ ordering start ================================================
    order [0] Class: SampleFirstStartup => Dependencies size: 0 => callCreateOnMainThread: true => waitOnMainThread: false
    order [1] Class: SampleSecondStartup => Dependencies size: 1 => callCreateOnMainThread: false => waitOnMainThread: true
    order [2] Class: SampleThirdStartup => Dependencies size: 2 => callCreateOnMainThread: false => waitOnMainThread: false
    order [3] Class: SampleFourthStartup => Dependencies size: 3 => callCreateOnMainThread: false => waitOnMainThread: false
    ================================================ ordering end ================================================

完整的代码实例,你可以通过查看app获取。

更多

可选配置

  • LoggerLevel: 控制Android Startup中的日志输出,可选值包括LoggerLevel.NONE, LoggerLevel.ERROR and LoggerLevel.DEBUG

  • AwaitTimeout: 控制Android Startup中主线程的超时等待时间,即阻塞的最长时间。

Manifest中配置

使用这些配置,你需要定义一个类去实现StartupProviderConfig接口,并且实现它的对应方法。

class SampleStartupProviderConfig : StartupProviderConfig {
 
    override fun getConfig(): StartupConfig =
        StartupConfig.Builder()
            .setLoggerLevel(LoggerLevel.DEBUG)
            .setAwaitTimeout(12000L)
            .build()
}

与此同时,你还需要在manifest中进行配置StartupProviderConfig

<provider
     android:name="com.rousetime.android_startup.provider.StartupProvider"
    android:authorities="${applicationId}.android_startup"
    android:exported="false">
 
    <meta-data
         android:name="com.rousetime.sample.startup.SampleStartupProviderConfig"
         android:value="android.startup.provider.config" />
 
</provider>

经过上面的配置,StartupProvider会自动解析SampleStartupProviderConfig

Application中配置

在Application需要借助StartupManager.Builder()进行配置。

override fun onCreate() {
    super.onCreate()
 
    val config = StartupConfig.Builder()
        .setLoggerLevel(LoggerLevel.DEBUG)
        .setAwaitTimeout(12000L)
        .build()
 
    StartupManager.Builder()
        .setConfig(config)
        ...
        .build(this)
        .start()
        .await()
}

方法

AndroidStartup

  • createExecutor(): Executor: 如果定义的组件没有运行在主线程,那么可以通过该方法进行控制运行的子线程。

  • onDependenciesCompleted(startup: Startup<*>, result: Any?): 该方法会在每一个依赖执行完毕之后进行回调。

实战测试

AwesomeGithub中使用了Android Startup,优化配置的初始化时间与组件化开发的配置注入时机,使用前与使用后时间对比:

状态启动页面消耗时间
使用前WelcomeActivity420ms
使用后WelcomeActivity333ms

项目

android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件。

AwesomeGithub: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

微信公众号:【Android补给站】或者扫描下方二维码进行关注