本文旨在把 MVVM 与 Clean 架构结合起来,指导我们怎样使用此架构来编写解耦、可测试和可维护的代码。
为啥要把 Clean 和 MVVM 结合起来 MVVM 旨在把视图(Activity / Fragments)和业务逻辑(ViewModel)拆分出来,这对小型项目已经足够了,但随着代码的增长,ViewModel 会逐渐变得臃肿庞大,对业务逻辑进行职责分离会变得越发吃力。
在这种情况下,MVVM 结合 Clean Architecture 便登场了,它进一步分离了代码的职责,明确地抽象了程序执行的操作逻辑。
注意:其实也可以将 Clean Architecture 与 model-view-presenter (MVP) 架构结合起来。但由于 Android Jetpack 已经提供了内置的 ViewModel,因此我们将使用 MVVM 而不是 MVP。
使用 Clean 带来的优势
代码进一步解耦(最大的优势)
比 MVVM 更容易测试
包结构导航变得容易
工程容易维护
可以更快的添加新 features
Clean 架构带来的缺点
稍微陡峭的学习曲线,所有层如何协同工作可能需要一些时间来理解,特别是如果我们之前基于简单的 MVVM or MVP 模式。
添加了很多额外的类,这对于低复杂度的项目来说并不理想。
接下来举个例子,允许用户创建新帖子并查看他们创建的帖子列表。为了简单起见,示例中没有使用其他库(如 Hilt、Coroutine 等)。
具有简洁架构的 MVVM 各层,代码分为三个独立的层:
展示层 Present Layer
领域层 Domain Layer
数据层 Data Layer
下面详细介绍每一层。目前,例子中生成的包结构如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ├── UseCase.kt ├── UseCaseHandler.kt ├── UseCasesScheduler.kt ├── UseCasesThreadPoolScheduler.kt ├── data │ ├── model │ │ └── Post.kt │ └── source │ ├── PostDataRepository.kt │ ├── PostDataSource.kt │ ├── local │ │ └── LocalDataSource.kt │ └── remote │ └── RemoteDataSource.kt ├── ui │ ├── domain │ │ ├── GetPosts.kt │ │ └── SavePost.kt │ └── postlist │ ├── PostListActivity.kt │ ├── PostListAdapter.kt │ └── PostListViewModel.kt └── utils ├── Injection.kt └── ViewModelFactory.kt
其实还有很多方法来构建文件/文件夹层次结构。平时更偏好根据功能对项目文件进行分组。这样简洁明了。
Present Layer Present Layer 包括 Activity
、Fragment
和 ViewModel
。其中 Activity
应该尽可能单一。永远不要将业务逻辑放在 Activity
当中。Activity
与 ViewModel
会话,ViewModel
将与 Domain Layer 执行操作。 ViewModel
从不直接与 Data Layer
会话。 这里将一个 UseCaseHandler
和两个 UseCases
传递给的 ViewModel
。在这个架构中,UseCase
是一个定义 ViewModel
如何与数据层交互的操作。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class PostListViewModel ( val useCaseHandler: UseCaseHandler, val getPosts: GetPosts, val savePost: SavePost ): ViewModel() { fun getAllPosts (userId: Int , callback: PostDataSource .LoadPostsCallback ) { val requestValue = GetPosts.RequestValues(userId) useCaseHandler.execute( getPosts, requestValue, object : UseCase.UseCaseCallback<GetPosts.ResponseValue> { override fun onSuccess (response: GetPosts .ResponseValue ) { callback.onPostsLoaded(response.posts) } override fun onError (t: Throwable ) { callback.onError(t) } } ) } fun savePost (post: Post , callback: PostDataSource .SaveTaskCallback ) { val requestValues = SavePost.RequestValues(post) useCaseHandler.execute( savePost, requestValues, object : UseCase.UseCaseCallback<SavePost.ResponseValue> { override fun onSuccess (response: SavePost .ResponseValue ) { callback.onSaveSuccess() } override fun onError (t: Throwable ) { callback.onError(t) } } ) } }
Domain Layer Domain Layer 包含应用程序的所有用例。在此示例中,有 UseCase
,一个抽象类。所有的 UseCase
都将扩展这个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 abstract class UseCase <Q : UseCase.RequestValues, P : UseCase.ResponseValue > { var requestValues: Q? = null var useCaseCallback: UseCaseCallback<P>? = null internal fun run () { executeUseCase(requestValues) } protected abstract fun executeUseCase (requestValues: Q ?) interface RequestValues interface ResponseValue interface UseCaseCallback <R > { fun onSuccess (response: R ) fun onError (t: Throwable ) } }
UseCaseHandler
处理 UseCase
的执行。当从数据库或远程服务器获取数据时,不应该阻塞 UI。这是决定在后台线程上执行 UseCase
并在主线程上接收响应的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class UseCaseHandler (private val mUseCaseScheduler: UseCaseScheduler) { fun <T : UseCase.RequestValues, R : UseCase.ResponseValue> execute ( useCase: UseCase <T , R>, values: T , callback: UseCase .UseCaseCallback <R >) { useCase.requestValues = values useCase.useCaseCallback = UiCallbackWrapper(callback, this ) mUseCaseScheduler.execute(Runnable { useCase.run() }) } private fun <V : UseCase.ResponseValue> notifyResponse (response: V , useCaseCallback: UseCase .UseCaseCallback <V >) { mUseCaseScheduler.notifyResponse(response, useCaseCallback) } private fun <V : UseCase.ResponseValue> notifyError ( useCaseCallback: UseCase .UseCaseCallback <V >, t: Throwable ) { mUseCaseScheduler.onError(useCaseCallback, t) } private class UiCallbackWrapper <V : UseCase.ResponseValue > ( private val mCallback: UseCase.UseCaseCallback<V>, private val mUseCaseHandler: UseCaseHandler) : UseCase.UseCaseCallback<V> { override fun onSuccess (response: V ) { mUseCaseHandler.notifyResponse(response, mCallback) } override fun onError (t: Throwable ) { mUseCaseHandler.notifyError(mCallback, t) } } companion object { private var INSTANCE: UseCaseHandler? = null fun getInstance () : UseCaseHandler { if (INSTANCE == null ) { INSTANCE = UseCaseHandler(UseCaseThreadPoolScheduler()) } return INSTANCE!! } } }
顾名思义,GetPosts
, UseCase
负责获取用户的所有帖子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class GetPosts (private val mDataSource: PostDataSource) : UseCase<GetPosts.RequestValues, GetPosts.ResponseValue>() { protected override fun executeUseCase (requestValues: GetPosts .RequestValues ?) { mDataSource.getPosts( requestValues?.userId ?: -1 , object : PostDataSource.LoadPostsCallback { override fun onPostsLoaded (posts: List <Post >) { val responseValue = ResponseValue(posts) useCaseCallback?.onSuccess(responseValue) } override fun onError (t: Throwable ) { useCaseCallback?.onError(Throwable("Data not found" )) } } ) } class RequestValues (val userId: Int ) : UseCase.RequestValues class ResponseValue (val posts: List<Post>) : UseCase.ResponseValue }
用例 UseCase
的目的是成为 ViewModel
和 Repository
之间的中介。
假设将来决定添加 “编辑帖子” 功能。所要做的就是添加一个新的 EditPost
UseCase
,它的所有代码将与其他 UseCase
完全分离和解耦。引入新功能,但无意中破坏了现有代码中的某些内容。创建一个单独的 UseCase
有助于极大地避免这种情况。
当然,无法 100% 消除这种可能性,但肯定可以将其最小化。这就是 Clean 架构与其他模式的区别:代码如此解耦,以至于可以将每一层视为黑匣子。
数据层 该层向外部类公开数据源 API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface PostDataSource { interface LoadPostsCallback { fun onPostsLoaded (posts: List <Post >) fun onError (t: Throwable ) } interface SaveTaskCallback { fun onSaveSuccess () fun onError (t: Throwable ) } fun getPosts (userId: Int , callback: LoadPostsCallback ) fun savePost (post: Post ) }
PostDataRepository
实现 PostDataSource
。它决定是从本地数据库还是远程服务器获取数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class PostDataRepository private constructor ( private val localDataSource: PostDataSource, private val remoteDataSource: PostDataSource ): PostDataSource { companion object { private var INSTANCE: PostDataRepository? = null fun getInstance (localDataSource: PostDataSource , remoteDataSource: PostDataSource ) : PostDataRepository { if (INSTANCE == null ) { INSTANCE = PostDataRepository(localDataSource, remoteDataSource) } return INSTANCE!! } } var isCacheDirty = false override fun getPosts (userId: Int , callback: PostDataSource .LoadPostsCallback ) { if (isCacheDirty) { getPostsFromServer(userId, callback) } else { localDataSource.getPosts(userId, object : PostDataSource.LoadPostsCallback { override fun onPostsLoaded (posts: List <Post >) { refreshCache() callback.onPostsLoaded(posts) } override fun onError (t: Throwable ) { getPostsFromServer(userId, callback) } }) } } override fun savePost (post: Post ) { localDataSource.savePost(post) remoteDataSource.savePost(post) } private fun getPostsFromServer (userId: Int , callback: PostDataSource .LoadPostsCallback ) { remoteDataSource.getPosts(userId, object : PostDataSource.LoadPostsCallback { override fun onPostsLoaded (posts: List <Post >) { refreshCache() refreshLocalDataSource(posts) callback.onPostsLoaded(posts) } override fun onError (t: Throwable ) { callback.onError(t) } }) } private fun refreshLocalDataSource (posts: List <Post >) { posts.forEach { localDataSource.savePost(it) } } private fun refreshCache () { isCacheDirty = false } }
这个类有两个变量:localDataSource
和remoteDataSource
。它们的类型是 PostDataSource
,所以不用关心它们实际上是如何实现的。
如果想要调整远程数据源,所要做的就是更改 RemoteDataSource
中的实现。这样不必接触任何其他类。这就是解耦代码的优点。更改任何给定的类不应影响代码的其他部分。
另外还有一些额外的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 interface UseCaseScheduler { fun execute (runnable: Runnable ) fun <V : UseCase.ResponseValue> notifyResponse (response: V , useCaseCallback: UseCase .UseCaseCallback <V >) fun <V : UseCase.ResponseValue> onError (useCaseCallback: UseCase .UseCaseCallback <V >, t: Throwable ) }class UseCaseThreadPoolScheduler : UseCaseScheduler { val POOL_SIZE = 2 val MAX_POOL_SIZE = 4 val TIMEOUT = 30 private val mHandler = Handler() internal var mThreadPoolExecutor: ThreadPoolExecutor init { mThreadPoolExecutor = ThreadPoolExecutor( POOL_SIZE, MAX_POOL_SIZE, TIMEOUT.toLong(), TimeUnit.SECONDS, ArrayBlockingQueue(POOL_SIZE) ) } override fun execute (runnable: Runnable ) { mThreadPoolExecutor.execute(runnable) } override fun <V : UseCase.ResponseValue> notifyResponse (response: V , useCaseCallback: UseCase .UseCaseCallback <V >) { mHandler.post { useCaseCallback.onSuccess(response) } } override fun <V : UseCase.ResponseValue> onError (useCaseCallback: UseCase .UseCaseCallback <V >, t: Throwable ) { mHandler.post { useCaseCallback.onError(t) } } }
UseCaseThreadPoolScheduler
负责使用 ThreadPoolExecuter
异步执行任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class ViewModelFactory : ViewModelProvider.Factory { override fun <T : ViewModel> create (modelClass: Class <T >) : T { if (modelClass == PostListViewModel::class .java) { return PostListViewModel( Injection.provideUseCaseHandler(), Injection.provideGetPosts(), Injection.provideSavePost() ) as T } throw IllegalArgumentException("unknown model class $modelClass " ) } companion object { private var INSTANCE: ViewModelFactory? = null fun getInstance () : ViewModelFactory { if (INSTANCE == null ) { INSTANCE = ViewModelFactory() } return INSTANCE!! } } }
ViewModelFactory
负责创建 ViewModel
构造函数中传递必要的参数。
依赖注入 再用一个例子来解释依赖注入。查看 PostDataRepository
类,它有两个依赖项:LocalDataSource
和 RemoteDataSource
。使用 Injection
类向 PostDataRepository
类提供这些依赖项。
注入依赖有两个主要优点。
一是可以从一个中心位置控制对象的实例化,而不是将其分散到整个代码库。
另一个原因是,这将帮助我们为 PostDataRepository
编写单元测试,因为只需将 LocalDataSource
和 RemoteDataSource
的 Mock 版本传递给 PostDataRepository
构造函数而不是实际值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 object Injection { fun providePostDataRepository () : PostDataRepository { return PostDataRepository.getInstance(provideLocalDataSource(), provideRemoteDataSource()) } fun provideViewModelFactory () = ViewModelFactory.getInstance() fun provideLocalDataSource () : PostDataSource = LocalDataSource.getInstance() fun provideRemoteDataSource () : PostDataSource = RemoteDataSource.getInstance() fun provideGetPosts () = GetPosts(providePostDataRepository()) fun provideSavePost () = SavePost(providePostDataRepository()) fun provideUseCaseHandler () = UseCaseHandler.getInstance() }
强烈建议使用 Hilt,超出了本文范围,可以参考这里
MVVM 结合 Clean:牢固的组合 这个案例的目的是了解具有 Clean 架构的 MVVM,因此跳过了一些可以尝试进一步改进的内容:
使用 Coroutine 删除回调并使其更加整洁。
使用 Flow 来表示 UI。
使用 Hilt 注入依赖项。
这就是 Android 应用程序最好、最具可扩展性的架构之一!