前言
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
来实现主动配置的,在Android
中ContentProvider
的初始化机会介于Application
的attachBaseContext
与onCreate
之间。所以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.xml
的provider
标签下所配置的Startup
与Config
。
无关解析的局部都在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) }
外围逻辑是:
- 通过
ComponentName()
获取指定的StartupProvider
- 通过
getProviderInfo()
获取对应StartupProvider
下的meta-data
数据 - 遍历
meta-data
数组 - 依据当时预约的
value
来匹配对应的name
- 最终通过反射来获取对应
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中提供了三种线程别离为
cpuExecutor
: cpu应用频率高,高速计算;外围线程池的大小与cpu核数相干。ioExecutor
: io操作,网络解决等;外部应用缓存线程池。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()
办法中。最终调用Startup
的onDependenciesCompleted()
办法。
所以咱们能够在初始化组件中重写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
的入度
SampleFirstStartup
: 0SampleSecondStartup
: 1SampleThirdStartup
: 2SampleFourthStartup
: 3
那么这个入度有什么用呢?依据由AOV网结构拓扑序列的拓扑排序算法次要是循环执行以下两步,直到不存在入度为0的顶点为止。
- 抉择一个入度为0的顶点并输入之;
- 从网中删除此顶点及所有出边
循环完结后,若输入的顶点数小于网中的顶点数,则输入“有回路”信息,否则输入的顶点序列就是一种拓扑序列。
依据下面的步骤,能够得出下面的四个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补给站】或者扫描下方二维码进行关注