Android Architecture Templates 架构解析:从 v1.0 到 v1.1.0,如何将 14 个模块全量迁移到 Jetpack Compose

16 阅读8分钟

从 XML + ViewBinding 到纯 Compose,APK 从 19.55MB 降至 9.59MB甚至以下,冷启动稳定在 380ms


一、写在前面

去年年底,我开源了一套 Android 基础架构框架 Android Architecture Templates,它基于 MVVM + 模块化分层架构(Core/Service/Middleware/Features),涵盖了网络、存储、路由、启动、崩溃、日志、埋点等全套基建能力。

开源后收到了不少开发者的反馈和 Star,但也有一个声音反复出现:“为什么还在用 XML + ViewBinding?能不能迁移到 Compose?”

于是,我决定把这个框架全量迁移到 Jetpack Compose。经过两周的集中开发,v1.1.0-compose 版本正式发布。

这篇文章,我会完整记录这次迁移的技术方案、踩坑经历和最终收益


二、迁移前的项目状态

2.1 架构概览

在 v1.0 版本中,项目采用 MVVM + 模块化分层架构,共 14 个模块:

app (壳工程)
    ↓
features (业务功能层) —— feature_login 登录模块
    ↓
middleware (中间件层) —— router + permission
    ↓
service (基础服务层) —— startup + crash + logger
    ↓
core (核心基建层) —— base/network/storage/cache/common/ui

这套架构的核心设计理念是:依赖方向严格向下features → middleware → service → core,确保代码高内聚、低耦合。

2.2 核心指标(v1.0)

指标数值
冷启动耗时~450ms
Release APK 大小19.55MB(优化后 9.59MB)
UI 框架XML + ViewBinding
模块数量14 个
代码行数~5000+ 行

2.3 为什么决定迁移?

技术债务积累:随着 Compose 生态的成熟,继续维护 XML + ViewBinding 意味着:

  • 新功能开发效率低(XML + 绑定代码 + 状态管理三处修改)
  • 难以复用 Compose 生态的成熟库(如 Navigation Compose、 Accompanist 等)
  • 团队新成员更倾向于学习 Compose,而非维护旧技术栈

Google 官方推荐:增量迁移是官方推荐的最佳实践,让 Compose 和 View 在代码库中共存,直到应用完全迁移至 Compose。而我的项目页面数量很少(Splash、Login、Dashboard),正是全量迁移的最佳时机


三、迁移方案设计

3.1 迁移范围

类型迁移前迁移后
UI 框架XML + ViewBindingJetpack Compose
Activity 基类BaseActivity<VB, VM>BaseComposeActivity<State, Event, VM>
FragmentBaseFragment<VB, VM>已移除(全部改用 Compose Screen)
主题系统themes.xml + attrs.xmlCompose MaterialTheme + StateFlow 驱动
主题切换AppCompatDelegateStateFlow 响应式切换,无需 recreate
依赖库appcompat / material / fragment-ktx / viewpager2全部移除

3.2 核心原则

  1. 基建层完全不动core/networkcore/storagecore/common 等纯逻辑层 0 修改
  2. 业务逻辑层保持不变ViewModelRepository 继续使用,仅替换 UI 渲染层
  3. 渐进式清理:先让 Compose 跑起来,再逐步移除 View 体系残留

四、迁移实战:关键步骤与踩坑记录

4.1 第一步:Gradle 依赖配置

踩坑:Kotlin 版本与 Compose Compiler 版本必须严格匹配。

我的项目使用 Kotlin 2.1.20,对应的 Compose Compiler 版本是 2.1.20(Kotlin Gradle Plugin 内置)。

# gradle/libs.versions.toml
[versions]
kotlin = "2.1.20"
compose-bom = "2025.08.00"

[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

在模块中应用 Compose Compiler 插件后,不再需要 composeOptions { kotlinCompilerExtensionVersion } 配置。

4.2 第二步:创建 Compose 主题系统

原有的 XML 主题(themes.xmlattrs.xmlcolors.xml)需要迁移到 Compose。

关键设计:主题切换使用 StateFlow 驱动,而非 AppCompatDelegate

// ThemeManager.kt
object ThemeManager {
    private val _isDark = MutableStateFlow(KVStorage.getBoolean(KEY_DARK_MODE, false))
    val isDark: StateFlow<Boolean> = _isDark.asStateFlow()
    
    fun toggleTheme() {
        _isDark.update { !it }
        KVStorage.putBoolean(KEY_DARK_MODE, _isDark.value)
    }
}

// AppTheme.kt
@Composable
fun AppTheme(content: @Composable () -> Unit) {
    val isDark by ThemeManager.isDark.collectAsStateWithLifecycle()
    val colorScheme = if (isDark) DarkColorScheme else LightColorScheme
    MaterialTheme(colorScheme = colorScheme, content = content)
}

效果:切换主题时不重建 Activity,UI 自动重组,丝滑无闪烁。

4.3 第三步:BaseComposeActivity 设计

原有的 BaseActivity 依赖 ViewBinding,需要替换为纯 Compose 版本:

abstract class BaseComposeActivity<State, Event : ViewEvent, VM : BaseViewModel<State, Event>> : ComponentActivity() {
    protected abstract val viewModel: VM

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        observeEvents()
        setContent {
            AppTheme {
                // 占位 + 内容平滑过渡
                Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background))
                LaunchedEffect(Unit) {
                    yield() // 等待一帧,确保 Compose 完全初始化
                }
                ScreenContent()
            }
        }
    }

    @Composable
    protected abstract fun ScreenContent()
}

4.4 第四步:Splash 闪屏页迁移(消除白屏)

问题:闪屏 Overlay 消失后,Compose 内容还未渲染完成,出现短暂白屏。

解决方案:使用 installSplashScreen() + setKeepOnScreenCondition 保持 Overlay,同时在 setContent 中立即渲染品牌色占位。

class SplashActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val splashScreen = installSplashScreen()
        var composeReady by mutableStateOf(false)
        splashScreen.setKeepOnScreenCondition { !composeReady }

        super.onCreate(savedInstanceState)
        setContent {
            AppTheme {
                // 立即渲染品牌色占位
                Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.primary))
                LaunchedEffect(Unit) {
                    // 执行启动任务...
                    composeReady = true
                    navigateToMain()
                }
            }
        }
    }
}

4.5 第五步:页面迁移(Splash → Login → Dashboard)

迁移前:3 个 Activity + 3 个 XML 布局文件 + 3 个 ViewBinding 类

迁移后:3 个 @Composable Screen 函数 + 3 个继承 BaseComposeActivity 的 Activity

以 Login 页面为例:

@Composable
fun LoginScreen(viewModel: LoginViewModel = viewModel()) {
    val state by viewModel.state.collectAsState()
    
    Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
        OutlinedTextField(
            value = state.phone,
            onValueChange = viewModel::onPhoneChanged,
            label = { Text("手机号") }
        )
        Button(
            onClick = viewModel::login,
            enabled = state.isLoginEnabled
        ) {
            Text("登录")
        }
    }
}

代码量对比:XML + Activity + ViewBinding 约 200 行 → Compose Screen 约 80 行,减少 60%

4.6 第六步:清理 View 体系残留

迁移完成后,执行全量清理:

清理项数量
删除 View 组件类12 个(badge/dialog/loading/snackbar/state/toast/transition/veil/widget 等)
删除 XML 资源文件19 个(layout/anim/drawable)
删除 BaseActivity/BaseFragment2 个
删除无用空目录14 个
移除依赖库appcompat / material / fragment-ktx / viewpager2

五、关键技术亮点

5.1 KSP 路由 + 生成类型兼容性(行业痛点突破)

在 v1.0 中,我实现了一套基于 KSP 的路由注解处理器。但在迁移过程中遇到了一个行业级难题

问题getSymbolsWithAnnotation 在解析类时,如果类的签名中包含尚未生成的类型(如 ViewBinding),会静默跳过该类,导致 @Route 注解被遗漏。

解决方案:实现 双重扫描机制

  1. 标准 resolver.getSymbolsWithAnnotation() 获取能正常解析的符号
  2. 递归遍历 resolver.getAllFiles(),手动检查每个声明是否有目标注解
  3. 合并结果并去重

这套方案不仅解决了 ViewBinding 的兼容问题,也适用于任何 KSP + 生成类型的场景(如 Dagger/Hilt + ViewBinding)。

5.2 主题响应式切换(无需 recreate)

传统 XML 主题切换依赖 AppCompatDelegate.setDefaultNightMode(),会导致 Activity 重建(recreate()),造成闪烁和状态丢失。

我们的方案ThemeManager.isDark 使用 StateFlow,UI 通过 collectAsStateWithLifecycle 订阅,主题切换时只触发 Compose 重组,不重建 Activity

5.3 DAG 启动任务调度器(冷启动 380ms)

启动任务调度器基于有向无环图(DAG) 解析任务依赖,自动并行执行无依赖的任务。

迁移前后对比:

方案Application 创建耗时
传统串行初始化~800ms
DAG 并行调度~230ms
性能提升70%

完整启动链路耗时:

进程创建           115ms
Application 创建 231ms
启动任务执行         2ms
Activity 创建    0ms
首帧渲染           32ms
总耗时: 380ms

5.4 APK 瘦身:19.55MB → 9.59MB

优化手段效果
ABI 过滤(仅保留 arm64-v8a)-40% ~ -60%
资源压缩(shrinkResources = true)-10% ~ -20%
图片转 WebP(质量 75%)-20% ~ -30%
R8 混淆 + 移除未使用代码-15% ~ -25%
移除 appcompat/material 等依赖-5% ~ -10%

六、迁移前后对比

维度v1.0 (XML + ViewBinding)v1.1.0 (纯 Compose)
UI 框架XML + ViewBindingJetpack Compose
Activity 基类BaseActivity<VB, VM>BaseComposeActivity<State, Event, VM>
Fragment支持已移除
主题切换AppCompatDelegate(重建 Activity)StateFlow 驱动(仅重组)
依赖库appcompat / material / fragment-ktx / viewpager2全部移除
APK 大小9.59MB< 9MB(预期)
冷启动耗时~450ms~380ms
UI 代码量XML + Activity + ViewBinding纯 Kotlin Compose(-60%)

七、踩坑实录

7.1 Compose Compiler 版本不匹配

现象:编译报错 Compose Compiler requires Kotlin version X but found Y

解决:Kotlin 2.1.20 使用 org.jetbrains.kotlin.plugin.compose 插件,版本号与 Kotlin 保持一致,不再需要 kotlinCompilerExtensionVersion

7.2 闪屏页白屏

现象:Splash → Main 跳转时出现短暂白屏

解决installSplashScreen() + setKeepOnScreenCondition + 品牌色 Box 占位三重保障

7.3 主题切换后 UI 未更新

现象:调用 ThemeManager.toggleTheme() 后 UI 未变化

解决:使用 collectAsStateWithLifecycle 订阅 StateFlow,确保 Compose 能感知状态变化

7.4 路由注解被 KSP 遗漏

现象:部分 @Route 注解未被扫描到

解决:实现双重扫描机制,手动遍历 resolver.getAllFiles() 查找注解


八、迁移收益总结

8.1 开发效率提升

  • UI 代码量减少 60%:纯 Kotlin,无需维护 XML 和 ViewBinding
  • 状态管理更清晰:单向数据流,State 驱动 UI 重组
  • 预览调试更方便@Preview 注解支持设计时预览

8.2 性能提升

  • 冷启动 380ms:达到行业顶级水平
  • APK < 9MB:轻量级交付
  • 主题切换无重建:体验更丝滑

8.3 技术债务清零

  • 移除 12 个 View 组件类
  • 移除 19 个 XML 资源文件
  • 移除 appcompat / material / fragment-ktx / viewpager2 依赖
  • 14 个空目录清理

九、项目地址

GitHubgithub.com/kyrach-m/Sa…

如果这个项目对你有帮助,欢迎 Star、Fork、提 Issue。


十、写在最后

从 v1.0 到 v1.1.0,这次迁移不仅仅是 UI 框架的替换,更是对整套架构的一次全面体检和升级

一些感悟

  1. 架构是长期投资:好的架构能让你在技术演进时游刃有余。正是因为 v1.0 做了严格的模块化分层,迁移时才能做到“基建层 0 修改,UI 层全量替换”。
  2. Compose 不是银弹,但值得拥抱:迁移过程中确实遇到了一些坑(版本兼容、白屏、主题切换),但解决后的收益是巨大的。
  3. 开源的价值:把项目开源出来,不仅能收到反馈,也能倒逼自己把代码写得更好。

希望这篇文章对正在考虑迁移到 Compose 的你有所帮助。如果有什么问题,欢迎在评论区交流!


作者:kyrach GitHub:github.com/kyrach-m 欢迎关注我的技术专栏,更多 Android 架构实战分享。