Android Clean Architecture

affaan-m/ECC/docs/zh-CN/skills/android-clean-architecture

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

适用于Android和Kotlin多平台项目的Clean Architecture模式——模块结构、依赖规则、用例、仓库以及数据层模式。

AI 產生的概覽

針對 Android 與 Kotlin 多平台專案的整潔架構模式指南,涵蓋模組結構、分層與資料存取。

功能
此技能為採用整潔架構的 Android 與 Kotlin 多平台專案提供結構參考。內容說明建議的模組配置、各層之間的相依規則、UseCase 與 Repository 模式、映射器,以及使用 Room、SQLDelight 和 Ktor 的資料層設計。也涵蓋使用 Koin 與 Hilt 的相依注入、錯誤處理模式、Gradle 慣例外掛,以及應避免的反模式。
適用情境
適用於建立或重構 Android 或 KMP 專案的模組結構,或實作 UseCase、Repository 與 DataSource 時。也適合設計領域層、資料層與呈現層之間的資料流,或設定相依注入。
執行需求
除代理外不需任何指令碼或工具,屬於純說明性參考。套用這些模式的前提是一個 Kotlin 專案,可能使用 Room、SQLDelight、Ktor、Koin、Hilt 或 Gradle 慣例外掛。

Android 整洁架构

适用于 Android 和 KMP 项目的整洁架构模式。涵盖模块边界、依赖反转、UseCase/Repository 模式,以及使用 Room、SQLDelight 和 Ktor 的数据层设计。

何时启用

  • 构建 Android 或 KMP 项目模块结构
  • 实现 UseCases、Repositories 或 DataSources
  • 设计各层(领域层、数据层、表示层)之间的数据流
  • 使用 Koin 或 Hilt 设置依赖注入
  • 在分层架构中使用 Room、SQLDelight 或 Ktor

模块结构

推荐布局

project/├── app/                  # Android 入口点,DI 装配,Application 类├── core/                 # 共享工具类,基类,错误类型├── domain/               # 用例,领域模型,仓库接口(纯 Kotlin)├── data/                 # 仓库实现,数据源,数据库,网络├── presentation/         # 界面,ViewModel,UI 模型,导航├── design-system/        # 可复用的 Compose 组件,主题,排版└── feature/              # 功能模块(可选,用于大型项目)    ├── auth/    ├── settings/    └── profile/

依赖规则

app → presentation, domain, data, corepresentation → domain, design-system, coredata → domain, coredomain → core (或无依赖)core → (无依赖)

关键:domain 绝不能依赖 data、presentation 或任何框架。它仅包含纯 Kotlin 代码。

领域层

UseCase 模式

每个 UseCase 代表一个业务操作。使用 operator fun invoke 以获得简洁的调用点:

kotlin
class GetItemsByCategoryUseCase(    private val repository: ItemRepository) {    suspend operator fun invoke(category: String): Result<List<Item>> {        return repository.getItemsByCategory(category)    }}
// Flow-based UseCase for reactive streamsclass ObserveUserProgressUseCase(    private val repository: UserRepository) {    operator fun invoke(userId: String): Flow<UserProgress> {        return repository.observeProgress(userId)    }}

领域模型

领域模型是普通的 Kotlin 数据类——没有框架注解:

kotlin
data class Item(    val id: String,    val title: String,    val description: String,    val tags: List<String>,    val status: Status,    val category: String)
enum class Status { DRAFT, ACTIVE, ARCHIVED }

仓库接口

在领域层定义,在数据层实现:

kotlin
interface ItemRepository {    suspend fun getItemsByCategory(category: String): Result<List<Item>>    suspend fun saveItem(item: Item): Result<Unit>    fun observeItems(): Flow<List<Item>>}

数据层

仓库实现

协调本地和远程数据源:

kotlin
class ItemRepositoryImpl(    private val localDataSource: ItemLocalDataSource,    private val remoteDataSource: ItemRemoteDataSource) : ItemRepository {
    override suspend fun getItemsByCategory(category: String): Result<List<Item>> {        return runCatching {            val remote = remoteDataSource.fetchItems(category)            localDataSource.insertItems(remote.map { it.toEntity() })            localDataSource.getItemsByCategory(category).map { it.toDomain() }        }    }
    override suspend fun saveItem(item: Item): Result<Unit> {        return runCatching {            localDataSource.insertItems(listOf(item.toEntity()))        }    }
    override fun observeItems(): Flow<List<Item>> {        return localDataSource.observeAll().map { entities ->            entities.map { it.toDomain() }        }    }}

映射器模式

将映射器作为扩展函数放在数据模型附近:

kotlin
// In data layerfun ItemEntity.toDomain() = Item(    id = id,    title = title,    description = description,    tags = tags.split("|"),    status = Status.valueOf(status),    category = category)
fun ItemDto.toEntity() = ItemEntity(    id = id,    title = title,    description = description,    tags = tags.joinToString("|"),    status = status,    category = category)

Room 数据库 (Android)

kotlin
@Entity(tableName = "items")data class ItemEntity(    @PrimaryKey val id: String,    val title: String,    val description: String,    val tags: String,    val status: String,    val category: String)
@Daointerface ItemDao {    @Query("SELECT * FROM items WHERE category = :category")    suspend fun getByCategory(category: String): List<ItemEntity>
    @Upsert    suspend fun upsert(items: List<ItemEntity>)
    @Query("SELECT * FROM items")    fun observeAll(): Flow<List<ItemEntity>>}

SQLDelight (KMP)

sql
-- Item.sqCREATE TABLE ItemEntity (    id TEXT NOT NULL PRIMARY KEY,    title TEXT NOT NULL,    description TEXT NOT NULL,    tags TEXT NOT NULL,    status TEXT NOT NULL,    category TEXT NOT NULL);
getByCategory:SELECT * FROM ItemEntity WHERE category = ?;
upsert:INSERT OR REPLACE INTO ItemEntity (id, title, description, tags, status, category)VALUES (?, ?, ?, ?, ?, ?);
observeAll:SELECT * FROM ItemEntity;

Ktor 网络客户端 (KMP)

kotlin
class ItemRemoteDataSource(private val client: HttpClient) {
    suspend fun fetchItems(category: String): List<ItemDto> {        return client.get("api/items") {            parameter("category", category)        }.body()    }}
// HttpClient setup with content negotiationval httpClient = HttpClient {    install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }    install(Logging) { level = LogLevel.HEADERS }    defaultRequest { url("https://api.example.com/") }}

依赖注入

Koin (适用于 KMP)

kotlin
// Domain moduleval domainModule = module {    factory { GetItemsByCategoryUseCase(get()) }    factory { ObserveUserProgressUseCase(get()) }}
// Data moduleval dataModule = module {    single<ItemRepository> { ItemRepositoryImpl(get(), get()) }    single { ItemLocalDataSource(get()) }    single { ItemRemoteDataSource(get()) }}
// Presentation moduleval presentationModule = module {    viewModelOf(::ItemListViewModel)    viewModelOf(::DashboardViewModel)}

Hilt (仅限 Android)

kotlin
@Module@InstallIn(SingletonComponent::class)abstract class RepositoryModule {    @Binds    abstract fun bindItemRepository(impl: ItemRepositoryImpl): ItemRepository}
@HiltViewModelclass ItemListViewModel @Inject constructor(    private val getItems: GetItemsByCategoryUseCase) : ViewModel()

错误处理

Result/Try 模式

使用 Result<T> 或自定义密封类型进行错误传播:

kotlin
sealed interface Try<out T> {    data class Success<T>(val value: T) : Try<T>    data class Failure(val error: AppError) : Try<Nothing>}
sealed interface AppError {    data class Network(val message: String) : AppError    data class Database(val message: String) : AppError    data object Unauthorized : AppError}
// In ViewModel — map to UI stateviewModelScope.launch {    when (val result = getItems(category)) {        is Try.Success -> _state.update { it.copy(items = result.value, isLoading = false) }        is Try.Failure -> _state.update { it.copy(error = result.error.toMessage(), isLoading = false) }    }}

约定插件 (Gradle)

对于 KMP 项目,使用约定插件以减少构建文件重复:

kotlin
// build-logic/src/main/kotlin/kmp-library.gradle.ktsplugins {    id("org.jetbrains.kotlin.multiplatform")}
kotlin {    androidTarget()    iosX64(); iosArm64(); iosSimulatorArm64()    sourceSets {        commonMain.dependencies { /* shared deps */ }        commonTest.dependencies { implementation(kotlin("test")) }    }}

在模块中应用:

kotlin
// domain/build.gradle.ktsplugins { id("kmp-library") }

应避免的反模式

  • 在 domain 中导入 Android 框架类——保持其为纯 Kotlin
  • 向 UI 层暴露数据库实体或 DTO——始终映射到领域模型
  • 将业务逻辑放在 ViewModels 中——提取到 UseCases
  • 使用 GlobalScope 或非结构化协程——使用 viewModelScope 或结构化并发
  • 臃肿的仓库实现——拆分为专注的 DataSources
  • 循环模块依赖——如果 A 依赖 B,则 B 绝不能依赖 A

参考

查看技能:compose-multiplatform-patterns 了解 UI 模式。 查看技能:kotlin-coroutines-flows 了解异步模式。

來源與署名

來源:affaan-m/ECC位於docs/zh-CN/skills/android-clean-architecture提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架