原文地址:https://listenzz.github.io/android-architecture-part-4-applying-clean-architecture-on-android-hands-on

[译] Android 架构:Part 4 —— 实践 Clean Architecture(含源码)_第1张图片

在 Android 架构系列的最后部分,我们将 Clean Architecture 调整到 Android 平台。我们将 Android 和真实世界从业务逻辑中分离,令利益相关者满意,使一切都容易测试。

理论很棒,但是当我们创建一个新 Android 项目时,该从哪开始呢?让我们用整洁代码弄脏我们的手,把空白画布变成一个架构。

基础

让我们先做一些基础工作 —— 创建模块并建立依赖关系,使其与依赖规则保持一致。

这些将是我们的模块,从抽象到具体:

1. domain

实体、用例、仓库接口和设备接口放入 domain 模块。

理想情况下,实体和业务逻辑应该是平台无关的。为了安全起见,为了防止我们在这里放置一些 Android 的东西,我们将使它成为一个纯 java 模块。

2. data

data 模块应当持有与数据持久化和操作相关的所有内容。在这里我们可以找到 DAO、ORM、SharedPreferences、网络相关的比如 Retrofit Service 或类似的东西。

3. device

device 模块应该拥有与 Android 相关的所有东西(除了数据持久化 和 UI)。例如 ConnectivityManager, NotificationManager 和 misc 传感器的封装类。

我们将使 data 和 device 模块成为 Android 模块,因为它们必须知道 Android,不能是纯的 java。

4. 最容易的部分,app 模块(UI 模块)

当你创建项目时,该模块已经由 Android Studio 为你创建好了。

在这里,你可以放置与 Android UI 相关的类,譬如 Presenter,Controller,ViewModel,Adapter 以及 View。

依赖

依赖规则定义了具体模块依赖于抽象模块。

你可能会记得本系列的第三部分,UI(app),DB-API(data)以及设备(device)的东西都在外层。这意味着它们在同一抽象级别。那么我们该如何将它们串在一起呢?

理想的情况下,这些模块仅依赖于领域(domain)模块。在这种情况下,依赖关系看起来有点像一颗星:

[译] Android 架构:Part 4 —— 实践 Clean Architecture(含源码)_第2张图片

但是这里我们涉及 Android 所以事情就不那么完美了。因为我们需要创建对象依赖图以及初始化一些东西,模块有时依赖另一个模块而不是领域模块。

例如,我们需要在 app 模块创建依赖注入的对象依赖图,这就迫使 app 模块需要知道其余所有模块。

我们调整后的依赖关系图:

[译] Android 架构:Part 4 —— 实践 Clean Architecture(含源码)_第3张图片

砖,更多的砖

终于,是时候写些代码了。为了容易演示,我们将以 RSS 阅读器 app 为例。我们的用户应该能够管理他们的 RSS Feed 订阅,从 Feed 中获取文章并阅读它们。

领域

让我们从领域层开始,创建我们的核心业务模型和逻辑。

我们的业务模型非常简单:

  • Feed - 持有 RSS Feed 相关数据比如 url、缩略 URL、标题和描述
  • Article - 持有文章相关数据比如文章标题、url 和发表时间

至于我们的逻辑,我们将使用 UseCase 也就是交互器。它们将简单类中的小部分业务逻辑封装起来。它们都会实现一般的 UseCase 协议:

public interface UseCase

{ interface Callback { void onSuccess(); void onError(Throwable throwable); } void execute(P parameter, Callback callback); }

当用户打开我们的 app 要做的第一件事情是添加一个新的 RSS Feed 订阅。所以从我们的交互器开始,我们创建 AddNewFeedUseCase,以及处理 feed 添加和验证逻辑的辅助类。

AddNewFeedUseCase 使用 FeedValidator 来检查 feed URL 的有效性,我们还将创建 FeedRepository 协议,它为我们的业务逻辑提供基础的增删改查能力来管理 feed 数据:

public interface FeedRepository {    int createNewFeed(String feedUrl);    List getUserFeeds();    List getFeedArticles(int feedId);    boolean deleteFeed(int feedId);}

注意我们在领域层的命名是如何清晰地说明我们的 app 是做什么的。

把所有东西放在一起,我们的 AddNewFeedUseCase 看起来像这样:

public final class AddNewFeedUseCase implements UseCase {   private final FeedValidator feedValidator;   private final FeedRepository feedRepository;   @Override   public void execute(final String feedUrl, final Callback callback) {       if (feedValidator.isValid(feedUrl)) {           onValidFeedUrl(feedUrl, callback);       } else {           callback.onError(new InvalidFeedUrlException());       }   }   private void onValidFeedUrl(final String feedUrl, final Callback callback) {       try {           feedRepository.createNewFeed(feedUrl);           callback.onSuccess();       } catch (final Throwable throwable) {           callback.onError(throwable);       }   }}

为了简洁起见,省略了构造函数。

现在,你可能会困惑,为什么我们的用例和回调是一个接口?

为了更好地演示我们下一个问题,让我们来研究研究 GetFeedArticlesUseCase

获得 feedId -> 通过 FeedRepository 抓取 feed 文章 -> 返回 feed 文章列表

这是数据流问题,用例位于表现层和数据层之间,我们怎样建立起层和层之间的通信?记得那些输入和输出端口吗?

[译] Android 架构:Part 4 —— 实践 Clean Architecture(含源码)_第4张图片

我们的用例必须实现输入端口(interface)。Presenter 调用用例的方法,数据流向用例(feedId)。用例将 feedId 转换成 feed 文章列表,并希望将其返回给表现层。它拥有指向输出端口(Callback)的引用,因为输出端口是定义在同一层的,所以它调用了输出端口的一个方法。因此数据将发送到输出端口 —— Presenter。

我们稍微调整一下 UseCase 协议:

public interface UseCase {   interface Callback {       void onSuccess(R return);       void onError(Throwable throwable);   }   void execute(P parameter, Callback callback);}public interface CompletableUseCase

{ interface Callback { void onSuccess(); void onError(Throwable throwable); } void execute(P parameter, Callback callback);}

UseCase 接口是输入端口,而 Callback 接口是输出端口。

GetFeedArticlesUseCase 实现如下:

class GetFeedArticlesUseCase implements UseCase {   private final FeedRepository feedRepository;   @Override   public void execute(final Integer feedId, final Callback callback) {       try {           callback.onSuccess(feedRepository.getFeedArticles(feedId));       } catch (final Throwable throwable) {           callback.onError(throwable);       }   } }

最后一件领域层需要注意的事情是,交互器只应该包含业务逻辑。在这样做时,它们可以使用 Repository,组合其它交互器,使用类似我们例子中 FeedValidator 这样的公共设施类。

UI

很好,我们可以抓取文章,现在让我们向用户展示它们。

我们的 View 有一个简单的协议:

interface View {   void showArticles(List feedArticles);   void showErrorMessage();   void showLoadingIndicator();}

此 View 的 Presenter 的表现逻辑非常简单。它抓取文章,转换成视图模型传递给 View,简单吧,对吗?

简单的 Presenter 是 Clean Architecture 和 表现 —— 业务逻辑分离的另一个伟大成就。

这是我们的 FeedArticlesPresenter

class FeedArticlesPresenter implements UseCase.Callback {   private final GetFeedArticlesUseCase getFeedArticlesUseCase;   private final ViewModeMapper viewModelMapper;   public void fetchFeedItems(final int feedId) {       getFeedArticlesUseCase.execute(feedId, this);   }   @Override   public void onSuccess(final List articles) {       getView().showArticles(viewModelMapper.mapArticlesToViewModels(articles));   }   @Override   public void onError(final Throwable throwable) {       getView().showErrorMessage();   } }

注意 FeedArticlesPresenter 实现了 Callback 接口,并将自身传递给用例,它实际上是用例的输出端口,并以这种方式关闭数据流。这是我们前面提到过的数据流的具体例子,我们可以在流程图上调整标签来匹配这个例子:

[译] Android 架构:Part 4 —— 实践 Clean Architecture(含源码)_第5张图片

我们的参数 P 是整数 feedId,返回类型 R 是文章列表。

你不一定必须使用 Presenter 来处理表现逻辑,我们可以说,Clean Architecture 是“前端”无关的 —— 这意味着你可以使用 MVP,MVC,MVVM 或其他任何东西。

我们来加点 Rx

如果你想知道为什么会有这样关于 RxJava 的炒作,那么来看看我们的用例的响应式实现:

public interface UseCase {   Single execute(P parameter);         }public interface CompletableUseCase

{ Completable execute(P parameter);}

回调接口现在已经消失,我们使用 RxJava Single / Completable 接口作为输出端口。

响应式 FeedArticlePresenter 的实现如下:

class FeedArticlesPresenter {   private final GetFeedArticlesUseCase getFeedArticlesUseCase;   private final ViewModeMapper viewModelMapper;   public void fetchFeedItems(final int feedId) {       getFeedItemsUseCase.execute(feedId)                  .map(feedViewModeMapper::mapFeedItemsToViewModels)                  .subscribeOn(Schedulers.io())                  .observeOn(AndroidSchedulers.mainThread())                  .subscribe(this::onSuccess, this::onError);   }   private void onSuccess(final List articleViewModels) {      getView().showArticles(articleViewModels);   }   private void onError(final Throwable throwable) {      getView().showErrorMessage();   }}

虽然有点隐蔽,相同的数据流反转原则仍然存在,因为没有 RxJava,Presenter 会实现回调,而使用 RxJava, 订阅者也包含在外层 —— Presenter 的某个地方。

译者注:如果你打算用 ViewModel 取代 Presenter,并且在项目中使用了 RxJava,那么向你安利 使用 RxCommand 在 Android 上实现 MVVM。

Data and Device

data 和 device 模块包含所有业务逻辑不关心的实现细节。它只关系协议,使你容易测试,以及在不触及业务逻辑的情况下更换实现。

这里,你可以使用你喜欢的 ORM 或 DAO 来存储本地数据,使用你喜欢的网络服务来从网络获取数据。我们将实现 FeedService 来拉取文章,使用 FeedDao 来存储文章数据到设备。

每个数据源(网络和本地存储)都有自己的模型来处理。

在我们的例子中,它们是 ApiFeed - ApiArticle 和 DbFeed - DbArticle。

FeedRepository 的具体实现也可以在 data 模块中找到。

device 模块持有 Notifications 协议的实现,就是对 NotificationManager 类的一个包装。当有新的用户可能感兴趣并参与的文章发表时,我们会在我们的业务逻辑中使用 Notifications 来向用户显示一个通知。

模型,到处都是模型

你可能已经注意到,我们提及的模型不仅仅是实体或业务。

实际上,我们还有 db 模型,API 模型,视图模型,当然还有业务模型。

每一层都有自己的模型是个不错的实践,这样你的具体细节,譬如视图,就不需要依赖低层实现的具体细节。通过这种方式,举个例子,当你决定更换 ORM 框架时,你就不需要破坏不相干的代码。

为了确保这点,有必要在每个层中使用对象映射器。在我们的例子中,我们使用 ViewModelMapper 将 demain 模块中的 Article 模型映射成 ArticleViewModel

总结

遵循这些准则,我们创建了健壮且通用的架构。首先,看起要写好多代码,确实也是,但是记住,我们是为未来的变化和功能搭建我们的架构。如果你正确地做了,未来你会感谢当初的决定。

在下一部分中,我们会介绍可能是这个架构中最重要的部分,它的可测试性以及如何测试它。那么,在此期间,你对架构实现的哪部分最感兴趣呢?

Part I

Part II

part III

源码

原文

更多相关文章

  1. android phone模块 4.2平台和4.4平台上的不同
  2. Android中因为没有使用wifi模块 因此:将WIDGETS-->Settings sh...
  3. android 拨号盘Contact模块讲解(四)
  4. android开心网语音发送模块的录音功能
  5. OpenGL ES教程V之更多3D模型(原文对照)
  6. Android React Native使用原生模块
  7. Android构建模块
  8. Android引入广播机制的用意。单线程模型Message、Handler、Messa
  9. Android 5.1 Settings模块源码分析

随机推荐

  1. android设备唯一码的获取,cpu号,mac地址
  2. eoe android客户端源码剖析(二)侧滑菜单Sli
  3. Retrofit 2.0 超能实践(一),okHttp完美支持H
  4. Android(安卓)JUnit test 进行自动化测试
  5. Android(安卓)USB 主机(Host)和配件(Accesso
  6. Android studio如何集成ShareSDK详解
  7. 提高Android在eclipse下的编译速度
  8. Windows下搭建android开发环境
  9. Android_android的测试工具CTS
  10. RatingBar的使用方法