在当今快速发展的Android应用开发领域,一个良好的架构设计对于构建可维护、可测试且易于扩展的应用至关重要。Clean Architecture(简称洁净架构)作为一种流行的架构模式,已经成为许多Android开发者的首选。本文将从基础概念出发,深入浅出地讲解Clean Architecture的核心思想、实现方式以及在实际开发中的应用,帮助你全面掌握这一架构模式。
一、Clean Architecture基础概念
1.1 什么是Clean Architecture?
Clean Architecture是由Robert C. Martin(也被称为Uncle Bob)提出的一种软件架构模式,旨在创建一个独立于框架、UI、数据库等外部因素的系统核心。这种架构的核心思想是关注点分离和依赖规则,通过将系统分为不同的层次,每一层都有其特定的职责,从而使得系统更加模块化、可测试和可维护。
在Android开发中,Clean Architecture通常被实现为多层结构:
- 表现层(Presentation Layer):负责UI展示和用户交互,包含Activities、Fragments、ViewModels等组件。
- 领域层(Domain Layer):包含业务逻辑和规则,是应用的核心,独立于任何外部框架。
- 数据层(Data Layer):负责数据的获取和存储,包括网络请求、本地数据库操作等。
- 核心层(Core Layer):提供整个应用通用的基础组件、工具类和扩展函数。
1.2 基本示例:多层架构的实现
下面是一个详细的示例,展示了包含Core层的Clean Architecture在Android中的实现:
1. 核心层(Core Layer)
// 核心层:通用工具类和扩展函数
package com.example.core.utils
// 网络状态检测工具
class NetworkUtils(private val context: Context) {
fun isNetworkAvailable(): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
} else {
val networkInfo = connectivityManager.activeNetworkInfo ?: return false
return networkInfo.isConnected
}
}
}
// 日期时间工具类
class DateTimeUtils {
companion object {
fun formatDateTime(timestamp: Long, pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
val dateFormat = SimpleDateFormat(pattern, Locale.getDefault())
return dateFormat.format(Date(timestamp))
}
fun getCurrentTimestamp(): Long {
return System.currentTimeMillis()
}
}
}
// 字符串扩展函数
fun String.isValidEmail(): Boolean {
val emailPattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+"
return this.matches(emailPattern.toRegex())
}
// 核心层:通用基础类
package com.example.core.base
// 基础ViewModel
abstract class BaseViewModel : ViewModel() {
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
private val _error = MutableLiveData<String?>()
val error: LiveData<String?> = _error
protected fun showLoading() {
_loading.value = true
}
protected fun hideLoading() {
_loading.value = false
}
protected fun handleError(message: String?) {
_error.value = message
}
}
// 基础Fragment
abstract class BaseFragment : Fragment() {
protected fun showToast(message: String) {
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}
protected fun showLoading(show: Boolean) {
// 实现通用的加载指示器逻辑
}
}
// 核心层:通用网络组件
package com.example.core.network
// 网络结果包装类
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val message: String, val code: Int? = null) : NetworkResult<Nothing>()
object Loading : NetworkResult<Nothing>()
}
// 网络异常处理
class NetworkExceptionHandler {
fun handleException(throwable: Throwable): NetworkResult.Error {
return when (throwable) {
is IOException -> NetworkResult.Error("网络连接失败,请检查网络设置")
is HttpException -> {
val code = throwable.code()
val errorMessage = when (code) {
401 -> "未授权,请重新登录"
403 -> "禁止访问"
404 -> "请求的资源不存在"
500 -> "服务器内部错误"
else -> "网络请求失败,错误码:$code"
}
NetworkResult.Error(errorMessage, code)
}
else -> NetworkResult.Error(throwable.message ?: "未知错误")
}
}
}
// 核心层:依赖注入模块
package com.example.core.di
@Module
@InstallIn(SingletonComponent::class)
class CoreModule {
@Provides
@Singleton
fun provideNetworkUtils(@ApplicationContext context: Context): NetworkUtils {
return NetworkUtils(context)
}
@Provides
@Singleton
fun provideNetworkExceptionHandler(): NetworkExceptionHandler {
return NetworkExceptionHandler()
}
// 其他核心组件的提供方法...
}
2. 数据层(Data Layer)
// 数据模型
data class UserEntity(
val id: String,
val name: String,
val email: String,
val createdAt: Long
)
// 数据源接口
interface UserDataSource {
suspend fun getUser(userId: String): UserEntity
suspend fun saveUser(user: UserEntity): Boolean
}
// 远程数据源实现
class RemoteUserDataSource @Inject constructor(
private val apiService: ApiService,
private val networkExceptionHandler: NetworkExceptionHandler // 使用核心层组件
) : UserDataSource {
override suspend fun getUser(userId: String): UserEntity {
try {
// 从网络API获取用户数据
return apiService.getUser(userId)
} catch (e: Exception) {
// 使用核心层的异常处理
val error = networkExceptionHandler.handleException(e)
throw Exception(error.message)
}
}
override suspend fun saveUser(user: UserEntity): Boolean {
try {
// 保存用户数据到远程服务器
return apiService.saveUser(user)
} catch (e: Exception) {
val error = networkExceptionHandler.handleException(e)
throw Exception(error.message)
}
}
}
// 本地数据源实现
class LocalUserDataSource @Inject constructor(
private val userDao: UserDao,
private val dateTimeUtils: DateTimeUtils // 使用核心层组件
) : UserDataSource {
override suspend fun getUser(userId: String): UserEntity {
// 从本地数据库获取用户数据
return userDao.getUser(userId)
}
override suspend fun saveUser(user: UserEntity): Boolean {
// 使用核心层的时间工具类获取当前时间戳
val updatedUser = user.copy(createdAt = dateTimeUtils.getCurrentTimestamp())
// 保存用户数据到本地数据库
userDao.insertUser(updatedUser)
return true
}
}
// 仓库实现
class UserRepositoryImpl @Inject constructor(
private val remoteDataSource: RemoteUserDataSource,
private val localDataSource: LocalUserDataSource,
private val networkUtils: NetworkUtils // 使用核心层组件
) : UserRepository {
override suspend fun getUser(userId: String): NetworkResult<User> {
return try {
// 使用核心层的网络工具检查网络状态
if (networkUtils.isNetworkAvailable()) {
// 有网络时,优先从远程获取
try {
val remoteUser = remoteDataSource.getUser(userId)
localDataSource.saveUser(remoteUser) // 缓存到本地
NetworkResult.Success(remoteUser.toDomain())
} catch (e: Exception) {
// 远程获取失败,尝试从本地获取
val localUser = localDataSource.getUser(userId)
NetworkResult.Success(localUser.toDomain())
}
} else {
// 无网络时,从本地获取
val localUser = localDataSource.getUser(userId)
NetworkResult.Success(localUser.toDomain())
}
} catch (e: Exception) {
NetworkResult.Error(e.message ?: "Unknown error occurred")
}
}
override suspend fun saveUser(user: User): NetworkResult<Boolean> {
return try {
val userEntity = user.toData()
// 先保存到本地
val localSuccess = localDataSource.saveUser(userEntity)
// 如果有网络,也保存到远程
if (networkUtils.isNetworkAvailable()) {
try {
val remoteSuccess = remoteDataSource.saveUser(userEntity)
NetworkResult.Success(localSuccess && remoteSuccess)
} catch (e: Exception) {
// 远程保存失败,但本地成功也返回成功
NetworkResult.Success(localSuccess)
}
} else {
// 无网络,只返回本地保存结果
NetworkResult.Success(localSuccess)
}
} catch (e: Exception) {
NetworkResult.Error(e.message ?: "Unknown error occurred")
}
}
}
// 扩展函数:数据模型转领域模型
fun UserEntity.toDomain(): User {
return User(
id = id,
name = name,
email = email,
createdAt = createdAt
)
}
// 扩展函数:领域模型转数据模型
fun User.toData(): UserEntity {
return UserEntity(
id = id,
name = name,
email = email,
createdAt = createdAt
)
}
3. 领域层(Domain Layer)
// 领域模型
data class User(
val id: String,
val name: String,
val email: String,
val createdAt: Long
)
// 仓库接口(定义在领域层,由数据层实现)
interface UserRepository {
suspend fun getUser(userId: String): NetworkResult<User>
suspend fun saveUser(user: User): NetworkResult<Boolean>
}
// 用例(Use Case)
class GetUserUseCase @Inject constructor(
private val userRepository: UserRepository
) {
// 用例通常只有一个公开方法,表示一个具体的业务操作
suspend operator fun invoke(userId: String): NetworkResult<User> {
// 可以在这里添加业务逻辑,如验证、转换等
return userRepository.getUser(userId)
}
}
class SaveUserUseCase @Inject constructor(
private val userRepository: UserRepository
) {
suspend operator fun invoke(user: User): NetworkResult<Boolean> {
// 使用核心层的字符串扩展函数验证邮箱
if (user.name.isBlank() || !user.email.isValidEmail()) {
return NetworkResult.Error("用户名不能为空,邮箱格式必须正确")
}
return userRepository.saveUser(user)
}
}
4. 表现层(Presentation Layer)
// ViewModel(继承自核心层的BaseViewModel)
class UserViewModel @Inject constructor(
private val getUserUseCase: GetUserUseCase,
private val saveUserUseCase: SaveUserUseCase,
private val dateTimeUtils: DateTimeUtils // 使用核心层组件
) : BaseViewModel() { // 继承自核心层的基础ViewModel
// UI状态
data class UserState(
val user: User? = null,
val formattedDate: String = "",
val isEditable: Boolean = false
)
private val _userState = MutableStateFlow(UserState())
val userState: StateFlow<UserState> = _userState.asStateFlow()
// 加载用户信息
fun loadUser(userId: String) {
viewModelScope.launch {
showLoading() // 使用基类方法
when (val result = getUserUseCase(userId)) {
is NetworkResult.Success -> {
val user = result.data
// 使用核心层的日期工具格式化时间
val formattedDate = dateTimeUtils.formatDateTime(user.createdAt)
_userState.update {
it.copy(user = user, formattedDate = formattedDate)
}
}
is NetworkResult.Error -> {
handleError(result.message) // 使用基类方法
}
NetworkResult.Loading -> {
// 已经在上面设置了loading状态
}
}
hideLoading() // 使用基类方法
}
}
// 保存用户信息
fun saveUser(user: User) {
viewModelScope.launch {
showLoading() // 使用基类方法
when (val result = saveUserUseCase(user)) {
is NetworkResult.Success -> {
if (result.data) {
// 使用核心层的日期工具格式化时间
val formattedDate = dateTimeUtils.formatDateTime(user.createdAt)
_userState.update {
it.copy(user = user, formattedDate = formattedDate)
}
} else {
handleError("保存用户信息失败") // 使用基类方法
}
}
is NetworkResult.Error -> {
handleError(result.message) // 使用基类方法
}
NetworkResult.Loading -> {
// 已经在上面设置了loading状态
}
}
hideLoading() // 使用基类方法
}
}
// 切换编辑模式
fun toggleEditMode() {
_userState.update { it.copy(isEditable = !it.isEditable) }
}
}
// Fragment(继承自核心层的BaseFragment)
class UserProfileFragment : BaseFragment() { // 继承自核心层的基础Fragment
private val viewModel: UserViewModel by viewModels()
private var _binding: FragmentUserProfileBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentUserProfileBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 获取用户ID
val userId = arguments?.getString("USER_ID") ?: return
// 设置保存按钮点击事件
binding.btnSave.setOnClickListener {
val name = binding.etName.text.toString()
val email = binding.etEmail.text.toString()
val user = viewModel.userState.value.user?.copy(
name = name,
email = email
) ?: return@setOnClickListener
viewModel.saveUser(user)
}
// 设置编辑按钮点击事件
binding.btnEdit.setOnClickListener {
viewModel.toggleEditMode()
}
// 观察ViewModel的loading状态
viewLifecycleOwner.lifecycleScope.launch {
viewModel.loading.collect { isLoading ->
showLoading(isLoading) // 使用基类方法
}
}
// 观察ViewModel的error状态
viewLifecycleOwner.lifecycleScope.launch {
viewModel.error.collect { errorMsg ->
errorMsg?.let {
showToast(it) // 使用基类方法
}
}
}
// 观察用户状态
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userState.collect { state ->
updateUI(state)
}
}
}
// 加载用户数据
viewModel.loadUser(userId)
}
private fun updateUI(state: UserViewModel.UserState) {
state.user?.let { user ->
binding.etName.setText(user.name)
binding.etEmail.setText(user.email)
binding.tvCreatedAt.text = "创建时间:${state.formattedDate}"
}
// 根据编辑模式更新UI状态
binding.etName.isEnabled = state.isEditable
binding.etEmail.isEnabled = state.isEditable
binding.btnSave.isVisible = state.isEditable
binding.btnEdit.text = if (state.isEditable) "取消" else "编辑"
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
1.3 Clean Architecture的核心原则
依赖规则:依赖关系只能从外层指向内层,内层不应该知道外层的存在。例如,领域层不应该依赖于数据层或表现层。
关注点分离:每一层都有其特定的职责,不应该承担其他层的职责。
抽象接口:通过接口定义不同层之间的交互,使得各层可以独立开发和测试。
实体独立:核心业务实体应该独立于任何外部框架和库。
可测试性:架构设计应该使得系统的各个部分都易于测试,特别是业务逻辑。
二、Clean Architecture的深层原理
2.1 依赖倒置原则(DIP)
Clean Architecture的核心原理之一是依赖倒置原则,它是SOLID原则中的"D"。这一原则指出:
- 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
- 抽象不应该依赖于细节,细节应该依赖于抽象。
在我们的示例中,UserRepository
接口定义在领域层,而其实现UserRepositoryImpl
位于数据层。这样,领域层(高层模块)不直接依赖于数据层(低层模块),而是依赖于抽象(接口)。
// 领域层定义接口
interface UserRepository {
suspend fun getUser(userId: String): NetworkResult<User>
suspend fun saveUser(user: User): NetworkResult<Boolean>
}
// 数据层实现接口
class UserRepositoryImpl(/*...*/) : UserRepository {
// 实现方法...
}
2.2 单一职责原则(SRP)
Clean Architecture强调每一层、每个组件都应该有一个单一的职责。例如:
- 用例(Use Cases):每个用例只负责一个特定的业务操作。
- 仓库(Repositories):负责协调不同的数据源,但不包含业务逻辑。
- 数据源(Data Sources):只负责与特定的数据提供者(如API、数据库)交互。
- 核心层组件:提供通用功能,如网络状态检测、日期格式化等。
2.3 边界与映射器
Clean Architecture中的一个重要概念是边界(Boundaries)和映射器(Mappers)。不同层之间通常使用不同的数据模型,需要在层与层之间进行转换:
// 数据层模型
data class UserEntity(val id: String, val name: String, val email: String, val createdAt: Long)
// 领域层模型
data class User(val id: String, val name: String, val email: String, val createdAt: Long)
// 表现层模型
data class UserViewState(val id: String, val displayName: String, val email: String, val formattedDate: String, val isEditable: Boolean)
// 数据层到领域层的映射
fun UserEntity.toDomain(): User = User(id, name, email, createdAt)
// 领域层到数据层的映射
fun User.toData(): UserEntity = UserEntity(id, name, email, createdAt)
// 领域层到表现层的映射
fun User.toViewState(formattedDate: String, isEditable: Boolean): UserViewState =
UserViewState(id, name, email, formattedDate, isEditable)
这种映射确保了各层之间的独立性,一层的模型变化不会直接影响其他层。
2.4 依赖注入的作用
依赖注入(DI)在Clean Architecture中扮演着重要角色,它帮助我们实现依赖倒置原则,使得高层模块可以依赖于抽象而非具体实现。在Android中,常用的DI框架有Dagger、Hilt和Koin。
// 使用Hilt进行依赖注入的示例
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
// 核心层组件注入
@Provides
@Singleton
fun provideNetworkUtils(@ApplicationContext context: Context): NetworkUtils {
return NetworkUtils(context)
}
@Provides
@Singleton
fun provideDateTimeUtils(): DateTimeUtils {
return DateTimeUtils()
}
@Provides
@Singleton
fun provideNetworkExceptionHandler(): NetworkExceptionHandler {
return NetworkExceptionHandler()
}
// 数据层组件注入
@Provides
@Singleton
fun provideApiService(): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
@Provides
@Singleton
fun provideUserDao(database: AppDatabase): UserDao {
return database.userDao()
}
@Provides
@Singleton
fun provideRemoteUserDataSource(
apiService: ApiService,
networkExceptionHandler: NetworkExceptionHandler
): RemoteUserDataSource {
return RemoteUserDataSource(apiService, networkExceptionHandler)
}
@Provides
@Singleton
fun provideLocalUserDataSource(
userDao: UserDao,
dateTimeUtils: DateTimeUtils
): LocalUserDataSource {
return LocalUserDataSource(userDao, dateTimeUtils)
}
@Provides
@Singleton
fun provideUserRepository(
remoteDataSource: RemoteUserDataSource,
localDataSource: LocalUserDataSource,
networkUtils: NetworkUtils
): UserRepository {
return UserRepositoryImpl(remoteDataSource, localDataSource, networkUtils)
}
// 领域层组件注入
@Provides
fun provideGetUserUseCase(userRepository: UserRepository): GetUserUseCase {
return GetUserUseCase(userRepository)
}
@Provides
fun provideSaveUserUseCase(userRepository: UserRepository): SaveUserUseCase {
return SaveUserUseCase(userRepository)
}
}
通过依赖注入,我们可以轻松替换组件的实现,例如在测试时使用模拟(Mock)实现,而不需要修改使用这些组件的代码。
三、核心层(Core Layer)的作用与实现
3.1 核心层的定位与职责
核心层(Core Layer)是Clean Architecture中的一个重要组成部分,它提供了整个应用通用的基础设施和工具。核心层的主要职责包括:
- 提供通用基础组件:如基础Activity、Fragment、ViewModel等,减少重复代码。
- 封装第三方库:将第三方库封装在核心层中,使应用其他部分不直接依赖于特定的第三方实现。
- 提供通用工具类:如网络状态检测、日期格式化、字符串处理等工具类。
- 定义通用扩展函数:为Kotlin标准类型或Android框架类提供有用的扩展函数。
- 提供通用UI组件:如加载指示器、错误提示、通用对话框等。
核心层的特点是:
- 被所有其他层依赖:核心层是最基础的层,可以被数据层、领域层和表现层依赖。
- 不依赖其他层:核心层不应该依赖于应用的其他层。
- 稳定性高:核心层的变化应该很少,因为它提供的是基础设施。
3.2 核心层的模块划分
核心层通常可以划分为以下几个模块:
// 1. 基础组件模块
package com.example.core.base
// 基础Activity
abstract class BaseActivity : AppCompatActivity() {
// 通用的Activity功能,如权限处理、主题设置等
}
// 基础Fragment
abstract class BaseFragment : Fragment() {
// 通用的Fragment功能,如视图绑定、生命周期日志等
}
// 基础ViewModel
abstract class BaseViewModel : ViewModel() {
// 通用的ViewModel功能,如错误处理、加载状态等
}
// 基础Adapter
abstract class BaseAdapter<T, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
// 通用的RecyclerView.Adapter功能,如数据操作、点击事件等
}
// 2. 网络模块
package com.example.core.network
// 网络状态监听器
class NetworkStateListener(private val context: Context) {
// 监听网络状态变化的功能
}
// 通用API响应处理
class ApiResponseHandler {
// 处理API响应的通用逻辑
}
// 3. 数据库模块
package com.example.core.database
// 数据库迁移助手
class DatabaseMigrationHelper {
// 处理数据库版本迁移的通用逻辑
}
// 4. 工具类模块
package com.example.core.utils
// 日志工具
class LogUtils {
// 封装日志记录功能,可以根据构建类型切换日志行为
}
// 文件工具
class FileUtils {
// 文件操作相关的通用功能
}
// 5. 扩展函数模块
package com.example.core.extensions
// Context扩展
fun Context.dpToPx(dp: Float): Float {
return dp * resources.displayMetrics.density
}
// View扩展
fun View.setVisible(visible: Boolean) {
visibility = if (visible) View.VISIBLE else View.GONE
}
// 6. UI组件模块
package com.example.core.ui
// 加载对话框
class LoadingDialog(context: Context) : Dialog(context) {
// 自定义加载对话框实现
}
// 通用错误视图
class ErrorView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
// 自定义错误视图实现
}
3.3 核心层与其他层的交互
核心层与其他层的交互主要通过以下方式:
- 继承:其他层的组件可以继承核心层的基础组件,获得通用功能。
// 表现层的Activity继承核心层的BaseActivity
class MainActivity : BaseActivity() {
// 特定于MainActivity的实现
}
// 表现层的ViewModel继承核心层的BaseViewModel
class ProductViewModel : BaseViewModel() {
// 特定于ProductViewModel的实现
}
- 依赖注入:通过依赖注入,其他层可以使用核心层提供的工具类和服务。
// 数据层的仓库使用核心层的网络工具
class ProductRepositoryImpl @Inject constructor(
private val apiService: ApiService,
private val networkUtils: NetworkUtils // 来自核心层
) : ProductRepository {
// 实现方法
}
- 扩展函数:其他层可以直接使用核心层定义的扩展函数。
// 表现层使用核心层的扩展函数
class ProductFragment : Fragment() {
private fun setupUI() {
// 使用核心层定义的扩展函数
binding.errorView.setVisible(viewModel.hasError)
val marginInPx = requireContext().dpToPx(16f)
}
}
3.4 核心层的实际应用示例
下面是一个更完整的核心层实现示例,展示了如何在实际项目中应用核心层:
// 1. 网络状态监听实现
class NetworkMonitor @Inject constructor(
private val context: Context
) {
private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
fun isNetworkAvailable(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
} else {
val networkInfo = connectivityManager.activeNetworkInfo ?: return false
return networkInfo.isConnected
}
}
fun observeNetworkState(): Flow<Boolean> = callbackFlow {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(true)
}
override fun onLost(network: Network) {
trySend(false)
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
} else {
// 对于低版本Android,使用广播接收器监听网络变化
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
trySend(isNetworkAvailable())
}
}
context.registerReceiver(receiver, filter)
awaitClose {
context.unregisterReceiver(receiver)
}
}
}.distinctUntilChanged()
}
// 2. 通用资源管理器
class ResourceProvider @Inject constructor(
private val context: Context
) {
fun getString(@StringRes resId: Int): String {
return context.getString(resId)
}
fun getString(@StringRes resId: Int, vararg formatArgs: Any): String {
return context.getString(resId, *formatArgs)
}
fun getColor(@ColorRes resId: Int): Int {
return ContextCompat.getColor(context, resId)
}
fun getDrawable(@DrawableRes resId: Int): Drawable? {
return ContextCompat.getDrawable(context, resId)
}
}
// 3. 通用错误处理器
class ErrorHandler @Inject constructor(
private val resourceProvider: ResourceProvider
) {
fun getErrorMessage(throwable: Throwable): String {
return when (throwable) {
is IOException -> resourceProvider.getString(R.string.error_network)
is HttpException -> {
when (throwable.code()) {
401 -> resourceProvider.getString(R.string.error_unauthorized)
404 -> resourceProvider.getString(R.string.error_not_found)
else -> resourceProvider.getString(R.string.error_server, throwable.code())
}
}
else -> throwable.message ?: resourceProvider.getString(R.string.error_unknown)
}
}
}
// 4. 通用分页加载器
class PaginationHandler<T : Any> {
fun createPager(
pageSize: Int = 20,
initialKey: Int = 1,
loadData: suspend (page: Int, size: Int) -> List<T>
): Flow<PagingData<T>> {
return Pager(
config = PagingConfig(
pageSize = pageSize,
enablePlaceholders = false,
initialLoadSize = pageSize
),
initialKey = initialKey,
pagingSourceFactory = {
object : PagingSource<Int, T>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
val page = params.key ?: initialKey
return try {
val data = loadData(page, params.loadSize)
LoadResult.Page(
data = data,
prevKey = if (page > 1) page - 1 else null,
nextKey = if (data.isNotEmpty()) page + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, T>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}
}
).flow
}
}
// 5. 通用权限处理器
class PermissionHandler(private val activity: ComponentActivity) {
private val permissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val deniedPermissions = permissions.filter { !it.value }.keys.toList()
currentCallback?.invoke(deniedPermissions.isEmpty(), deniedPermissions)
currentCallback = null
}
private var currentCallback: ((Boolean, List<String>) -> Unit)? = null
fun requestPermissions(
permissions: Array<String>,
callback: (allGranted: Boolean, deniedPermissions: List<String>) -> Unit
) {
if (permissions.all { ContextCompat.checkSelfPermission(activity, it) == PackageManager.PERMISSION_GRANTED }) {
callback(true, emptyList())
return
}
currentCallback = callback
permissionLauncher.launch(permissions)
}
}
// 6. 通用安全存储工具
class SecurePreferences @Inject constructor(
context: Context
) {
private val masterKeyAlias by lazy {
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
}
private val sharedPreferences by lazy {
EncryptedSharedPreferences.create(
"secure_prefs",
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
fun putString(key: String, value: String) {
sharedPreferences.edit().putString(key, value).apply()
}
fun getString(key: String, defaultValue: String = ""): String {
return sharedPreferences.getString(key, defaultValue) ?: defaultValue
}
fun putBoolean(key: String, value: Boolean) {
sharedPreferences.edit().putBoolean(key, value).apply()
}
fun getBoolean(key: String, defaultValue: Boolean = false): Boolean {
return sharedPreferences.getBoolean(key, defaultValue)
}
// 其他类型的存取方法...
fun clear() {
sharedPreferences.edit().clear().apply()
}
}
四、Clean Architecture与核心层的高级用法与进阶技巧
4.1 结合MVVM模式与核心层
Clean Architecture在Android中通常与MVVM(Model-View-ViewModel)模式结合使用,核心层可以提供MVVM模式的基础设施:
// 核心层:MVVM基础设施
package com.example.core.mvvm
// 基础ViewModel,处理通用状态
abstract class BaseViewModel : ViewModel() {
// 通用UI状态
data class UiState(
val isLoading: Boolean = false,
val error: String? = null
)
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
protected fun showLoading() {
_uiState.update { it.copy(isLoading = true, error = null) }
}
protected fun hideLoading() {
_uiState.update { it.copy(isLoading = false) }
}
protected fun handleError(message: String?) {
_uiState.update { it.copy(error = message, isLoading = false) }
}
// 用于处理协程异常的扩展函数
protected fun <T> Flow<T>.handleErrors(): Flow<T> {
return catch { e ->
handleError(e.message)
throw e
}
}
}
// 基础Fragment,处理通用UI逻辑
abstract class BaseFragment<VM : BaseViewModel> : Fragment() {
protected abstract val viewModel: VM
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeUiState()
}
private fun observeUiState() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
handleLoading(state.isLoading)
handleError(state.error)
}
}
}
}
protected open fun handleLoading(isLoading: Boolean) {
// 默认实现,子类可以覆盖
}
protected open fun handleError(error: String?) {
error?.let {
Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show()
}
}
}
// 使用示例
class ProductViewModel(
private val getProductsUseCase: GetProductsUseCase
) : BaseViewModel() {
private val _products = MutableStateFlow<List<Product>>(emptyList())
val products: StateFlow<List<Product>> = _products.asStateFlow()
fun loadProducts() {
viewModelScope.launch {
showLoading()
try {
val result = getProductsUseCase()
_products.value = result
hideLoading()
} catch (e: Exception) {
handleError(e.message)
}
}
}
}
class ProductListFragment : BaseFragment<ProductViewModel>() {
override val viewModel: ProductViewModel by viewModels()
private var _binding: FragmentProductListBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentProductListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 设置RecyclerView
val adapter = ProductAdapter()
binding.recyclerView.adapter = adapter
// 观察产品数据
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.products.collect { products ->
adapter.submitList(products)
}
}
}
// 加载产品数据
viewModel.loadProducts()
}
// 覆盖基类方法,自定义加载状态处理
override fun handleLoading(isLoading: Boolean) {
binding.progressBar.isVisible = isLoading
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
4.2 使用协程和Flow与核心层结合
核心层可以提供协程和Flow的扩展功能,简化异步操作:
// 核心层:协程和Flow扩展
package com.example.core.coroutines
// 协程异常处理器
class CoroutineExceptionHandler @Inject constructor(
private val errorHandler: ErrorHandler
) {
fun handle(throwable: Throwable): String {
return errorHandler.getErrorMessage(throwable)
}
}
// Flow扩展函数
fun <T> Flow<T>.asResult(): Flow<Result<T>> {
return map { Result.success(it) }
.catch { emit(Result.failure(it)) }
}
// 用于处理网络请求的扩展函数
suspend fun <T> safeApiCall(
errorHandler: CoroutineExceptionHandler,
apiCall: suspend () -> T
): Result<T> {
return try {
Result.success(apiCall())
} catch (throwable: Throwable) {
Result.failure(Exception(errorHandler.handle(throwable)))
}
}
// 使用示例
class ProductRepositoryImpl @Inject constructor(
private val apiService: ApiService,
private val coroutineExceptionHandler: CoroutineExceptionHandler
) : ProductRepository {
override suspend fun getProducts(): Result<List<Product>> {
return safeApiCall(coroutineExceptionHandler) {
apiService.getProducts().map { it.toDomain() }
}
}
override fun observeProducts(): Flow<Result<List<Product>>> {
return apiService.observeProducts()
.map { products -> products.map { it.toDomain() } }
.asResult()
}
}
4.3 模块化架构与核心层
Clean Architecture非常适合模块化开发,核心层可以作为基础模块被其他模块依赖:
app/ # 应用模块,包含Android特定代码
├── core/ # 核心层模块
│ ├── base/ # 基础组件
│ ├── network/ # 网络组件
│ ├── database/ # 数据库组件
│ ├── utils/ # 工具类
│ └── ui/ # UI组件
├── data/ # 数据层模块
│ ├── common/ # 共享数据模型和工具
│ ├── user/ # 用户相关数据源和仓库
│ └── product/ # 产品相关数据源和仓库
├── domain/ # 领域层模块
│ ├── common/ # 共享领域模型和工具
│ ├── user/ # 用户相关用例
│ └── product/ # 产品相关用例
└── presentation/ # 表现层模块
├── common/ # 共享UI组件和工具
├── user/ # 用户相关UI
└── product/ # 产品相关UI
在build.gradle
文件中定义模块依赖:
// core模块依赖
dependencies {
// 核心层只依赖Android基础库和第三方库
implementation "androidx.core:core-ktx:1.7.0"
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "com.google.android.material:material:1.5.0"
// 其他第三方库...
}
// data模块依赖
dependencies {
implementation project(':core')
implementation project(':domain')
}
// domain模块依赖
dependencies {
implementation project(':core')
// 不依赖data和presentation模块
}
// presentation模块依赖
dependencies {
implementation project(':core')
implementation project(':domain')
// 不依赖data模块
}
// app模块依赖
dependencies {
implementation project(':core')
implementation project(':data')
implementation project(':domain')
implementation project(':presentation')
}
这种模块化结构确保了依赖规则的遵守,同时核心层作为基础设施被所有其他模块使用。
五、常见问题与解决方案
5.1 过度工程化
问题:Clean Architecture加上核心层可能导致代码量增加,特别是在小型项目中,可能会感觉过度工程化。
解决方案:
- 根据项目规模调整架构复杂度,小项目可以简化层次。
- 可以合并某些层或组件,例如在小项目中可以不使用用例层,直接从ViewModel调用仓库。
- 核心层可以只包含最必要的组件,随着项目发展再逐步扩展。
// 简化版的架构,适用于小型项目
class UserViewModel(
private val userRepository: UserRepository, // 直接依赖仓库,省略用例层
private val networkUtils: NetworkUtils // 只使用核心层的必要组件
) : ViewModel() {
fun loadUser(userId: String) {
viewModelScope.launch {
try {
// 使用核心层的网络工具检查网络状态
if (networkUtils.isNetworkAvailable()) {
val user = userRepository.getUser(userId)
_user.value = user
} else {
_error.value = "网络不可用"
}
} catch (e: Exception) {
_error.value = e.message
}
}
}
// 其他方法...
}
5.2 核心层过度膨胀
问题:随着项目发展,核心层可能会不断添加新功能,导致过度膨胀,变得难以维护。
解决方案:
- 严格控制核心层的职责范围,只包含真正通用的组件。
- 将特定领域的功能放在相应的层中,而不是放在核心层。
- 定期重构核心层,移除不再使用的组件。
- 将核心层进一步模块化,分为多个子模块。
// 核心层的模块化示例
core/
├── core-ui/ # UI相关的核心组件
├── core-network/ # 网络相关的核心组件
├── core-database/ # 数据库相关的核心组件
├── core-utils/ # 通用工具类
└── core-testing/ # 测试相关的核心组件
5.3 数据映射开销
问题:在不同层之间进行数据模型转换可能会增加开发工作量和运行时开销。
解决方案:
- 使用自动映射工具,如MapStruct。
- 在核心层中提供通用的映射工具。
- 使用扩展函数简化映射代码。
// 核心层提供的通用映射工具
package com.example.core.mapper
// 通用映射接口
interface Mapper<I, O> {
fun map(input: I): O
}
// 列表映射扩展函数
fun <I, O> List<I>.mapList(mapper: Mapper<I, O>): List<O> {
return map { mapper.map(it) }
}
// 使用示例
class UserEntityToUserMapper @Inject constructor() : Mapper<UserEntity, User> {
override fun map(input: UserEntity): User {
return User(
id = input.id,
name = input.name,
email = input.email,
createdAt = input.createdAt
)
}
}
class UserRepositoryImpl @Inject constructor(
private val remoteDataSource: RemoteUserDataSource,
private val userMapper: UserEntityToUserMapper
) : UserRepository {
override suspend fun getUsers(): List<User> {
val userEntities = remoteDataSource.getUsers()
// 使用核心层提供的映射扩展函数
return userEntities.mapList(userMapper)
}
}
5.4 测试复杂性
问题:虽然Clean Architecture提高了可测试性,但测试设置可能变得复杂。
解决方案:
- 在核心层提供测试辅助工具。
- 使用依赖注入框架简化测试设置。
- 创建测试工厂方法。
// 核心层的测试辅助工具
package com.example.core.testing
// 测试协程调度器提供者
class TestCoroutineDispatcherProvider : CoroutineDispatcherProvider {
val testDispatcher = TestCoroutineDispatcher()
override val main: CoroutineDispatcher = testDispatcher
override val io: CoroutineDispatcher = testDispatcher
override val default: CoroutineDispatcher = testDispatcher
fun cleanUp() {
testDispatcher.cleanupTestCoroutines()
}
}
// 测试数据工厂
class TestDataFactory {
companion object {
fun createUser(id: String = "user123"): User {
return User(
id = id,
name = "Test User",
email = "test@example.com",
createdAt = System.currentTimeMillis()
)
}
fun createUserEntity(id: String = "user123"): UserEntity {
return UserEntity(
id = id,
name = "Test User",
email = "test@example.com",
createdAt = System.currentTimeMillis()
)
}
// 其他测试数据创建方法...
}
}
// 使用示例:ViewModel测试
class UserViewModelTest {
// 使用核心层的测试工具
private val testDispatcherProvider = TestCoroutineDispatcherProvider()
private val testCoroutineScope = TestCoroutineScope(testDispatcherProvider.testDispatcher)
// 模拟依赖
private val getUserUseCase = mockk<GetUserUseCase>()
private val dateTimeUtils = mockk<DateTimeUtils>()
// 被测试对象
private lateinit var viewModel: UserViewModel
@Before
fun setup() {
Dispatchers.setMain(testDispatcherProvider.testDispatcher)
// 设置模拟行为
every { dateTimeUtils.formatDateTime(any()) } returns "2023-01-01 12:00:00"
// 创建ViewModel实例
viewModel = UserViewModel(getUserUseCase, dateTimeUtils)
}
@Test
fun `loadUser should update state when successful`() = testCoroutineScope.runBlockingTest {
// 准备
val userId = "user123"
val user = TestDataFactory.createUser(userId)
coEvery { getUserUseCase(userId) } returns NetworkResult.Success(user)
// 执行
viewModel.loadUser(userId)
// 验证
val state = viewModel.userState.value
assertEquals(user, state.user)
assertEquals("2023-01-01 12:00:00", state.formattedDate)
assertFalse(viewModel.loading.value ?: true)
assertNull(viewModel.error.value)
}
@After
fun tearDown() {
Dispatchers.resetMain()
testDispatcherProvider.cleanUp()
}
}
5.5 错误的依赖方向
问题:在实现过程中,可能会不小心违反依赖规则,例如让内层依赖外层,或者让核心层依赖其他层。
错误示例:
// 错误:核心层依赖于数据层
class NetworkUtils(private val apiService: ApiService) { // ApiService定义在数据层
// ...
}
// 错误:领域层依赖于表现层
class GetUserUseCase(private val userViewModel: UserViewModel) { // UserViewModel定义在表现层
// ...
}
解决方案:
- 严格遵循依赖规则,核心层和内层只依赖抽象接口,不依赖具体实现。
- 使用依赖注入框架确保正确的依赖方向。
- 定期进行架构审查,确保依赖关系正确。
正确示例:
// 正确:核心层不依赖其他层
class NetworkUtils(private val context: Context) { // 只依赖Android框架
// ...
}
// 正确:领域层依赖于接口,而非具体实现
class GetUserUseCase(private val userRepository: UserRepository) { // 依赖接口
// ...
}
六、为什么Clean Architecture成为主流架构
6.1 解决的问题
Clean Architecture在Android开发中成为主流架构,主要是因为它解决了以下问题:
代码耦合:传统架构中,业务逻辑、UI和数据访问往往紧密耦合,导致代码难以维护和测试。Clean Architecture通过层次分离和依赖规则,降低了代码耦合度。
测试困难:在没有清晰架构的应用中,测试往往需要大量模拟Android框架组件,这使得测试变得复杂且不可靠。Clean Architecture使得核心业务逻辑可以独立测试,不依赖于Android框架。
技术栈更新:Android生态系统发展迅速,新技术和库不断涌现。Clean Architecture使得应用的核心部分与具体技术实现分离,便于适应技术变化。
团队协作:在大型团队中,没有清晰的架构会导致代码冲突和集成问题。Clean Architecture通过明确的职责划分,使得团队成员可以并行工作。
可维护性差:随着应用规模增长,没有良好架构的代码库会变得越来越难以维护。Clean Architecture提供了清晰的结构和规则,使得代码库更易于理解和维护。
6.2 与其他架构的比较
Clean Architecture vs MVC:
- MVC(Model-View-Controller)是一种简单的架构模式,但在Android中,Activity/Fragment往往同时承担View和Controller的职责,导致这些类变得臃肿。
- Clean Architecture通过多层结构和明确的职责划分,避免了这种问题,使得代码更加模块化。
Clean Architecture vs MVP:
- MVP(Model-View-Presenter)改进了MVC,将视图逻辑从Activity/Fragment中分离出来。
- Clean Architecture进一步扩展了这一思想,不仅分离了视图逻辑,还分离了业务逻辑和数据访问逻辑,形成了更完整的架构。
Clean Architecture vs MVVM:
- MVVM(Model-View-ViewModel)使用数据绑定减少了视图和逻辑之间的耦合。
- Clean Architecture可以与MVVM结合使用,MVVM主要解决表现层的问题,而Clean Architecture提供了整体架构框架。
6.3 行业趋势
Clean Architecture成为主流架构的行业趋势包括:
Google官方支持:Google的Android架构组件(如ViewModel、LiveData、Room等)设计时考虑了Clean Architecture的原则,使得实现Clean Architecture变得更加容易。
Kotlin语言特性:Kotlin的特性(如扩展函数、高阶函数、协程等)使得实现Clean Architecture更加简洁和优雅。
依赖注入框架成熟:Dagger、Hilt、Koin等依赖注入框架的成熟,使得实现依赖倒置原则变得更加容易。
响应式编程流行:RxJava、Flow等响应式编程库的流行,使得处理异步操作和数据流变得更加简单,这与Clean Architecture的理念相契合。
测试驱动开发兴起:测试驱动开发(TDD)在Android社区的兴起,推动了开发者采用更易于测试的架构,而Clean Architecture正是这样的架构。
七、总结
7.1 Clean Architecture的优势
关注点分离:通过将系统分为不同的层次,每一层都有明确的职责,使得代码更加模块化和可维护。
依赖规则:内层不依赖外层,使得核心业务逻辑独立于框架和UI,提高了系统的稳定性和可扩展性。
可测试性:由于各层之间通过接口交互,可以轻松替换实现,使得单元测试更加容易。
灵活性:可以轻松替换外层组件,如数据库、UI框架等,而不影响核心业务逻辑。
可维护性:清晰的架构使得代码更易于理解和维护,新开发人员可以更快地上手。
核心层的通用性:核心层提供了整个应用通用的基础设施,减少了重复代码,提高了开发效率。
7.2 Clean Architecture的挑战
学习曲线:相比传统的MVC或简单的MVVM,Clean Architecture有更陡峭的学习曲线。
代码量增加:需要创建更多的类和接口,可能导致代码量增加。
过度工程化风险:在小型项目中可能显得过度工程化。
性能开销:层之间的数据转换可能带来一定的性能开销。
核心层管理:需要谨慎管理核心层,避免其过度膨胀或承担不属于它的职责。
7.3 何时使用Clean Architecture
Clean Architecture特别适合以下情况:
中大型项目:随着项目规模增长,Clean Architecture的优势会更加明显。
长期维护的项目:需要长期维护和迭代的项目可以从清晰的架构中受益。
团队协作:多人团队可以更好地并行开发,减少冲突。
需要高测试覆盖率的项目:对测试要求高的项目可以利用Clean Architecture的高可测试性。
可能需要更换技术栈的项目:如果预计未来可能需要更换数据库、UI框架等,Clean Architecture可以减少迁移成本。
7.4 最佳实践
根据项目规模调整架构复杂度:小项目可以简化层次,大项目则可以完整实现。
使用依赖注入:配合依赖注入框架如Dagger、Hilt或Koin使用。
保持领域层的纯净:领域层不应该依赖于任何外部框架或库。
合理设计核心层:核心层应该只包含真正通用的组件,避免过度膨胀。
使用用例表示业务操作:每个用例应该代表一个具体的业务操作。
合理使用映射器:在层之间使用映射器转换数据模型,保持各层的独立性。
编写单元测试:利用Clean Architecture的高可测试性,为各层编写单元测试。
避免过度抽象:只在必要时创建抽象,避免不必要的复杂性。
定期审查架构:定期检查是否遵循了依赖规则和关注点分离原则。
Clean Architecture作为一种成熟的架构模式,已经在Android开发社区得到了广泛的认可和应用。通过合理地实施Clean Architecture,包括核心层的设计,可以构建出更加健壮、可维护和可测试的Android应用。无论是初学者还是有经验的开发者,掌握Clean Architecture都将有助于提升代码质量和开发效率。