You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Gradle中搭建Android Clean Architecture多模块项目?

在Gradle中搭建Android Clean Architecture独立模块项目的分步指南

嘿,我在Android项目里搭建Clean Architecture已经有好几个年头了,刚好对你提到的把domain、data、presentation拆成独立Gradle模块的需求很熟悉——完全按照你说的那个经典项目的思路,我给你一步步拆解怎么实现:

1. 先理清模块职责与依赖方向

首先得明确每个模块的定位,这是Clean Architecture的核心:

  • :domain:纯Kotlin/Java模块,是项目的业务核心,只放实体类、用例、仓库接口,不依赖任何Android框架或其他模块
  • :data:Android库模块,负责数据获取与处理,实现domain层的仓库接口,只依赖domain层
  • :app(presentation层):主应用模块,负责UI展示与用户交互,依赖domain层(调用用例),不直接依赖data层

2. 创建各个独立模块

2.1 搭建Domain模块(纯业务核心)

  • 在Android Studio里右键项目根目录 → New → Module → 选「Kotlin Library」(或者「Java Library」),命名为domain
  • 这个模块的build.gradle.kts要保持极简,只保留必要的语言依赖,绝对不能加Android相关依赖:
    plugins {
        `kotlin-library`
    }
    
    dependencies {
        implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    }
    
  • 这里放的内容要纯粹:
    • 实体类(比如User.kt,定义业务里的核心数据结构)
    • 用例(比如GetUserUseCase.kt,封装单一业务操作)
    • 仓库接口(比如UserRepository.kt,只定义数据操作的契约,不写具体实现)

2.2 搭建Data模块(数据实现层)

  • 右键项目根目录 → New → Module → 选「Android Library」,命名为data
  • 在data/build.gradle.kts里添加对domain模块的依赖,同时引入你需要的数据工具库(比如Retrofit、Room):
    plugins {
        id("com.android.library")
        id("org.jetbrains.kotlin.android")
        id("kotlin-kapt") // 如果用Room的话需要
    }
    
    android {
        // 基本配置和普通Android库一致,比如compileSdk、defaultConfig等
    }
    
    dependencies {
        implementation project(":domain")
        // 数据工具库示例
        implementation "com.squareup.retrofit2:retrofit:2.9.0"
        implementation "androidx.room:room-runtime:2.5.0"
        kapt "androidx.room:room-compiler:2.5.0"
    }
    
  • 这里放数据层的具体实现:
    • 仓库实现类(比如UserRepositoryImpl.kt,实现domain层的UserRepository接口)
    • 数据源(远程数据源RemoteUserDataSource、本地数据源LocalUserDataSource)
    • 网络请求服务、数据库实体等

2.3 配置Presentation层(主App模块)

  • 主App模块(:app)就是我们的presentation层,负责UI相关代码
  • 在app/build.gradle.kts里添加对domain和data模块的依赖(注意:虽然依赖了data,但ViewModel里只调用domain的用例,不直接碰data的类):
    plugins {
        id("com.android.application")
        id("org.jetbrains.kotlin.android")
    }
    
    android {
        // 主应用的配置,比如applicationId、compileSdk等
    }
    
    dependencies {
        implementation project(":domain")
        implementation project(":data")
        // UI相关依赖示例
        implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
        implementation "androidx.compose.ui:ui:1.5.4"
    }
    
  • 这里放UI逻辑:
    • ViewModel(比如UserViewModel.kt,调用domain层的用例,转换业务数据为UI状态)
    • Activity/Fragment/Composable组件
    • UI状态类(比如UserUiState.kt,封装UI需要的加载、成功、失败状态)

3. 配置根项目的settings.gradle.kts

确保根目录的settings.gradle.kts里包含所有模块,这样Gradle才能识别它们:

pluginManagement {
    // 你的插件仓库配置
}
dependencyResolutionManagement {
    // 你的依赖仓库配置
}

include(":app")
include(":domain")
include(":data")

4. 严格遵守依赖规则(重中之重)

一定要守住Clean Architecture的依赖边界:

  • ✅ Presentation层 → 依赖Domain层(只能调用Domain的用例/实体)
  • ✅ Data层 → 依赖Domain层(实现Domain的仓库接口)
  • ❌ 绝对不能让Domain层依赖任何其他层
  • ❌ 绝对不能让Presentation层直接依赖Data层(避免UI和数据实现耦合)

5. 简单的代码示例帮你理解

Domain层代码

// domain/src/main/java/com/example/domain/User.kt
data class User(val id: String, val name: String, val email: String)

// domain/src/main/java/com/example/domain/UserRepository.kt
interface UserRepository {
    suspend fun getUserById(userId: String): User
}

// domain/src/main/java/com/example/domain/GetUserUseCase.kt
class GetUserUseCase(private val repository: UserRepository) {
    suspend operator fun invoke(userId: String): User {
        // 这里可以加业务校验逻辑,比如判断userId是否合法
        require(userId.isNotBlank()) { "User ID can't be empty" }
        return repository.getUserById(userId)
    }
}

Data层代码

// data/src/main/java/com/example/data/repository/UserRepositoryImpl.kt
class UserRepositoryImpl(private val remoteDataSource: RemoteUserDataSource) : UserRepository {
    override suspend fun getUserById(userId: String): User {
        return remoteDataSource.fetchUser(userId)
    }
}

// data/src/main/java/com/example/data/datasource/RemoteUserDataSource.kt
class RemoteUserDataSource(private val api: UserApi) {
    suspend fun fetchUser(userId: String): User {
        val response = api.getUser(userId)
        return response.toDomainUser() // 把API返回的DTO转成Domain的User实体
    }
}

// data/src/main/java/com/example/data/api/UserApi.kt
interface UserApi {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") userId: String): UserDto
}

Presentation层代码

// app/src/main/java/com/example/presentation/UserViewModel.kt
class UserViewModel(
    private val getUserUseCase: GetUserUseCase
) : ViewModel() {
    private val _userUiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
    val userUiState: StateFlow<UserUiState> = _userUiState

    fun loadUser(userId: String) {
        viewModelScope.launch {
            try {
                val user = getUserUseCase(userId)
                _userUiState.value = UserUiState.Success(user)
            } catch (e: Exception) {
                _userUiState.value = UserUiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

// app/src/main/java/com/example/presentation/UserUiState.kt
sealed class UserUiState {
    object Loading : UserUiState()
    data class Success(val user: User) : UserUiState()
    data class Error(val message: String) : UserUiState()
}

6. 额外实用小贴士

  • 用依赖注入框架(比如Hilt)来管理模块间的实例注入,不用手动new用例、仓库,减少耦合
  • 每个模块独立写测试:Domain层写单元测试,Data层写集成测试,Presentation层写UI测试
  • 保持Domain层的纯粹性,绝对不要引入Android类(比如Context),如果需要平台相关能力,通过接口让Data层实现

内容的提问来源于stack exchange,提问作者Utsav Shrestha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:01:57