什么是优雅的安卓架构设计?

安卓应用架构

Posted by Luyao on August 21, 2017 Views

原文作者:Fernando Cejas

原文标题:Architecting Android…The clean way?

原文地址:https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/

Architecting Android…The clean way?

在过去几个月里,与我的同事 @pedro_g_s@flipper83Tuenti 上进行几次关于安卓的讨论之后,我觉得是时候写一篇关于 安卓应用架构设计 的文章了。

这篇文章的目的是告诉你一些我过去几个月之间的想法和我在探讨实现它的过程中学到的东西。

开始

我们知道编写高质量的软件是困难且复杂的: 它并不仅仅只是满足需求,还应该是健壮的,可维护的,易测试的,并且足够灵活以适应增长和改变。这就是 “优雅的架构设计” ,我们在开发任何软件应用时都应该遵守。

规范很简单:优雅的架构设计为了产生如下所示的系统:

  • 独立于框架
  • 可测试的
  • 独立于 UI
  • 独立于数据库
  • 独立于任意外部模块

并不是必须要使用4个圆圈(正如你上面所看到的),因为这只是一个概要设计,但是你需要认真考虑一下 依赖规则:源代码的依赖仅仅只是指向内部的,在一个内部圆中不知道外部圆的任何事情。

下面这些词汇可以让你更好的理解:

  • Entities: 这些是应用中的业务对象
  • Use Cases: 这些用例控制数据流和对象的转换,也被称为交互件(Interactors)
  • Interface Adapters: 对于用例和业务对象,适配器更加方便的从格式化中转换数据,Presenters 和 Controllers 都属于这一层
  • Frameworks and Drivers: 所有的具体细节:UI,工具,框架等等

更深入的了解相关知识,参见 这篇文章 或者 这个视频

我们的方案

我将以一个小案例继续下去:简单的创建一个App展示云端的好友列表,当点击其中任意一个时,会有一个新的界面展示详细信息。 Here is a quick video:

安卓架构设计

我们的目的是关系分离,保证业务层不知道外部的任何东西。因此,它们可以不需要任何外部元素的依赖就可以进行测试。

为了达到这个目的,我的建议是将工程分为3层,每一层都有它自己的目的并且可以脱离其他层独立工作。

值得一提的是每一层使用的是自己的数据模型,所以它的独立性是可以保证的(你将在代码中看到,为了完成数据转换需要一个数据映射器(data mapper),这是你为了不在整个应用中交叉使用模型类付出的代价)。

看一下下面这个方案:

Note: 我没有使用任何外部库(除了解析 json 数据的 gson,测试用的junit, mockito, robolectric and espresso)。它们例子更加清晰。无论如何不要犹豫添加 ORMs 保存磁盘数据,或者一些依赖注入框架和其他一些你熟悉的,它们将给你带来便利。(记着重造轮子不是一个好选择)。

Presentation 层

视图和动画相关的逻辑在这里发生。它只使用了 Model View PresenterMVP),你也可以使用其他的模式,例如 MVC 和 MVVM。我不会详细说明 MVP 。fragments 和 activities 仅仅只是 view,其中只有 UI 逻辑,并且这里也是所有的渲染发生的地方。

如果你想要一个使用 MVP 和 MVVM 的例子,看看 Effective Android UI,看看我的朋友是怎么做的。

Domain Layer

这里是业务规则:所有的逻辑发生在这一层。对于安卓工程,你将在这里看到所有交互的实现(用例)。

这一层是一个纯粹的 java 模块,没有任何 android 依赖。所有的外部组件使用接口连接业务对象。

Data Layer

应用中需要的所有数据都来自于这一层,以用户仓库(UserRepository )的实现方式(接口定义在 Domain 层),它的策略是使用仓库模式,通过一个工厂,根据不同的特定条件选择不同的数据源。

例如,根据 id 获取用户信息时,如果用户信息在缓存中已经存在,将会选择磁盘缓存数据源,否则将向云端查询数据并存入磁盘缓存。

这背后的思想是数据源对于客户端来说是透明的。它不会关心数据是来自于内存,磁盘还是云端,它只关心是否将会得到数据。

NOTE:在代码中,我使用文件系统和 android 的 preferences 实现了一个简单原始的磁盘缓存,仅仅只是为了达到学习目的。再次记住,若果已经存在功能完备的第三方库,你不应该再重复造轮子

Error Handling(异常处理)

这一直是一个值得讨论的话题。如果你愿意在这里分享你的解决方案,将会更棒!

我的方案是使用回调。回调有两个方法,onResponse()onError() 。后者使用包装类 “ErrorBundle” 来表示异常:这个方法带来了一些困难,因为在错误到达表示层展示之前有一个调用链,降低了代码可读性。

另一方面,我实现了一个 event bus 系统,当错误发生时将会抛出事件。但是这种解决方案就像使用 GOTO。而且,在我看来,有时当你订阅了几个事件之后,如果你没有紧密的控制他们,你可能会忘记它们。

Testing(测试)

对于测试,我根据每一层选择了几种方案:

  • Presentation Layer:使用 android instrumentation 和 espresso 进行集成和功能测试
  • Domain Layer:使用 Junit 和 mockito 进行单元测试
  • Data Layer:Robolectric(因为之一层有 android 依赖),Junit 和 mockito ,进行集成和单元测试

Show me the code

我猜你肯定想知道代码在哪里,是吗?好吧,这里是 Github 链接,你可以看到我做了什么。关于工程结构要说的一点是,不同的层都由单独的模块表示。

  • presentation:这是一个 android 模块,代表 presentation layer
  • domain:一个没有 android 依赖的 java 模块
  • data:获取所有数据的 android 模块
  • data-test:data layer 的测试。由于 Robolectric 的一些限制,我只能在一个单独的 java 模块中使用它

Conclusion(结论)

正如 Uncle Bob 所说,“Architecture is About Intent, not Frameworks.”,我完全同意这句话。当然有许多不同的方法和实现来做这件事,我确定你(和我一样)每天都面临许多挑战,但是通过使用这个技术,你将确保你的应用是:

  • 易于维护的
  • 易于测试的
  • 高内聚的
  • 低耦合的

最后我强烈建议你尝试一下然后分享你的结论,经验和你发现的其他好方法:我们知道持续改进是一件很棒的事情。

我希望这篇文章对你有用,欢迎反馈!

源代码

延伸阅读

链接和资源