乐趣区

关于android:Android-Startup实现分析

前言

Android Startup 提供一种在利用启动时可能更加简略、高效的形式来初始化组件。开发人员能够应用 Android Startup 来简化启动序列,并显式地设置初始化程序与组件之间的依赖关系。与此同时,Android Startup反对 同步与异步期待、手动管制依赖执行机会 ,并通过 有向无环图拓扑排序 的形式来保障外部依赖组件的初始化程序。

Android Startup通过几轮的迭代曾经更加欠缺了,反对的性能场景也更加多样,如果你要应用 Android Startup 的新个性,请将依赖降级到最新版本 latest release

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

在之前的我为何弃用 Jetpack 的 App Startup? 文章中有提供一张与 App Startup 的比照图,当初也有了一点变动

指标 App Startup Android Startup
手动配置
主动配置
依赖反对
闭环解决
线程管制
异步期待
依赖回调
手动告诉
拓扑优化

核心内容都在这种比照图中,上面依据这种比照图来详细分析一下 Android Startup 的实现原理。

配置

手动

手动配置是通过 StartupManager.Builder()来实现的,实质很简略,应用 builder 模式来初始化一些必要的参数,进而来获取 StartupManager 实例,最初再启动Android Startup

val config = StartupConfig.Builder()
    .setLoggerLevel(LoggerLevel.DEBUG)
    .setAwaitTimeout(12000L)
    .setListener(object : StartupListener {override fun onCompleted(totalMainThreadCostTime: Long, costTimesModels: List<CostTimesModel>) {
            // can to do cost time statistics.
            costTimesLiveData.value = costTimesModels
            Log.d("StartupTrack", "onCompleted: ${costTimesModels.size}")
        }
    })
    .build()
 
StartupManager.Builder()
    .setConfig(config)
    .addStartup(SampleFirstStartup())
    .addStartup(SampleSecondStartup())
    .addStartup(SampleThirdStartup())
    .addStartup(SampleFourthStartup())
    .build(this)
    .start()
    .await()

主动

另一种形式是主动配置,开发者不须要手动调用 StartupManager.Builder(),只需在AndroidManifest.xml 文件中进行配置。

<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" />

    <meta-data
        android:name="com.rousetime.sample.startup.SampleFourthStartup"
        android:value="android.startup" />

</provider>

而实现这种配置的原理是:Android Startup外部是通过一个 ContentProvider 来实现主动配置的,在 AndroidContentProvider的初始化机会介于 ApplicationattachBaseContextonCreate 之间。所以 Android Startup 借助这一个性将初始化的逻辑都封装到自定义的 StartupProvider 中

class StartupProvider : ContentProvider() {override fun onCreate(): Boolean {context.takeIf { context -> context != null}?.let {val store = StartupInitializer.instance.discoverAndInitialize(it)
            StartupManager.Builder()
                .setConfig(store.config?.getConfig())
                .addAllStartup(store.result)
                .build(it)
                .start()
                .await()} ?: throw StartupException("Context cannot be null.")

        return true
    }
    ...
    ...
}

有了 StartupProvider 之后,下一步须要做的就是解析在 AndroidManife.xmlprovider标签下所配置的 StartupConfig

无关解析的局部都在 StartupInitializer 类中,通过它的 discoverAndInitialize() 办法就能获取到解析的数据。

internal fun discoverAndInitialize(context: Context): StartupProviderStore {TraceCompat.beginSection(StartupInitializer::class.java.simpleName)
 
    val result = mutableListOf<AndroidStartup<*>>()
    val initialize = mutableListOf<String>()
    val initialized = mutableListOf<String>()
    var config: StartupProviderConfig? = null
    try {val provider = ComponentName(context.packageName, StartupProvider::class.java.name)
        val providerInfo = context.packageManager.getProviderInfo(provider, PackageManager.GET_META_DATA)
        val startup = context.getString(R.string.android_startup)
        val providerConfig = context.getString(R.string.android_startup_provider_config)
        providerInfo.metaData?.let { metaData ->
            metaData.keySet().forEach { key ->
                val value = metaData[key]
                val clazz = Class.forName(key)
                if (startup == value) {if (AndroidStartup::class.java.isAssignableFrom(clazz)) {doInitialize((clazz.getDeclaredConstructor().newInstance() as AndroidStartup<*>), result, initialize, initialized)
                    }
                } else if (providerConfig == value) {if (StartupProviderConfig::class.java.isAssignableFrom(clazz)) {config = clazz.getDeclaredConstructor().newInstance() as? StartupProviderConfig
                        // save initialized config
                        StartupCacheManager.instance.saveConfig(config?.getConfig())
                    }
                }
            }
        }
    } catch (t: Throwable) {throw StartupException(t)
    }
 
    TraceCompat.endSection()
 
    return StartupProviderStore(result, config)
}

外围逻辑是:

  1. 通过 ComponentName() 获取指定的StartupProvider
  2. 通过 getProviderInfo() 获取对应 StartupProvider 下的 meta-data 数据
  3. 遍历 meta-data 数组
  4. 依据当时预约的 value 来匹配对应的name
  5. 最终通过反射来获取对应 name 的实例

其中在解析 Statup 的过程中,为了缩小 Statup 的配置,应用 doInitialize() 办法来主动创立依赖的Startup,并且提前对循环依赖进行查看。

依赖反对

/**
 * Returns a list of the other [Startup] objects that the initializer depends on.
 */
fun dependencies(): List<Class<out Startup<*>>>?

/**
 * Called whenever there is a dependency completion.
 *
 * @param [startup] dependencies [startup].
 * @param [result] of dependencies startup.
 */
fun onDependenciesCompleted(startup: Startup<*>, result: Any?)

某个初始化的组件在初始化之前所依赖的组件都必须通过 dependencies()进行申明。申明之后会在后续进行解析,保障依赖的组件优先执行结束;同时依赖的组件执行结束会回调 onDependenciesCompleted() 办法。执行程序则是通过有向图拓扑排序决定的。

闭环解决

无关闭环的解决,一方面会在主动配置环节的 doInitialize() 办法中会进行解决

private fun doInitialize(
    startup: AndroidStartup<*>,
    result: MutableList<AndroidStartup<*>>,
    initialize: MutableList<String>,
    initialized: MutableList<String>
) {
    try {val uniqueKey = startup::class.java.getUniqueKey()
        if (initialize.contains(uniqueKey)) {throw IllegalStateException("have circle dependencies.")
        }
        if (!initialized.contains(uniqueKey)) {initialize.add(uniqueKey)
            result.add(startup)
            startup.dependencies()?.forEach {doInitialize(it.getDeclaredConstructor().newInstance() as AndroidStartup<*>, result, initialize, initialized)
            }
            initialize.remove(uniqueKey)
            initialized.add(uniqueKey)
        }
    } catch (t: Throwable) {throw StartupException(t)
    }
}

将以后 Startup 退出到 initialize 中,同时遍历 dependencies() 依赖数组,递归调用doInitialize()

在递归的过程中,如果在 initialize 中存在对应的 uniqueKey(这里为 Startup 的惟一标识) 则代表发送的相互依赖,即存在依赖环。

另一方面,再后续的有向图拓扑排序优化也会进行环解决

fun sort(startupList: List<Startup<*>>): StartupSortStore {
    ...
 
    if (mainResult.size + ioResult.size != startupList.size) {throw StartupException("lack of dependencies or have circle dependencies.")
    }

}

在排序优化过程中会将在主线程执行与非主线程执行的 Startup 进行分类,再分类过程中并不会进行排重解决,只关注以后的 Startup 是否再主线程执行。所以最初只有这两种分类的大小之和不等于 Startup 的总和就代表存在环,即有相互依赖。

线程解决

线程方面,应用的是 StartupExecutor 接口,在 AndroidStartup 默认实现了它的接口办法createExecutor()

override fun createExecutor(): Executor = ExecutorManager.instance.ioExecutor

在 ExecutorManager 中提供了三种线程别离为

  1. cpuExecutor: cpu 应用频率高,高速计算;外围线程池的大小与 cpu 核数相干。
  2. ioExecutor: io 操作,网络解决等;外部应用缓存线程池。
  3. mainExecutor: 主线程。

所以如果须要批改默认线程,能够重写 createExecutor() 办法。

异步期待

在下面的依赖反对局部曾经提到应用 dependencies() 来设置依赖的组件。每一个初始化组件可能执行的前提是它本身的依赖组件全副曾经执行结束。

如果是同步依赖,天然很简略,只须要依照依赖的程序顺次执行即可。而对于异步依赖工作,则须要保障所有的异步依赖工作实现,以后组件能力失常执行。

Android Startup 借助了 CountDownLatch 来保障异步依赖的执行实现监听。

CountDownLatch字面意思就是倒计时锁,它是作用于线程中,初始化时会设置一个 count 大小的倒计时,通过 await() 来期待倒计时的完结,只不过倒计时的数值缩小是通过手动调用 countDown() 来触发的。

所以在抽象类 AndroidStartup 中,通过 await()countDown()来保障异步工作的精确执行。

abstract class AndroidStartup<T> : Startup<T> {private val mWaitCountDown by lazy { CountDownLatch(dependencies()?.size ?: 0) }
    private val mObservers by lazy {mutableListOf<Dispatcher>() }
 
    override fun toWait() {
        try {mWaitCountDown.await()
        } catch (e: InterruptedException) {e.printStackTrace()
        }
    }
 
    override fun toNotify() {mWaitCountDown.countDown()
    }
    ...
}

咱们通过 toWait() 办法来期待依赖组件的执行结束,而依赖的组件工作执行结束之后,通过 toNotify() 来告诉以后组件,一旦所有的依赖执行结束之后,就会开释以后的线程,使它继续执行上来。

toWait()toNotify()的具体调用机会别离在 StartupRunnable 与 StartupManagerDispatcher 中执行。

依赖回调

在依赖回调之前,先来意识一个接口ManagerDispatcher

interface ManagerDispatcher {
 
    /**
     * dispatch prepare
     */
    fun prepare()
 
    /**
     * dispatch startup to executing.
     */
    fun dispatch(startup: Startup<*>, sortStore: StartupSortStore)
 
    /**
     * notify children when dependency startup completed.
     */
    fun notifyChildren(dependencyParent: Startup<*>, result: Any?, sortStore: StartupSortStore)
}

ManagerDispatcher 中有三个接口办法,别离用来治理 Startup 的执行逻辑,保障执行前的筹备工作,执行过程中的散发与执行后的回调。所以依赖回调天然也在其中。

调用逻辑被封装到 notifyChildren() 办法中。最终调用 StartuponDependenciesCompleted()办法。

所以咱们能够在初始化组件中重写 onDependenciesCompleted() 办法,从而拿到所依赖的组件实现后返回的后果。例如 Sample 中的SampleSyncFourStartup

class SampleSyncFourStartup: AndroidStartup<String>() {
 
    private var mResult: String? = null
 
    override fun create(context: Context): String? {return "$mResult + sync four"}
 
    override fun callCreateOnMainThread(): Boolean = true
 
    override fun waitOnMainThread(): Boolean = false
 
    override fun dependencies(): List<Class<out Startup<*>>>? {return listOf(SampleAsyncTwoStartup::class.java)
    }
 
    override fun onDependenciesCompleted(startup: Startup<*>, result: Any?) {mResult = result as? String?}
}

当然这是在以后组件中获取依赖组件的返回后果,Android Startup还提供了在任意时候来查问任意组件的执行情况,并且反对获取任意曾经实现的组件的返回后果。

Android Startup提供 StartupCacheManager 来实现这些性能。具体应用形式能够通过查看 Sample 来获取。

手动告诉

下面介绍了依赖回调,它是主动调用依赖实现后的一系列操作。Android Startup也提供了手动告诉依赖工作的实现。

手动告诉的设置是通过 manualDispatch() 办法开启。它将配合 onDispatch() 一起实现。

ManagerDispatcher 接口具体实现类的 notifyChildren() 办法中,如果开启手动告诉,就不会走主动告诉流程,调用 toNotify() 办法,而是会将以后组件的 Dispatcher 增加到注册表中。期待 onDispatche() 的手动调用去唤醒 toNotify() 的执行。

override fun notifyChildren(dependencyParent: Startup<*>, result: Any?, sortStore: StartupSortStore) {
    // immediately notify main thread,Unblock the main thread.
    if (dependencyParent.waitOnMainThread()) {needAwaitCount.incrementAndGet()
        awaitCountDownLatch?.countDown()}
 
     sortStore.startupChildrenMap[dependencyParent::class.java.getUniqueKey()]?.forEach {sortStore.startupMap[it]?.run {onDependenciesCompleted(dependencyParent, result)
 
            if (dependencyParent.manualDispatch()) {dependencyParent.registerDispatcher(this)
            } else {toNotify()
            }
        }
    }
    ...
}

具体实现示例能够查看 SampleManualDispatchStartup

拓扑优化

Android Startup中初始化组件与组件间的关系其实就是一张有向无环拓扑图。

Sample 中的一个 demo 为例:

咱们将每一个 Startup 的边指向指标为一个入度。依据这个规定很容易算出这四个 Startup 的入度

  1. SampleFirstStartup: 0
  2. SampleSecondStartup: 1
  3. SampleThirdStartup: 2
  4. SampleFourthStartup: 3

那么这个入度有什么用呢?依据由 AOV 网结构拓扑序列的拓扑排序算法次要是循环执行以下两步,直到不存在入度为 0 的顶点为止。

  1. 抉择一个入度为 0 的顶点并输入之;
  2. 从网中删除此顶点及所有出边

循环完结后,若输入的顶点数小于网中的顶点数,则输入“有回路”信息,否则输入的顶点序列就是一种拓扑序列。

依据下面的步骤,能够得出下面的四个 Startup 的输入程序为

SampleFirstStartup -> SampleSecondStartup -> SampleThirdStartup -> SampleFourthStartup

以上的输入程序也是初始化组件间的执行程序。这样即保障了依赖组件间的失常执行,也保障了初始化组件的执行程序的最优解,即依赖组件间的等待工夫最短,同时也查看了依赖组件间是否存在环。

既然曾经有了计划与实现步骤,上面要做的就是用代码实现进去。

fun sort(startupList: List<Startup<*>>): StartupSortStore {TraceCompat.beginSection(TopologySort::class.java.simpleName)

    val mainResult = mutableListOf<Startup<*>>()
    val ioResult = mutableListOf<Startup<*>>()
    val temp = mutableListOf<Startup<*>>()
    val startupMap = hashMapOf<String, Startup<*>>()
    val zeroDeque = ArrayDeque<String>()
    val startupChildrenMap = hashMapOf<String, MutableList<String>>()
    val inDegreeMap = hashMapOf<String, Int>()

    startupList.forEach {val uniqueKey = it::class.java.getUniqueKey()
        if (!startupMap.containsKey(uniqueKey)) {startupMap[uniqueKey] = it
            // save in-degree
            inDegreeMap[uniqueKey] = it.dependencies()?.size ?: 0
            if (it.dependencies().isNullOrEmpty()) {zeroDeque.offer(uniqueKey)
            } else {
                // add key parent, value list children
                it.dependencies()?.forEach { parent ->
                    val parentUniqueKey = parent.getUniqueKey()
                    if (startupChildrenMap[parentUniqueKey] == null) {startupChildrenMap[parentUniqueKey] = arrayListOf()}
                    startupChildrenMap[parentUniqueKey]?.add(uniqueKey)
                }
            }
        } else {throw StartupException("$it multiple add.")
        }
    }

    while (!zeroDeque.isEmpty()) {zeroDeque.poll()?.let {startupMap[it]?.let { androidStartup ->
                temp.add(androidStartup)
                // add zero in-degree to result list
                if (androidStartup.callCreateOnMainThread()) {mainResult.add(androidStartup)
                } else {ioResult.add(androidStartup)
                }
            }
            startupChildrenMap[it]?.forEach { children ->
                inDegreeMap[children] = inDegreeMap[children]?.minus(1) ?: 0
                // add zero in-degree to deque
                if (inDegreeMap[children] == 0) {zeroDeque.offer(children)
                }
            }
        }
    }

    if (mainResult.size + ioResult.size != startupList.size) {throw StartupException("lack of dependencies or have circle dependencies.")
    }

    val result = mutableListOf<Startup<*>>().apply {addAll(ioResult)
        addAll(mainResult)
    }
    printResult(temp)

    TraceCompat.endSection()

    return StartupSortStore(
        result,
        startupMap,
        startupChildrenMap
    )
}

有了下面的步骤,置信这段代码都可能了解。

除了下面所介绍的性能,Android Startup还反对 Systrace 插桩,为用户提供系统分析初始化的耗时具体过程;初始化组件的精确耗时收集统计,不便用户下载与上传到指定服务器等等。

Android Startup的外围功能分析临时就到这里完结了,心愿可能对你有所帮忙。

当然,自己真挚的邀请你退出 Android Startup 的建设中,如果你有什么好的倡议也请不吝赐教。

我的项目

android_startup: 提供一种在利用启动时可能更加简略、高效的形式来初始化组件,优化启动速度。

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

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

android-api-analysis: 联合具体的 Demo 来全面解析 Android 相干的知识点, 帮忙读者可能更快的把握与了解所论述的要点。

daily_algorithm: 每日一算法,由浅入深,欢送退出一起共勉。

为本人代言

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

退出移动版