目录
一、启动类型概述
1.1 三种启动类型
类型
定义
特点
冷启动
进程从 0 开始创建
耗时最长,重点优化
温启动
进程已存在,Activity 重建
耗时中等
热启动
Activity 从后台切前台
耗时最短
1.2 冷启动完整流程
scss
复制代码
用户点击 Launcher
↓
ActivityManagerService 处理 Intent
↓
Zygote 进程 fork 新进程
↓
Application.attachBaseContext()
↓
Application.onCreate()
↓
Activity.onCreate()
↓
Activity.onStart()
↓
Activity.onResume()
↓
Choreographer 第一次回调
↓
首帧绘制完成(用户可见)
二、指标体系建立
2.1 核心指标
指标
定义
优秀值
合格值
冷启动总耗时
点击到首帧
< 800ms
< 1500ms
Application 初始化
attachBaseContext 到 onCreate 结束
< 200ms
< 400ms
Activity 创建
onCreate 到 onResume 结束
< 300ms
< 500ms
首帧绘制
onResume 到首帧
< 100ms
< 200ms
完全可交互
核心数据加载完成
< 1200ms
< 2500ms
2.2 线上分位统计
分位
目标值
P50
< 800ms
P90
< 1200ms
P95
< 1500ms
P99
< 2500ms
2.3 指标监控维度
设备品牌、型号
Android 版本
应用版本
RAM 大小
网络环境
三、各阶段优化方案
3.1 进程创建阶段
优化点
优化项
方案
类加载优化
主 dex 只放启动必须的类
MultiDex
主 dex 精准配置
JIT 禁用
Android N+ 可禁用 JIT 加速启动
避免反射
启动期避免使用反射
代码示例:禁用 JIT
xml
复制代码
<!-- AndroidManifest.xml -->
<application
android:vmSafeMode="true">
</application>
3.2 Application 初始化阶段
优化策略
区分进程 :只在主进程初始化
必要/非必要分离 :必要任务同步,非必要异步
延迟初始化 :使用 IdleHandler 在空闲时执行
任务启动器 :用专门的任务启动器管理
代码示例:进程区分
kotlin
复制代码
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (isMainProcess()) {
initEssential()
delayInitNonEssential()
}
}
private fun isMainProcess(): Boolean {
val processName = getProcessName(this)
return processName == packageName
}
private fun getProcessName(context: Context): String {
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
return am?.runningAppProcesses?.find { it.pid == Process.myPid() }?.processName.orEmpty()
}
}
代码示例:IdleHandler 延迟初始化
kotlin
复制代码
Handler(Looper.getMainLooper()).postIdle {
initPush()
initMap()
initAnalytics()
false // 只执行一次
}
3.3 Activity 创建阶段
优化策略
布局优化 :减少层级、使用 merge/include
异步加载 :AsyncLayoutInflater 异步 inflate
ViewStub :延迟加载非必须 View
数据预加载 :提前加载首页数据
代码示例:布局优化
xml
复制代码
<!-- 使用 merge 减少层级 -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<include layout="@layout/header" />
<include layout="@layout/content" />
</merge>
xml
复制代码
<!-- 使用 ViewStub 延迟加载 -->
<ViewStub
android:id="@+id/stub_detail"
android:layout="@layout/layout_detail"
android:inflatedId="@+id/fl_detail" />
代码示例:异步加载布局
kotlin
复制代码
AsyncLayoutInflater(this).inflate(
R.layout.activity_main,
null
) { view, _, _ ->
setContentView(view)
initViews(view)
}
3.4 首帧绘制阶段
优化策略
SplashScreen API :使用官方启动页
消除黑白屏 :设置 windowBackground
过度绘制 :减少不必要的背景
简化首屏 :首屏只显示必要内容
代码示例:SplashScreen API
xml
复制代码
<!-- values/themes.xml -->
<style name="Theme.Launcher" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/white</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
kotlin
复制代码
// Activity 中
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition {
!viewModel.isDataReady.value
}
}
四、MultiDex 优化
4.1 主 Dex 配置方式
AGP 8.0+
kotlin
复制代码
// app/build.gradle.kts
android {
defaultConfig {
multiDexEnabled = true
}
buildTypes {
release {
multiDexKeepProguard = file("multidex-keep.pro")
}
}
}
AGP 7.x 及以下
groovy
复制代码
// app/build.gradle
android {
defaultConfig {
multiDexEnabled true
}
buildTypes {
release {
multiDexKeepProguard file('multidex-keep.pro')
}
}
}
proguard
复制代码
# 核心启动类
-keep class com.yourapp.MyApplication { *; }
-keep class com.yourapp.MainActivity { *; }
-keep class com.yourapp.SplashActivity { *; }
-keep class com.yourapp.base.BaseActivity { *; }
# 基础工具类
-keep class com.yourapp.utils.AppUtils { *; }
-keep class com.yourapp.constants.* { *; }
# 启动必须的第三方 SDK
-keep class com.tencent.bugly.** { *; }
-keep class com.umeng.analytics.** { *; }
# ContentProvider
-keep class com.yourapp.provider.** { *; }
# 保留注解
-keep class androidx.annotation.Keep
-keep @androidx.annotation.Keep class * {*;}
4.3 验证主 Dex 配置
方法 1:APK Analyzer
Android Studio → Build → Analyze APK
选择 APK 文件
查看 classes.dex 的内容
方法 2:命令行
bash
复制代码
# 使用 build-tools 中的 dexdump
DEXDUMP=$ANDROID_HOME/build-tools/34.0.0/dexdump
unzip -p app-release.apk classes.dex > classes.dex
$DEXDUMP classes.dex | grep "Class descriptor"
4.4 常见问题
问题
解决方案
ClassNotFoundException
在 multidex-keep.pro 中添加该类
主 Dex 方法超限
进一步精简,延迟初始化更多 SDK
ContentProvider 初始化失败
确保 Provider 在主 Dex 中
五、任务启动器
5.1 架构设计
scss
复制代码
TaskLauncher (入口)
↓
TaskManager (任务管理)
↓
TaskDispatcher (任务分发)
├─ 主线程 Executor
├─ IO 线程池
├─ CPU 密集线程池
└─ 单线程 Executor
5.2 核心代码
Task.kt - 任务定义
kotlin
复制代码
package com.yourapp.launcher
import android.os.Process
import android.os.SystemClock
import androidx.annotation.IntRange
const val PRIORITY_LOWEST = -20
const val PRIORITY_LOW = -10
const val PRIORITY_DEFAULT = 0
const val PRIORITY_HIGH = 10
const val PRIORITY_HIGHEST = 20
enum class ThreadType {
MAIN, IO, CPU, SINGLE
}
abstract class Task {
open val name: String = this::class.java.simpleName
open val priority: Int = PRIORITY_DEFAULT
open val threadType: ThreadType = ThreadType.IO
open val needWait: Boolean = false
open val dependencies: List<Class<out Task>> = emptyList()
open val timeoutMillis: Long = 3000L
open val isEnabled: Boolean = true
internal val isFinished = java.util.concurrent.atomic.AtomicBoolean(false)
internal val isRunning = java.util.concurrent.atomic.AtomicBoolean(false)
internal val latch = java.util.concurrent.CountDownLatch(1)
abstract fun run()
open fun onStart() {}
open fun onSuccess() {}
open fun onError(e: Throwable) {}
open fun onTimeout() {}
internal fun execute() {
if (!isEnabled) {
finish()
return
}
if (!isRunning.compareAndSet(false, true)) return
onStart()
val startTime = SystemClock.uptimeMillis()
try {
when (threadType) {
ThreadType.MAIN -> Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT)
ThreadType.IO -> Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
ThreadType.CPU -> Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT)
ThreadType.SINGLE -> Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
}
run()
onSuccess()
} catch (e: Throwable) {
onError(e)
TaskLauncher.monitor?.onTaskError(this, e)
} finally {
val endTime = SystemClock.uptimeMillis()
TaskLauncher.monitor?.onTaskComplete(this, endTime - startTime)
finish()
}
}
internal fun waitFinish() {
try {
if (!latch.await(timeoutMillis, java.util.concurrent.TimeUnit.MILLISECONDS)) {
onTimeout()
TaskLauncher.monitor?.onTaskTimeout(this)
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
}
}
private fun finish() {
isFinished.set(true)
latch.countDown()
}
}
// DSL 快速创建
inline fun createTask(
name: String,
priority: Int = PRIORITY_DEFAULT,
threadType: ThreadType = ThreadType.IO,
needWait: Boolean = false,
dependencies: List<Class<out Task>> = emptyList(),
crossinline block: () -> Unit
): Task = object : Task() {
override val name: String = name
override val priority: Int = priority
override val threadType: ThreadType = threadType
override val needWait: Boolean = needWait
override val dependencies: List<Class<out Task>> = dependencies
override fun run() = block()
}
TaskDispatcher.kt - 任务分发器
kotlin
复制代码
package com.yourapp.launcher
import android.os.Handler
import android.os.Looper
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicInteger
internal class TaskDispatcher {
companion object {
private const val CPU_COUNT = 4
private const val CORE_POOL_SIZE = CPU_COUNT + 1
private const val MAX_POOL_SIZE = CPU_COUNT * 2 + 1
private const val KEEP_ALIVE_SECONDS = 30L
}
private val mainHandler = Handler(Looper.getMainLooper())
private val ioExecutor: ThreadPoolExecutor = ThreadPoolExecutor(
CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
LinkedBlockingQueue(), TaskThreadFactory("launcher-io-"),
ThreadPoolExecutor.CallerRunsPolicy
)
private val cpuExecutor: ThreadPoolExecutor = ThreadPoolExecutor(
CPU_COUNT, CPU_COUNT, 0L, TimeUnit.MILLISECONDS,
LinkedBlockingQueue(), TaskThreadFactory("launcher-cpu-"),
ThreadPoolExecutor.CallerRunsPolicy
)
private val singleExecutor: ExecutorService = Executors.newSingleThreadExecutor(
TaskThreadFactory("launcher-single-")
)
init {
ioExecutor.allowCoreThreadTimeOut(true)
}
fun dispatch(task: Task) {
when (task.threadType) {
ThreadType.MAIN -> {
if (isMainThread()) task.execute()
else mainHandler.post { task.execute() }
}
ThreadType.IO -> ioExecutor.execute { task.execute() }
ThreadType.CPU -> cpuExecutor.execute { task.execute() }
ThreadType.SINGLE -> singleExecutor.execute { task.execute() }
}
}
fun postToMain(runnable: Runnable) = mainHandler.post(runnable)
fun isMainThread(): Boolean = Looper.myLooper() == Looper.getMainLooper()
fun shutdown() {
ioExecutor.shutdown()
cpuExecutor.shutdown()
singleExecutor.shutdown()
}
private class TaskThreadFactory(private val namePrefix: String) : ThreadFactory {
private val threadNumber = AtomicInteger(1)
override fun newThread(r: Runnable): Thread {
return Thread(r, namePrefix + threadNumber.getAndIncrement()).apply {
isDaemon = true
}
}
}
}
TaskManager.kt - 任务管理器
kotlin
复制代码
package com.yourapp.launcher
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.LinkedBlockingQueue
internal class TaskManager(private val dispatcher: TaskDispatcher) {
private val allTasks = CopyOnWriteArrayList<Task>()
private val taskMap = ConcurrentHashMap<Class<out Task>, Task>()
private val inDegreeMap = ConcurrentHashMap<Class<out Task>, Int>()
private val adjacencyMap = ConcurrentHashMap<Class<out Task>, MutableList<Class<out Task>>>()
private val zeroDegreeQueue = LinkedBlockingQueue<Task>()
private val needWaitTasks = CopyOnWriteArrayList<Task>()
fun addTask(task: Task) {
if (!task.isEnabled || taskMap.containsKey(task::class.java)) return
allTasks.add(task)
taskMap[task::class.java] = task
if (task.needWait) needWaitTasks.add(task)
inDegreeMap[task::class.java] = task.dependencies.size
if (task.dependencies.isEmpty()) {
zeroDegreeQueue.offer(task)
} else {
task.dependencies.forEach { depClass ->
adjacencyMap.getOrPut(depClass) { mutableListOf() }.add(task::class.java)
}
}
}
fun addTasks(tasks: List<Task>) {
tasks.forEach { addTask(it) }
val sorted = zeroDegreeQueue.sortedByDescending { it.priority }
zeroDegreeQueue.clear()
zeroDegreeQueue.addAll(sorted)
}
fun start() {
while (zeroDegreeQueue.isNotEmpty()) {
val task = zeroDegreeQueue.poll() ?: break
dispatchTask(task)
}
}
fun await() = needWaitTasks.forEach { it.waitFinish() }
fun onTaskFinish(task: Task) {
val dependents = adjacencyMap[task::class.java] ?: return
dependents.forEach { dependentClass ->
val inDegree = inDegreeMap[dependentClass] ?: return@forEach
val newInDegree = inDegree - 1
inDegreeMap[dependentClass] = newInDegree
if (newInDegree == 0) {
taskMap[dependentClass]?.let { dependentTask ->
zeroDegreeQueue.offer(dependentTask)
dispatchTask(dependentTask)
}
}
}
}
private fun dispatchTask(task: Task) = dispatcher.dispatch(task)
fun getAllTasks(): List<Task> = allTasks.toList()
}
TaskLauncher.kt - 启动器入口
kotlin
复制代码
package com.yourapp.launcher
import android.content.Context
import android.os.SystemClock
object TaskLauncher {
interface Monitor {
fun onStart()
fun onTaskStart(task: Task)
fun onTaskComplete(task: Task, duration: Long)
fun onTaskError(task: Task, error: Throwable)
fun onTaskTimeout(task: Task)
fun onFinish(totalDuration: Long)
}
private class EmptyMonitor : Monitor {
override fun onStart() {}
override fun onTaskStart(task: Task) {}
override fun onTaskComplete(task: Task, duration: Long) {}
override fun onTaskError(task: Task, error: Throwable) {}
override fun onTaskTimeout(task: Task) {}
override fun onFinish(totalDuration: Long) {}
}
var monitor: Monitor = EmptyMonitor()
private set
private var isInitialized = false
private lateinit var dispatcher: TaskDispatcher
private lateinit var manager: TaskManager
private var startTime: Long = 0
fun init(context: Context) {
if (isInitialized) return
dispatcher = TaskDispatcher()
manager = TaskManager(dispatcher)
isInitialized = true
}
fun setMonitor(monitor: Monitor) {
this.monitor = monitor
}
fun addTask(task: Task): TaskLauncher {
checkInitialized()
manager.addTask(task)
return this
}
fun addTasks(tasks: List<Task>): TaskLauncher {
checkInitialized()
manager.addTasks(tasks)
return this
}
fun start() {
checkInitialized()
startTime = SystemClock.uptimeMillis()
monitor.onStart()
manager.start()
}
fun await() {
checkInitialized()
manager.await()
val totalDuration = SystemClock.uptimeMillis() - startTime
monitor.onFinish(totalDuration)
}
fun startAndAwait() {
start()
await()
}
fun shutdown() {
if (::dispatcher.isInitialized) dispatcher.shutdown()
isInitialized = false
}
private fun checkInitialized() {
check(isInitialized) { "TaskLauncher not initialized, call init() first" }
}
}
5.3 实际使用示例
定义初始化任务
kotlin
复制代码
package com.yourapp.init
import android.content.Context
import com.yourapp.launcher.*
class InitBuglyTask(private val context: Context) : Task() {
override val name: String = "InitBugly"
override val priority: Int = PRIORITY_HIGHEST
override val threadType: ThreadType = ThreadType.MAIN
override val needWait: Boolean = true
override fun run() {
// Bugly.init(context, "YOUR_APP_ID", BuildConfig.DEBUG)
Thread.sleep(50)
}
}
class InitNetworkTask : Task() {
override val name: String = "InitNetwork"
override val priority: Int = PRIORITY_HIGH
override val threadType: ThreadType = ThreadType.IO
override val needWait: Boolean = false
override fun run() {
Thread.sleep(100)
}
}
class InitDatabaseTask : Task() {
override val name: String = "InitDatabase"
override val priority: Int = PRIORITY_HIGH
override val threadType: ThreadType = ThreadType.IO
override val needWait: Boolean = false
override fun run() {
Thread.sleep(120)
}
}
class PreCacheTask : Task() {
override val name: String = "PreCache"
override val priority: Int = PRIORITY_HIGH
override val threadType: ThreadType = ThreadType.IO
override val needWait: Boolean = false
override val dependencies: List<Class<out Task>> = listOf(InitDatabaseTask::class.java)
override fun run() {
Thread.sleep(80)
}
}
Application 中集成
kotlin
复制代码
package com.yourapp
import android.app.Application
import android.os.Handler
import android.os.Looper
import com.yourapp.init.*
import com.yourapp.launcher.*
class MyApplication : Application() {
private val handler = Handler(Looper.getMainLooper())
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
TaskLauncher.init(this)
TaskLauncher.setMonitor(LaunchMonitor)
}
override fun onCreate() {
super.onCreate()
if (isMainProcess()) {
val criticalTasks = listOf(
InitBuglyTask(this),
InitConfigTask(this)
)
val asyncTasks = listOf(
InitNetworkTask(),
InitImageLoaderTask(),
InitDatabaseTask(),
InitPushTask(),
InitMapTask(),
PreCacheTask()
)
val delayTasks = listOf(
InitAnalyticsTask(),
InitShareTask(),
InitPayTask()
)
TaskLauncher
.addTasks(criticalTasks)
.addTasks(asyncTasks)
.startAndAwait()
handler.post {
TaskLauncher.addTasks(delayTasks).start()
}
}
}
private fun isMainProcess(): Boolean {
val processName = getProcessName(this)
return processName == packageName
}
private fun getProcessName(context: Context): String {
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as? android.app.ActivityManager
return am?.runningAppProcesses?.find { it.pid == android.os.Process.myPid() }?.processName.orEmpty()
}
}
监控器实现
kotlin
复制代码
package com.yourapp
import android.util.Log
import com.yourapp.launcher.Task
import com.yourapp.launcher.TaskLauncher
object LaunchMonitor : TaskLauncher.Monitor {
private const val TAG = "LaunchMonitor"
private val taskTimes = mutableMapOf<String, Long>()
override fun onStart() {
Log.d(TAG, "========== 任务启动器开始 ==========")
}
override fun onTaskStart(task: Task) {
Log.d(TAG, "任务开始: ${task.name}")
}
override fun onTaskComplete(task: Task, duration: Long) {
Log.d(TAG, "任务完成: ${task.name}, 耗时: ${duration}ms")
taskTimes[task.name] = duration
}
override fun onTaskError(task: Task, error: Throwable) {
Log.e(TAG, "任务错误: ${task.name}", error)
}
override fun onTaskTimeout(task: Task) {
Log.w(TAG, "任务超时: ${task.name}")
}
override fun onFinish(totalDuration: Long) {
Log.d(TAG, "========== 任务启动器结束 ==========")
Log.d(TAG, "总耗时: ${totalDuration}ms")
Log.d(TAG, "任务耗时排行榜:")
taskTimes.entries
.sortedByDescending { it.value }
.forEach { (name, time) ->
Log.d(TAG, " $name: ${time}ms")
}
}
}
5.4 优化效果对比
方式
总耗时
说明
传统串行
630ms
Bugly(50) + Config(30) + Network(100) + ...
任务启动器
80ms
关键任务(80ms) + 后台并行
优化幅度:约 87%
六、监控与验证
6.1 线下监控工具
命令行快速测量
bash
复制代码
# 测量单次启动
adb shell am start -W -S com.yourapp/.MainActivity
# 自动化测量 10 次
for i in {1..10}; do
adb shell am force-stop com.yourapp
sleep 2
adb shell am start -W -S com.yourapp/.MainActivity | grep TotalTime >> launch_times.txt
sleep 3
done
# 计算平均值
awk '{sum+=$2} END {print "平均: " sum/NR "ms"}' launch_times.txt
Systrace / Perfetto
bash
复制代码
# Systrace
python systrace.py -a com.yourapp -t 10 -o launch_trace.html view am wm sched freq idle dalvik disk input
# Perfetto(推荐)
adb shell perfetto --out /data/local/tmp/perfetto_trace -c config.txt
adb pull /data/local/tmp/perfetto_trace
Android Studio Profiler
CPU Profiler:查看主线程执行情况
Memory Profiler:查看内存分配
Systrace:集成在 Profiler 中
6.2 线上监控方案
数据上报结构
kotlin
复制代码
data class LaunchEvent(
val launchType: String,
val appVersion: String,
val osVersion: String,
val deviceBrand: String,
val deviceModel: String,
val totalDuration: Long,
val attachDuration: Long,
val appCreateDuration: Long,
val activityCreateDuration: Long,
val firstFrameDuration: Long,
val coreDataDuration: Long,
val isUpgrade: Boolean,
val freeMemory: Long,
val batteryLevel: Int,
val timestamp: Long
)
抽样上报
kotlin
复制代码
object PerformanceReporter {
private const val SAMPLING_RATE = 0.1
fun reportLaunch(event: LaunchEvent) {
if (Random.nextDouble() > SAMPLING_RATE && !BuildConfig.DEBUG) {
return
}
reportToPlatform(event)
}
}
告警配置
告警级别
触发条件
处理方式
严重
P95 > 2000ms
通知研发群 + 自动提单
警告
P95 > 1500ms
周会重点关注
6.3 代码埋点完整方案
kotlin
复制代码
object LaunchMetrics {
private var processStart: Long = 0
private var attachBaseContextEnd: Long = 0
private var applicationCreateEnd: Long = 0
private var firstFrameTime: Long = 0
fun onAttachBaseContextStart() {
processStart = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Process.getStartUptimeMillis()
} else {
SystemClock.uptimeMillis()
}
Trace.beginSection("Launch_AttachBaseContext")
}
fun onAttachBaseContextEnd() {
attachBaseContextEnd = SystemClock.uptimeMillis()
Trace.endSection()
}
fun onApplicationCreateEnd() {
applicationCreateEnd = SystemClock.uptimeMillis()
Trace.endSection()
}
fun registerFirstFrameListener(view: View) {
view.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
firstFrameTime = SystemClock.uptimeMillis()
view.viewTreeObserver.removeOnPreDrawListener(this)
reportFullLaunchMetrics()
return true
}
})
}
private fun reportFullLaunchMetrics() {
val total = firstFrameTime - processStart
Log.d("LaunchMetrics", "总耗时: ${total}ms")
}
}
七、总结与最佳实践
7.1 优化 Checklist
7.2 优化优先级
P0 - 核心优化 :Application 初始化优化、任务启动器
P1 - 重要优化 :布局优化、首帧加速
P2 - 一般优化 :MultiDex 优化、细节调优
7.3 持续优化流程
markdown
复制代码
1. 建立基准线
↓
2. 实施优化
↓
3. 验证效果(线下 + 线上)
↓
4. 监控告警
↓
5. 发现劣化
↓
6. 回归步骤 2
7.4 常见误区
误区
正确做法
所有任务都放后台
必须在主线程的任务(如 Bugly)必须放主线程
过度优化主 dex
平衡启动速度和运行时性能
只看 P50 不看 P95
P95/P99 更影响用户体验
只优化不监控
建立完整的监控告警体系
附录
A. 完整目录结构
css
复制代码
app/src/main/java/com/yourapp/
├── MyApplication.kt
├── LaunchMonitor.kt
├── init/
│ ├── InitBuglyTask.kt
│ ├── InitConfigTask.kt
│ ├── InitNetworkTask.kt
│ ├── InitDatabaseTask.kt
│ └── ...
└── launcher/
├── Task.kt
├── TaskDispatcher.kt
├── TaskManager.kt
└── TaskLauncher.kt
B. 相关文档