如何在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需要的加载、成功、失败状态)
- ViewModel(比如
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
相关产品推荐
相关产品推荐

