Architecting Android…The clean way?

Architecting Android…The clean way?


过去几个月,与@pedro_g_s 和 @flipper83 (顺嘴说一下这两位是android开发大牛)两位同行在Tuenti 网站上友好的讨论之后,我认为这是一个写一篇关于android应用架构的文章的好时机 
写这篇文章的目的是想给大家展现一些我在这几个月所想的加上从研究和实施中学到的一些小方法。

入门指南

我们知道写一款有品质的软件是困难和复杂的: 不仅要满足要求,同时也是健壮的、可维护的、可测试的,并且足够灵活适应扩展和变化。这时 “清晰架构” 就出现了,而且可能是一个开发任何软件应用的好方法。

这个思路很简单:清晰架构表示产品系统遵循一组实践原则:

  • 框架独立.
  • 可测试性.
  • UI独立.
  • 数据库独立.
  • 任何外部代理模块独立.

Architecting Android…The clean way?_第1张图片

不一定非要使用四环结构 (如图所示),因为这只是一个原理图,但是你应该考虑依赖原则: 源码依赖只能向内指向,并且内核中的所有项不能了解任何外环的东西。

以下是一些相关的词汇用一个更好的方式熟悉和理解这些方法:

  • Entities: 应用的业务对象。
  • Use Cases: 结合业务对象的数据流入流出的用例. 同样被称为Interactors。
  • Interface Adapters: 这组适配器以最合适的格式转换用例和业务对象之间的数据。Presenters(表现层) 和 Controllers(控制层)就属于这里。
  • Frameworks and Drivers: 这里是具体的实现:UI,工具类, 框架,等等。

想要更好更生动的解释,参考这篇文章 或 视频。

我们的场景

我会用一个简单的情景让一切开始:创建一个简单的APP用来显示从云端获取的朋友列表和用户检索,当点击它们的时候,一个新的界面为用户显示详细信息。
我放了个视频在这里,这样你对我所说的有个大概的映像:

Android 架构

目标是 分离关注点,让业务规则对外环事物一无所知,因此,它们在被测试时不需要依赖其它外部元素。
要实现这个目标,我的 建议是将项目分为三层,每一个都有自己的目的并且和其它层分开工作。
值得一提的是,每一层都有自己的数据模型以达到这种独立性(你会在看到在代码中需要一个数据映射来完成数据转换,这需要付出一点代价,如果你不想把你的模型和整个应用交叉使用)。
这是图示,你可以感受一下:

Architecting Android…The clean way?_第2张图片

注意: 我没有使用任何外部库(除了用于json数据的解析的gson和用来测试的junit、mockito、robolectric 和espresso)。 原因是它可以使这个例子更清晰。无论如何不要犹豫添加ORMs存储数据、依赖注入框架或者你熟悉的任何类库,这些都会让你变得更轻松。(记住重复造轮子是不明智的)。

Presentation Layer (表现层)

这里, 表现的是逻辑和视图动画的关联。 这里用了一个Model View Presenter (下称MVP),但是你也可以用其它任何模式,像MVC 或者 MVVM。 我不会在这里详细描述它们,但是这里 fragments and activities 仅仅是views,它们内部除了UI逻辑没有其它逻辑, 这也是所有渲染发生的地方。

在这层的Presenters 由多个 interactors (用例) 组成,在android UI线程之外的新线程执行job,并通过回调将要渲染到view的数据取回。

Architecting Android…The clean way?_第3张图片

如果你需要一个使用MVP或者MVVM Effective Android UI 的炫酷的例子,可以看看我的朋友 Pedro Gómez 所做的。

Domain Layer (领域层)

业务规则定义:所有的逻辑发生在这一层。 对于android项目,你也会看到所有的 interactors (用例) 在这里实现。
这一层是一个纯java的模块,没有任何android依赖。 所有的外部组件使用接口访问业务对象。

Architecting Android…The clean way?_第4张图片

Data Layer (数据层)

应用需要的所有数据来自这一层,通过UserRepository实现(这个接口在 domain layer),使用了 Repository Pattern作为策略, 通过一个 factory 类,根据一定条件下选择不同的数据源。
例如: 通过ID获取用户时,如果这个用户在缓存中已经存在,则硬盘数据会被选中,否则 会从云端获取数据并保存在本地磁盘。
这一切背后的理念是数据源对客户端是透明的。 客户端不关心数据来源于内存、磁盘或者云端,它只关系数据会到达和被获取到。

Architecting Android…The clean way?_第5张图片

注意: 出于学习的目的,这里我实现了一个非常简单的代码,使用文件系统和android preferences 实现原始磁盘缓存,再次,如果已经存在能出色完成这些工作的类库,SHOULD NOT REINVENT THE WHEEL(不要重复造轮子)

Error Handling (错误处理)

这是一个长期值得讨论的主题,如果你可以分享你的解决方案那真实太好了。
我的策略是使用回调callbacks, 因此, 假如数据仓库发送改变,回调callback有两个方法 onResponse() 和onError(). 最后封装异常的类叫 “ErrorBundle”: 这种方法会带来一些困难,因为有一个回调链一个接一个,直到错误到表现层呈现。可读性会有一点牺牲。
另一方面, 如果出现错误,我使用event bus 系统抛出错误的事件,但是这类解决方案类似 GOTO,在我看来,当你订阅多个事件但不能很好的控制,你可能会懵掉。

Testing(测试)

关于测试,我根据不同的层选择了几个解决方案:

  • Presentation Layer(展示层): 使用android instrumentation 和 espresso 进行集成和功能测试。
  • Domain Layer(领域层): 使用JUnit 加 mockito 进行单元测试。
  • Data Layer(数据层): 使用Robolectric (这一层有android依赖) 加junit 加 mockito 进行集成和单元测试。

代码展示

我猜你在想代码在那里? 好吧,这就是我上面讲到内容的github连接。关于目录结构,提醒一下,不同的层使用模块来表示:

  • presentation: 是一个android模块代表展示层。
  • domain: 是一个没有android依赖的java模块。
  • data: 是一个android模块,所有数据的获取来源。
  • data-test: 数据层测试。由于使用Robolectric有一些限制问题,我不得不使用一个单独的模块。

结论

正如Bob大叔所说,“Architecture is About Intent, not Frameworks” 我完全统一这个说法。当然有许多不同的方式做这些事情(不同的实现方式),我很确信每天你(像我)一样会面临很多挑战,但是使用上面的方法,可以确保你的应用会:

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

最后我强力推荐你去实践一下,分享你的结果和经验,也许你会找到更好的方法:我们都知道持续改进 总是一个好的积极的事情。
我希望这篇文章对你有帮助,同样欢迎反馈不同意见。

Source code

  1. Clean architecture github repository – master branch
  2. Clean architecture github repository – releases

Further reading:

  1. Architecting Android..the evolution
  2. Tasting Dagger 2 on Android
  3. The Mayans Lost Guide to RxJava on Android
  4. It is about philosophy: Culture of a good programmer

Links and Resources

  1. The clean architecture by Uncle Bob
  2. Architecture is about Intent, not Frameworks
  3. Model View Presenter
  4. Repository Pattern by Martin Fowler
  5. Android Design Patterns Presentation

你可能感兴趣的:(架构,library)