原文链接:Architecting Android…The clean way?
在过去的几个月中我与公司Tuenti的@pedro_g_s和@flipper83(两位android技术大牛)两位同事讨论Android应用架构,我决定抽出一点时间写一篇关于Android应用架构的文章。
通过我过去几个月的学习和实践总结了一些方法,它们将向你展示如何去实现这种清晰的Android架构。
开始前的一些准备
我们知道开发一个高质量的软件是非常困难和复杂的,它不仅是为了满足功能实现,也应该是健壮、易维护、易测试和适于伸缩构建。这就是“清晰架构”的由来,这可能是开发任意软件程序的最好方法。
“清晰架构”的概念很简单,产品系统中遵循一系列的习惯原则:
1、与框架分离
2、易测试
3、与UI分离
4、与数据库分离
5、与其它外部组件分离
上图中你所看见的不一定非要4个圆环来表示,因为这只是原理图但你应该考虑到相关规则
源码依赖只能指向内环而内环不能知道它所在外环的任何事情。
下面的一些术语可以让你明白和了解这是种非常好的架构方式:
实体:他们是程序的业务对象
用例:他们是链接数据流和实体间的纽带,也可叫作Interactor(翻译成:交互器和耦合器,感觉都不对)
接口适配器:这些适配器为用例和实体提供相应格式的数据。Presenter(展现层)和Controller(控制层)都奴属于这里。
框架和驱动:这里是所有的具体实现,如:界面,工具类,框架等等
更加详尽的解释参见这篇文章The Clean Architecture和视频Robert C. Martin - Clean Architecture (貌似需要翻墙)
我们的方案
我们将从一个简单的案例开始:简单的创建一个app,这个app显示一个从云端取回的朋友或用户数据,当单击列表中的任意一个条目时,然后打开一个新的窗口显示这个用户的具体细节。
给你们看一个视频,你们就知道我讲的什么:Clean Architecture on Android - Sample App(需要翻墙)
Android架构
遵循“关注分离”的原则来让内环的业务操作不知道外环的一切,不依赖任何外部元素所以他们易于测试。
要做到这一点,“我的建议是将这个项目分离成3个不同的层”,这样每个层只关心自己的目标和工作以至独立于其它层。
值得一提的是,每个层只使用它自己的数据模块来达到独立性(你将在代码中见到那个 data mapper被用来实现数据转换,如果你不在整个应用中跨模块使用只需小的代价)
这儿是一个我建议的三层模式:
注意:我没有使用任何第三方库(除了解析json数据的GSON、用于测试的junit,mockito、robolectri、espresso)这是为了使这个示例更加整洁。在你的开发中,无论如何不要犹豫去添加你所熟悉的存储数据的ORMs框架、依赖注入框架、任何工具、第三方库,这样在编写整个示例过程中可以省掉不少功夫(记住:重复造轮子不是个好习惯)。
展现层(Presentation Layer)
这儿是视图和动画相关逻辑呈现的地方,它就是 Model View Presenter模式的一部分(参见:MVP),但你可以使用其它模式像MVC或MVVM。我不会进一步阐述他们,但这里的Fragments 与Activities仅仅是一个Views,这里不会有逻辑存在于UI逻辑中,并且这里是视图渲染填充的地方。这层中的Presenters结合interactors (用例)在UI线程之外的新线程中执行任务随后使用回调函数传参的形式来渲染视图。
如果你想要一个很酷的MVP和MVVM使用例子,你可以查看我朋友Pedro Gómez的Effective Android UI。
领域层(Domain Layer)
这里是业务规则:所有逻辑发生在这层. 对于Android 项目你也会在这里见到所有的interactors(用例)实现。
这层是一个纯java模块而不是其它Android依赖。所有外部组件使用接口连接业务对象
数据层(Data Layer)
应用需要的所有数据都来自这层,通过一个UserRepository实现(这个接口在领域层),它使用一个Repository Pattern和一个策略相结合,通过一个工厂,依赖某些条件来获取不同的数据源。例如通过一个id来获取用户,随后获取该用户在磁盘上的缓存数据,如果该用户存在的话,否则将从云端检索数据随后保存在磁盘缓存中。
这个背后的理念是所有数据对于客户端来说都是透明的,它并不关心来自内存、磁盘、还是云端的数据是怎样传递和获取的。
注意:我的代码实现非常简单,使用文件系统和Android Preferences作为磁盘缓存,这是为了达到学习的目的。重申:如果已有第三方库能胜任这种工作就不要重复的造轮子。
错误处理(Error Handling)
这总是一个值得讨论的话题,如果你有更好的解决方案请共享在这儿。
我的策略是使用回调接口, 以数据存储响应为例,回调接口中有onResponse()和onError()两个方法。这里封装了一个叫“ErrorBundle”异常处理类:这种方法也带来一些困难,因为这儿是一个回调链直接将错误抛给展现层(Presentation Layer)。代码可读性不太好。
另外,我的主张是实现一个事件总线系统去抛事件当某些错误发生时,但这类似使用GOTO。当你订阅几个事件时,如果你不善于控制处理它们,你会感到很困惑。
测试(Testing)
对于测试,根据不同层我选择了几个不同的方案:
展现层(Presentation Layer):使用android instrumentation、espresso集成测试、功能性测试。
领域层(Domain Layer):使用JUnit+mockito(java模拟测试框架)进行单元测试
数据层(Data Layer):Robolectric(因为这层有Android依赖)+junit+mockito进行集成和单元测试
我的代码(Show me the code)
我知道你在想我的代码在哪里,没错?这里是github链接代码,在里边你将知道我是怎么做的,关于项目结构不得不提一句,使用模块来区分不同的层:
展现层(presentation):它是一个Android module,它代表展现层。
领域层(domain):一个纯java对象模块而不是 Android Dependencies
数据层(data):一个Android Module,所有数据都从这里检索
data-test:为数据层提供测试,使用Robolectric时的某些局限性,不得不在一个单独的java模块中使用它
结论(Conclusion)
正如鲍勃叔说,“Architecture is About Intent, not Frameworks”,我完全同意这个说法当然这儿有许多不同的做法(不同的实现)我非常肯定,你和我一样每天面临很多挑战,但使用这些技术,你开发的软件将:
1、易于维护(Easy to maintain)
2、易于测试(Easy to test)
3、内聚性强(Very cohesive)
4、解耦(Decoupled)
我强烈建议你尝试并分享你的结果和体验,也许你能找到其它工作得更好的方法:我们知道持续改进是一件非常好的事。
我希望这篇文章能帮到你,非常欢迎并期待你的反馈!
源码(Source code):
1、Clean architecture github repository – master branch
2、Clean architecture github repository – releases
下载源码的朋友注意咯:
请进入第二个链接去下载:我下载的v0.5.0版,其它版本原作者在里边使用了Dagger 2注解框架,如果你没有使用过dagger 2, 当你在看源码的时候可能很茫然(比如说我)
我用UML的形式分析了下作者的这个源码(v0.5.0版)并把图上传在这里:希望能帮助你快速阅读
延伸阅读(Further reading)
1、Architecting Android..the evolution
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