我为什么弃用Jetpack的App Startup?

2020_08_05_00_07.jpeg

前言

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

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

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

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

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

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

因此细想了一下,不由让我想起了三国时的一个名词:鸡肋。食之无味,弃之惋惜。架构

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

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

因此坚持一下,就有了下面这个库,App Startup的进阶版Android Startupide

Android Startup

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

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

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

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

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

添加依赖

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

dependencies {
    implementation 'com.rousetime.android:android-startup:1.0.1'
}
依赖版本的更新信息: Release

快速使用

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,优化配置的初始化时间与组件化开发的配置注入时机,使用前与使用后时间对比:

状态 启动页面 消耗时间
使用前 WelcomeActivity 420ms
使用后 WelcomeActivity 333ms

AwesomeGithub

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

项目图

除了Android原生版本,还有基于Flutter的跨平台版本flutter_github

若是你喜欢个人文章,你能够关注个人微信公众号:【Android补给站】或者扫描下方二维码进行关注,固然你也能够直接关注当前网站的账号。主要区别就是微信可以更方法互动。

公众号更新不会很频繁,但一旦更新一定是纯干货。

Android补给站.jpg

相关文章
相关标签/搜索