本文旨在把 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
其实还有很多方法来构建文件/文件夹层次结构。平时更偏好根据功能对项目文件进行分组。这样简洁明了。
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: SavePostfun  getAllPosts (userId: Int , callback: PostDataSource .LoadPostsCallback ) val  requestValue = GetPosts.RequestValues(userId)object  : UseCase.UseCaseCallback<GetPosts.ResponseValue> {override  fun  onSuccess (response: GetPosts .ResponseValue ) override  fun  onError (t: Throwable ) fun  savePost (post: Post , callback: PostDataSource .SaveTaskCallback ) val  requestValues = SavePost.RequestValues(post)object  : UseCase.UseCaseCallback<SavePost.ResponseValue> {override  fun  onSuccess (response: SavePost .ResponseValue ) override  fun  onError (t: Throwable ) 
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 () 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 >) this )private  fun  <V : UseCase.ResponseValue>  notifyResponse (response: V , useCaseCallback: UseCase .UseCaseCallback <V >) private  fun  <V : UseCase.ResponseValue>  notifyError (             useCaseCallback: UseCase .UseCaseCallback <V >, t: Throwable ) private  class  UiCallbackWrapper <V : UseCase.ResponseValue >private  val  mCallback: UseCase.UseCaseCallback<V>,private  val  mUseCaseHandler: UseCaseHandler) : UseCase.UseCaseCallback<V> {override  fun  onSuccess (response: V ) override  fun  onError (t: Throwable ) companion  object  {private  var  INSTANCE: UseCaseHandler? = null fun  getInstance () if  (INSTANCE == null ) {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) :protected  override  fun  executeUseCase (requestValues: GetPosts .RequestValues ?) 1 , object  : PostDataSource.LoadPostsCallback {override  fun  onPostsLoaded (posts: List <Post >) val  responseValue = ResponseValue(posts)override  fun  onError (t: Throwable ) "Data not found" ))class  RequestValues val  userId: Int ) : UseCase.RequestValuesclass  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: PostDataSourcecompanion  object  {private  var  INSTANCE: PostDataRepository? = null fun  getInstance (localDataSource: PostDataSource , remoteDataSource: PostDataSource ) if  (INSTANCE == null ) {return  INSTANCE!!var  isCacheDirty = false override  fun  getPosts (userId: Int , callback: PostDataSource .LoadPostsCallback ) if  (isCacheDirty) {else  {object  : PostDataSource.LoadPostsCallback {override  fun  onPostsLoaded (posts: List <Post >) override  fun  onError (t: Throwable ) override  fun  savePost (post: Post ) private  fun  getPostsFromServer (userId: Int , callback: PostDataSource .LoadPostsCallback ) object  : PostDataSource.LoadPostsCallback {override  fun  onPostsLoaded (posts: List <Post >) override  fun  onError (t: Throwable ) private  fun  refreshLocalDataSource (posts: List <Post >) private  fun  refreshCache () 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: ThreadPoolExecutorinit  {override  fun  execute (runnable: Runnable ) override  fun  <V : UseCase.ResponseValue>  notifyResponse (response: V , useCaseCallback: UseCase .UseCaseCallback <V >) override  fun  <V : UseCase.ResponseValue>  onError (useCaseCallback: UseCase .UseCaseCallback <V >, t: Throwable ) 
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 >) if  (modelClass == PostListViewModel::class .java) {return  PostListViewModel(as  Tthrow  IllegalArgumentException("unknown model class $modelClass " )companion  object  {private  var  INSTANCE: ViewModelFactory? = null fun  getInstance () if  (INSTANCE == null ) {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 () return  PostDataRepository.getInstance(provideLocalDataSource(), provideRemoteDataSource())fun  provideViewModelFactory () fun  provideLocalDataSource () fun  provideRemoteDataSource () fun  provideGetPosts () fun  provideSavePost () fun  provideUseCaseHandler () 
强烈建议使用 Hilt,超出了本文范围,可以参考这里 
MVVM 结合 Clean:牢固的组合 这个案例的目的是了解具有 Clean 架构的 MVVM,因此跳过了一些可以尝试进一步改进的内容:
使用 Coroutine 删除回调并使其更加整洁。 
使用 Flow 来表示 UI。 
使用 Hilt 注入依赖项。 
 
这就是 Android 应用程序最好、最具可扩展性的架构之一!