• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

Android-Startup实现分析

android 搞代码 3年前 (2022-03-02) 29次浏览 已收录 0个评论
文章目录[隐藏]

前言

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补给站】或者扫描下方二维码进行关注


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Android-Startup实现分析

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址