过去几个月,与@pedro_g_s 和 @flipper83 (顺嘴说一下这两位是android开发大牛)两位同行在Tuenti 站点上友好的讨论以后。我以为这是一个写一篇关于android应用架构的文章的好时机。html
写这篇文章的目的是想给你们展示一些我在这几个月所想的加上从研究和实施中学到的一些小方法。java
入门指南
咱们知道写一款有品质的软件是困难和复杂的: 不只要知足要求,同一时候也是健壮的、可维护的、可測试的。而且足够灵活适应扩展和变化。这时 “清晰架构” 就出现了,而且多是一个开发不论什么软件应用的好方法。android
这个思路很是easy:清晰架构表示产品系统遵循一组实践原则:git
- 框架独立.
- 可測试性.
- UI独立.
- 数据库独立.
- 不论什么外部代理模块独立.
不必定非要使用四环结构 (如图所看到的),因为这仅仅是一个原理图,但是你应该考虑依赖原则: 源代码依赖仅仅能向内指向,并且内核中的所有项不能了解不论什么外环的东西。github
下面是一些相关的词汇用一个更好的方式熟悉和理解这些方法:数据库
- Entities: 应用的业务对象。
- Use Cases: 结合业务对象的数据流入流出的用例. 相同被称为Interactors。
- Interface Adapters: 这组适配器以最合适的格式转换用例和业务对象之间的数据。Presenters(表现层) 和 Controllers(控制层)就属于这里。
- Frameworks and Drivers: 这里是详细的实现:UI。工具类, 框架,等等。
咱们的场景
我会用一个简单的情景让一切開始:建立一个简单的APP用来显示从云端获取的朋友列表和用户检索。当点击它们的时候。一个新的界面为用户显示具体信息。vim
我放了个视频在这里。这样你对我所说的有个大概的映像:缓存
Android 架构
目标是 分离关注点。让业务规则对外环事物一无所知,所以,它们在被測试时不需要依赖其余外部元素。
要实现这个目标,个人 建议是将项目分为三层,每一个都有本身的目的并且和其余层分开工做。
值得一提的是,每一层都有本身的数据模型以达到这样的独立性(你会在看到在代码中需要一个数据映射来完毕数据转换,这需要付出一点代价,假设你不想把你的模型和整个应用交叉使用)。
这是图示,你可以感觉一下:架构
注意: 我没有使用不论什么外部库(除了用于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的数据取回。
假设你需要一个使用MVP或者MVVM Effective Android UI 的炫酷的样例,可以看看个人朋友 Pedro Gómez 所作的。
Domain Layer (领域层)
业务规则定义:所有的逻辑发生在这一层。 对于android项目,你也会看到所有的 interactors (用例) 在这里实现。
这一层是一个纯java的模块,没有不论什么android依赖。
所有的外部组件使用接口訪问业务对象。
Data Layer (数据层)
应用需要的所有数据来自这一层,经过UserRepository实现(这个接口在 domain layer),使用了 Repository Pattern做为策略, 经过一个 factory 类,依据必定条件下选择不一样的数据源。
好比: 经过ID获取用户时。假设这个用户在缓存中已经存在。则硬盘数据会被选中,不然 会从云端获取数据并保存在本地磁盘。
这一切背后的理念是数据源对client是透明的。 client不关心数据来源于内存、磁盘或者云端。它仅仅关系数据会到达和被获取到。
注意: 出于学习的目的,这里我实现了一个很easy的代码,使用文件系统和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
- Clean architecture github repository – master branch
- Clean architecture github repository – releases
Further reading:
- Architecting Android..the evolution
- Tasting Dagger 2 on Android
- The Mayans Lost Guide to RxJava on Android
- It is about philosophy: Culture of a good programmer
Links and Resources