资讯动态

Android Showcase 2.0 深度解析:基于 Clean Architecture 与模块化的生产级 Android 应用架构实践

发布时间:2026/10/15 15:28:59 来源:尧图企业网站定制
移动开发示例工程【免费下载链接】android-showcase Android application following best practices: Kotlin, Coroutines, JetPack, Clean Architecture, Feature Modules, Tests, MVVM, DI, Static Analysis...项目地址https://gitcode.com/gh_mirrors/an/android-showcase点击查看免费下载本文以开源仓库 android-showcase 为研究对象系统拆解这个以 Last.fm API 为数据源的音乐发现应用如何落地 Clean Architecture、Feature Modules、MVVMMVI、依赖注入与全链路质量门禁。读者将掌握模块化工程的分层边界如何设计、Gradle 版本目录与 Convention Plugins 如何统一构建、状态管理如何做到单向数据流、以及一套可复制的代码质量验证流水线。项目概览与应用场景Android Showcase 2.0 是一个生产就绪production-ready的 Android 示例应用其核心定位并非追求复杂的 UI 设计而是示范如何在真实规模下组织工程架构、工具链与工程实践。应用本身是一个基于 Jetpack Compose 构建的音乐发现工具从 Last.fm API 拉取专辑信息覆盖网络请求、本地缓存、导航与状态管理等真实业务场景。当前实现的功能范围详见 README 的 Application Scope 一节Album List专辑列表浏览专辑并支持搜索Album Details专辑详情查看专辑详细信息与曲目列表Favorites收藏保存喜欢的专辑WIP 进行中Profile个人资料/设置用户偏好与设置WIP 进行中。从源码结构看feature/album模块是功能最完整的示例包含data、domain、presentation三层以及对应的单元测试而feature/favourite与feature/settings目前仍以占位屏幕为主如FavouriteScreen直接展示UnderConstructionAnim。这一设计刻意将做完整与做示范区分开一个功能完整落地其余功能保留为可扩展骨架方便开发者对照学习。技术栈全景README 的 Tech-Stack 一节罗列了完整技术选型这里按职责归纳为六类并结合 gradle/libs.versions.toml 给出仓库内实际锁定的版本注意仓库实际版本以 TOML 为准README 徽章仅代表大版本线分类技术仓库版本示例核心语言Kotlin、Coroutines、Flow、KSP、SerializationKotlin2.4.20、kotlinx-serialization-json1.11.0JetpackCompose、Navigation Compose、ViewModel、Room、Core SplashscreenCompose BOM2026.09.00、Room2.8.5、navigation-compose2.10.2网络与图片Retrofit、OkHttp、CoilRetrofit3.0.0、OkHttp5.5.0、Coil2.7.0依赖注入KoinKoin4.2.2测试JUnit 5Jupiter、Mockk、Kluent、Espresso、KonsistJUnit Jupiter6.1.3、Konsist0.17.3构建与质量Gradle Kotlin DSL、Version Catalogs、Detekt、Ktlint、Spotless、RenovateDetekt1.23.8、Spotless8.10.3几个值得注意的细节SDK 版本集中在版本目录中compile-sdk 37、min-sdk 28、target-sdk 35注释明确说明这些值虽被标记为 unused但实际被各build.gradle.kts引用测试体系是 JUnit 5Jupiter而非 JUnit 4并通过 Konsist 测试强制禁止 JUnit4 注解见下文代码验证一节KSP 与 Kotlin 版本强绑定TOML 中专门注释说明 KSP 依赖特定 Kotlin 版本因此 Renovate 自动升级时对 KSP 单独禁用防止版本失配。架构设计模块化 Clean Architecture项目在模块层面实现 Clean Architecture——每个 feature 模块都是一个微服务式的独立可复用组件README Architecture 一节。这种设计带来的收益被明确列出代码复用跨 App 变体共享、关注点分离清晰的模块边界与显式依赖、并行开发、更快的构建增量编译与构建缓存、以及更强的可测试性。模块类型与依赖仓库的settings.gradle.kts实际声明了以下模块include( :app, :feature:album, :feature:settings, :feature:favourite, :feature:base, :library:test-utils, :konsist-test, )对照 README 的模块分类app主应用模块承担导航装配、DI 全局配置与应用级组件。对应源码 app/src/main/kotlin/com/igorwojda/showcase/app 下的MainShowcaseActivity、MainShowcaseScreen、AppKoinModule等feature-*功能模块album、settings、favourite各自包含完整的业务逻辑分层feature-base共享基础模块提供通用工具与基类如BaseViewModel、ApiResult、主题ShowcaseThemelibrary-*工具类模块如library/test-utils提供测试扩展CoroutinesTestDispatcherExtension、InstantTaskExecutorExtension。模块间的依赖规则在 Convention Plugin 中固化FeatureConventionPlugin会为除:feature:base以外的所有 feature 模块自动注入implementation(project(:feature:base))见 FeatureConventionPlugin.kt从构建脚本层面保证基础模块先行。Feature 模块的三层结构每个 feature 模块内建三层分层架构README Feature Module Structure 一节每层职责如下Presentation 层离用户最近混合使用 MVVM 与 MVI 两种模式见下文专节Domain 层应用核心层不依赖任何其他层——更换数据库data 层或屏幕 UIpresentation 层理想情况下不应引起 domain 层任何代码变更Data 层封装数据获取从网络取数并写入磁盘缓存离线时可用对外通过 Repository 暴露数据。以feature/album模块为实例三层在磁盘上的目录结构清晰可见feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/ ├── data/ # Data 层 │ ├── datasource/api/ # Retrofit Service、ApiModel、Response │ ├── datasource/database/ # Room Database、DAO、Entity │ ├── mapper/ # ApiModel/RoomModel - DomainModel 映射 │ └── repository/ # AlbumRepositoryImpl ├── domain/ # Domain 层 │ ├── enum/ # ImageSize │ ├── model/ # Album、Image、Track、Tag │ ├── repository/ # AlbumRepository 接口 │ └── usecase/ # GetAlbumListUseCase、GetAlbumUseCase └── presentation/ # Presentation 层 ├── composable/ # SearchBar └── screen/albumlist|albumdetail/ # Screen、UiState、Action、ViewModelPresentation 层MVVM MVI 的融合README 对 Presentation 层的描述可以拆解为两条设计主线MVVM 侧JetpackViewModel封装公共 UI 状态common UI state并通过Kotlin Flow以可观察状态对外暴露MVI 侧action用户意图修改公共 UI 状态经Kotlin Flow向视图发射新状态。这套方案源自单向数据流Unidirectional Data Flow与 Redux 三原则其工程落地集中在 feature/base 的 BaseViewModelabstract class BaseViewModelState : BaseState, Action : BaseActionState( initialState: State, ) : ViewModel() { private val _uiStateFlow MutableStateFlow(initialState) val uiStateFlow _uiStateFlow.asStateFlow() // 状态变化去重相同数据的同类型状态不会重复发射给 UI private var state by Delegates.observable(initialState) { _, old, new - if (old ! new) { _uiStateFlow.value new stateTimeDebugger?.addStateTransition(old, new) } } protected fun sendAction(action: Action) { stateTimeDebugger?.addAction(action) state action.reduce(state) // action 归约出新状态 } }对应的 View 侧消费方式在 AlbumListScreen.kt 中体现通过viewModel.uiStateFlow.collectAsStateWithLifecycle()以生命周期感知方式收集 Flow避免资源浪费这正是 README 引用的 Consuming Flows Safely 官方建议。而 AlbumListUiState.kt 中的状态被标注为Immutable供 Compose 编译器做组合优化composition optimizations。以专辑列表为例完整的 MVI 状态流转可见 AlbumListViewModel.ktviewModelScope.launch { queryFlow // 用户停止输入后防抖清空输入时立即重置 .debounce { query - if (query.isEmpty()) 0L else SEARCH_DEBOUNCE_MILLIS } .map { query - query.ifEmpty { DEFAULT_QUERY_NAME } } .distinctUntilChanged() .flatMapLatest { query - flow { emit(AlbumListAction.AlbumListLoadStart) emit(getAlbumList(query)) } }.collect(::sendAction) }这里有几个可复用的工程细节防抖搜索SEARCH_DEBOUNCE_MILLIS 300L且清空输入时延迟为 0保证清空即重置空查询回退空关键字时使用默认查询DEFAULT_QUERY_NAME Jackson避免空请求查询状态持久化查询串存放在SavedStateHandle的 StateFlow 中配置变更甚至进程死亡后仍能恢复并有对应测试onInit searches for saved query验证见 AlbumListViewModelTest.kt。Presentation 层还包含StateTimeTravelDebugger状态时间旅行调试器位于 StateTimeTravelDebugger.kt它在 DEBUG 构建下记录每次action → 状态迁移的时间线并把状态属性变化如albums: [] - [...]以Action标签输出到 Logcat帮助开发者追踪哪个 action 导致了哪个状态变化。Domain 层与世隔绝的核心Domain 层是应用的核心必须对其他层零依赖。其三个组成组件在 feature/album/domain 中均有对应UseCase承载业务逻辑。如 GetAlbumListUseCase.kt 在拿到 Repository 结果后通过mapSuccess过滤掉没有默认图片的专辑——这是典型的业务层加工DomainModel应用数据的单一事实来源。如 Album.kt 定义了Album数据类其getDefaultImageUrl()方法从图片列表中挑选EXTRA_LARGE尺寸的 URL 用于列表展示Repository 接口保持 domain 层与 data 层解耦的关键依赖倒置原则。如 AlbumRepository.kt 只声明searchAlbum与getAlbumInfo两个挂起函数具体网络/数据库实现全部藏在 data 层。Data 层Retrofit Room 双数据源Data 层封装了取数据并缓存的职责。本应用拥有两个数据源可视作隐式子层README 原文将其描述为 implicit sub-layer数据源组成类仓库实例Retrofit网络Service / Response Model / Api Data ModelAlbumRetrofitService.kt、SearchAlbumResponse、AlbumApiModelRoom本地持久化Database / DAO / EntityAlbumDatabase.kt、AlbumDao.kt、AlbumRoomModel网络层还封装了一套ApiResult返回类型见 ApiResult.kt区分三种结果Success收到正常响应、Error收到携带错误码的响应、Exception请求前发生异常如IOException。它通过自定义的 ApiResultAdapterFactory.kt 作为 Retrofit CallAdapter 接入这样 Service 方法的返回类型可以直接声明为ApiResultSearchAlbumResponse把成功/业务错误/异常三类结果从挂起函数中显式暴露出来。Mapper是 Data 层保持 Domain 独立的关键AlbumMapper见 AlbumMapper.kt提供apiToDomain、apiToRoom、roomToDomain三向映射其中apiToDomain还会过滤掉UNKNOWN尺寸或 URL 为空的图片条目。数据流一次搜索背后的完整链路README 以用户在专辑列表页的一次交互为例给出了应用数据流图。结合源码这条链路可精确还原为ViewScreen用户输入搜索词 →SearchBar回调viewModel::onQueryChange见 SearchBarComposable.kt→ 写入SavedStateHandleViewModelqueryFlow经debounce(300ms)→flatMapLatest依次发射AlbumListLoadStart与结果 Action见 AlbumListViewModel.ktUseCaseGetAlbumListUseCase调用AlbumRepository.searchAlbum并过滤无图专辑RepositoryAlbumRepositoryImpl见 AlbumRepositoryImpl.kt调用 Retrofit Service → 成功后同时写入 Room 缓存并映射为 Domain 模型若网络异常则回退读取 Room 缓存兜底Back to View新状态AlbumListUiState.Content(albums)经collectAsStateWithLifecycle驱动LazyVerticalGrid重新组合。其中第 4 步的网络异常回退数据库是有测试背书的核心能力AlbumRepositoryImplTest.kt 用UnknownHostException模拟断网场景断言 Repository 返回 Room 中缓存数据。项目特性调试、变体图标与主题图标开发与调试日志项目提供了一套可被标签过滤的结构化日志体系标签统一收敛在 TimberLogTags.ktNavigation导航事件与路由变化。实现位于 NavigationDestinationLogger.kt通过NavController.addOnDestinationChangedListener监听目标变化输出Route、ID、Label与 Bundle 参数并以Timber.tag(TimberLogTags.NAVIGATION)打印该监听器只在BuildConfig.DEBUG下注册见 MainShowcaseScreen.ktAction用户行为与 UI 状态变更。由StateTimeTravelDebugger.logLast()输出Network网络请求/响应日志。AppKoinModule中的HttpLoggingInterceptor通过Timber.tag(Network).d(message)打印见 AppKoinModule.kt。一个值得借鉴的安全实践网络日志的level在 DEBUG 构建下为BODY包含请求/响应体在 release 下强制设为NONE既避免生产环境的性能开销也防止敏感数据经任何 Logger 泄露该决策在源码注释中有完整说明。变体定制图标借助Easylauncher Gradle 插件debug构建的应用图标会被叠加一个底部深红底色白色文字的标签条ribbonColor #99AD0000、overlayHeight 0.25F见 EasyLauncherConventionPlugin.kt。这使开发者能在真机上一眼区分 debug/release 包避免误装。主题图标Themed Icons应用支持 Android 13 的 Themed Icons 等资源实现。Gradle 构建体系README 的 Gradle Config 一节是整个工程最有借鉴价值的部分之一包含五项关键实践。版本目录Version Catalogs集中管理依赖Gradle 版本目录gradle/libs.versions.toml作为跨模块共享的第三方依赖坐标中枢分为四个段落[versions]声明可被所有依赖引用的版本号[libraries]声明库坐标别名group/artifact/version[bundles]声明依赖组如retrofitbundle 聚合了 retrofit、kotlinx-serialization-converter、serialization-json、okhttp、okhttp-interceptor 五个依赖[plugins]声明 Gradle 插件依赖。Convention Plugins构建逻辑的标准化Convention Plugins 将通用构建逻辑封装为可复用插件避免每个模块重复粘贴构建代码。仓库build-logic模块共提供 9 个插件README 列表与源码一一对应插件职责源码ApplicationConventionPlugin主应用模块com.android.application配置ApplicationConventionPlugin.ktFeatureConventionPlugin功能模块com.android.library Kotlin Test 组合FeatureConventionPlugin.ktLibraryConventionPluginAndroid 库模块通用配置LibraryConventionPlugin.ktKotlinConventionPluginKotlin 编译、toolchain 与编译器选项KotlinConventionPlugin.ktTestConventionPlugin/TestConventionLibraryPluginJUnit5 测试框架、并行执行与 test-logger 主题TestConventionPlugin.ktDetektConventionPluginDetekt 静态分析detektCheck/detektApply任务DetektConventionPlugin.ktSpotlessConventionPluginKtlint Compose 规则集 格式校验SpotlessConventionPlugin.ktEasyLauncherConventionPlugin变体图标定制EasyLauncherConventionPlugin.ktAboutLibrariesConventionPlugin开源许可信息收集DuplicateMode.MERGE合并重复条目AboutLibrariesConventionPlugin.kt插件组合的复用价值在ApplicationConventionPlugin中体现得最直观它只声明 Android 应用相关配置compileSdk/minSdk/targetSdk、buildFeatures.compose true、compileOptions等见 ApplicationConventionPlugin.kt其余 Kotlin、Spotless、EasyLauncher、AboutLibraries 全部以apply...ConventionPlugin()组合复用。类型安全的项目访问器在 settings.gradle.kts 中启用enableFeaturePreview(TYPESAFE_PROJECT_ACCESSORS)后模块依赖从易出错的字符串路径升级为类型安全引用README 原例// Before implementation(project(:feature:album)) // After implementation(projects.feature.album)统一版本配置Java/JVM 版本单一事实来源Java/JVM 版本只在libs.versions.toml的java 17一处定义随后由JavaBuildConfig见 JavaBuildConfig.kt在构建逻辑编译阶段直接读取 TOML 文件内容生成常量——因为此时VersionCatalogsExtension尚不可用所以采用File(...).readLines()解析的巧妙手段。随后各 Convention Plugin 统一使用compileOptions { sourceCompatibility JavaBuildConfig.JAVA_VERSION targetCompatibility JavaBuildConfig.JAVA_VERSION } kotlin { compilerOptions { jvmTarget JavaBuildConfig.jvmTarget } jvmToolchain(JavaBuildConfig.jvmToolchainVersion) }这样 Java 与 Kotlin 的目标版本永远不会因多处手改而漂移。build-logic 中的类型安全版本目录访问器由于build-logic/settings.gradle.kts中通过versionCatalogs { create(libs) { from(files(../gradle/libs.versions.toml)) } }引用了主版本目录见 build-logic/settings.gradle.kts预编译脚本插件内也能类型安全地使用目录依赖再配合 DependencyHandlerExt.kt 提供的扩展函数插件内写法与标准 Gradle 用法完全一致// 底层实现add(implementation, provider) implementation(libs.timber)Gradle 配置缓存项目启用了 Gradle Configuration CacheREADME 注明基于 Gradle 9.0 文档配合增量编译与构建缓存显著缩短重复构建耗时。代码验证从本地命令到 CI 流水线本地质量检查命令README 的 Code Verification 一节给出可直接复用的命令集合# 质量检查 ./gradlew konsist-test:test --rerun-tasks # 架构与规范约定测试 ./gradlew lintDebug # Android Lint 分析 ./gradlew detektCheck # 复杂度与风格分析 ./gradlew spotlessCheck # 格式校验 ./gradlew testDebugUnitTest -x konsist-test:test # 单元测试排除 Konsist 测试 ./gradlew connectedCheck # UI 测试WIP ./gradlew :app:bundleDebug # 生产构建验证 # 自动修复 ./gradlew detektApply # 应用 Detekt 格式化修复 ./gradlew spotlessApply # 应用格式修复 ./gradlew lintDebug # 更新 Lint baseline注意-x konsist-test:test的用法因为 Konsist 测试本身也扫描全部源码常规单元测试时将其排除可加快反馈detektApply与detektCheck共用同一套配置autoCorrect布尔值不同见 DetektConventionPlugin.kt。Konsist用测试守护架构这是本项目最突出的工程质量实践用Konsist把架构约定写成可执行的单元测试任何违规提交都会被 CI 拦下。konsist-test模块的六个测试类分别守护不同维度CleanArchitectureKonsistTest.kt声明三层架构规则——domain.dependsOnNothing()、presentation.dependsOn(domain)、data.dependsOn(domain)从测试层面保证Domain 不依赖任何层的架构原则ModuleKonsistTest.kt每个文件的包名必须与所属模块一致UseCaseKonsistTest.kt所有UseCase后缀类必须位于domain.usecase包、必须有测试hasTestClasses(testPropertyName sut)、构造参数必须字母序排列且以类型命名、必须只有一个operator fun invokeViewModelKonsistTest.kt所有 ViewModel 必须有测试构造参数命名规则同 UseCaseAndroidKonsistTest.kt继承ViewModel的类必须ViewModel后缀GeneralKonsistTest.kt包名与路径匹配、禁止m前缀字段、禁止使用android.util.Log日志必须走 Timber、禁止空文件TestKonsistTest.kt禁止 JUnit4 的org.junit.Test注解统一 JUnit5。这些规则并非摆设——GetAlbumListUseCase、AlbumListViewModel等均满足每个 UseCase/ViewModel 都有测试的硬性要求测试文件与生产代码一一对应。CI 流水线与 Pre-push HooksREADME 提及.github/workflows下的 GitHub Actions 工作流PR 校验并行执行、主分支保护、Renovate 自动依赖更新 PR并支持可选的 pre-push Git 钩子在推送前本地执行质量检查以获得最快反馈。项目范围与限制README 明确声明本项目优先展示架构、工具链与开发实践而非复杂 UI 设计。界面使用 Material Design 3 组件但保持刻意简单。此外Favorites 与 Profile 功能目前是 WIPEspresso UI 测试同样标记为 WIP——这些未完成本身也是示范围的一部分为后续贡献者留出明确的参与空间CONTRIBUTING.md 中列出了功能实现、UI/UX 改进、性能优化、测试扩展、文档完善等贡献方向。开始使用按 README 的 Getting Started 一节前置条件Android Studio Giraffe2022.3.1、JDK 17、Android SDK 34仓库实际compileSdk为 37。克隆与打开git clone https://github.com/igorwojda/android-showcase.git # Android Studio: File - Open - 选择克隆目录推荐 IDE 插件Detekt配合根目录 detekt.yml 自定义规则、Kotlin 与 Android 插件通常预装。运行时应用在debug构建下会输出Navigation、Action、Network三组可过滤日志配合 Logcat 的 tag 过滤即可观察单向数据流与网络行为若需联网拉取真实数据需在本地BuildConfigGRADLE_API_TOKEN/GRADLE_API_BASE_URL见 AppKoinModule.kt中配置有效的 Last.fm API Key。Roadmap 与延伸学习资源README 的 Roadmap 表明项目持续跟进现代 Android 实践enhancement 标签下的 issue 即为待办方向Resources 一节列出了 Material Theme Builder、Compose Material 3 组件参考、Kotlin Coroutines 实战示例等开发工具以及 Now in Android、Android Architecture Blueprints、Tivi、CatchUp 等推荐参考项目。动画资源错误屏与建设中屏的 Lottie 动画按 Creative Commons License 2.0 分发来源分别为 LottieFiles 上的 Error screen 与 Building Screen。小结Android Showcase 2.0 的核心价值在于它把教科书原则落成了可编译、可测试、可检查的工程实体Clean Architecture 的三层边界由 Konsist 测试强制守护依赖管理由版本目录与 Convention Plugins 统一收口状态管理收敛为单向数据流的BaseViewModel抽象而调试体验则通过 Timber 标签体系与状态时间旅行调试器大幅提升。对于正在搭建大规模 Android 工程架构的团队这份仓库是一份可以直接对照移植的参考蓝本。赞分享移动开发示例工程【免费下载链接】android-showcase Android application following best practices: Kotlin, Coroutines, JetPack, Clean Architecture, Feature Modules, Tests, MVVM, DI, Static Analysis...项目地址https://gitcode.com/gh_mirrors/an/android-showcase点击查看免费下载相关推荐FeatureToggle核心组件解析从AlwaysOn到SqlFeatureToggle全攻略FeatureToggle核心组件解析从AlwaysOn到SqlFeatureToggle全攻略 FeatureToggle是一个为.NET应用程序设计的简单软件架构开发工具花笺便签快速上手5分钟完成Windows/macOS/Linux安装的保姆级教程花笺便签快速上手5分钟完成Windows/macOS/Linux安装的保姆级教程 花笺Floral Notepaper 是一款轻量、优雅的跨平台桌面便签工终极指南Android Clean Architecture架构模式深度解析终极指南Android Clean Architecture架构模式深度解析 Android Clean Architecture架构模式是现代Android示例工程移动开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑