Android架构

Android架构的目的是通过隔绝业务逻辑和外部变化以实现关注点分离,这样,就可以在不借助任何外部单元依赖的情况下对业务逻辑进行测试。为了达到这个目的,我的方案是将整个项目划分为3层,每一层都有各自明确的意图并且各层独立工作。

值得说明的是每一层都使用各自独有的数据模型,这样才能实现各层之间独立工作(查看代码,你会发现我们需要数据映射器(data mapper)来实现数据的转换,这是我们在整个应用上不想共用一套数据模型所必须要付出的代价)。

下面是帮助我们理解的一个图示:

注意:我没有使用任何第三方库(除了用于解析json数据的gson和用于单元测试的junit、mockito、robolectric以及espresso)。因为这样可以使这个实例更加的清晰易懂。但是无论如何,都不要犹豫去使用那些能够简化开发工作的东西,比如使用ORMs来存储数据或使用依赖注入框架或其他任何你熟悉的第三方库。(记住,重新发明轮子并不是一个好的习惯)。

表示层(Presentation Layer)

这一层处理有关视图和动画的逻辑,使用Model-View-Presenter(下文称MVP)的模式,当然我们可以在这一层使用其他的模式,比如MVC或者MVVM。这里将不会对其细节进行讨论。但是这里我们使用的MVP的模式,这就代表fragments和activitys都仅仅只是作为视图,没有任何出简单UI逻辑之外的其他逻辑处理。

这层中的Presenter由交互器组成(用例),它通过在Android UI线程外开启新线程来执行耗时操作,并通过回调接口将数据更新到试图上。

如果你想参考更酷的使用MVP或MVVM模式来实现高效 Android UI的例子,请关注一下我的朋友Pedro Gómez做的事情。

领域层(Domain Layer)

这一层处理所有有关于业务逻辑操作。就Android项目而言,你会在这一层看到所有的交互器的实现。

这一层是不包含任何android依赖的纯java模块。所有的外部组件都通过接口来连接业务对象。

数据层(Data Layer)

所有应用程序需要的数据都来自于这一层。数据通过一个使用仓储模式来实现的UserRepository实例(接口定义于领域层)。而所谓仓储模式的实现策略是通过工厂模式来根据不同的条件筛选不同的数据源。

举例来说,当我们通过id来获取某个用户时,如果用户已经存在于缓存文件中,这时硬盘缓存数据源就会被筛选出来。否则就会查询云端来获取数据然后保存到硬盘缓存中。

背后的设计思想是:数据来源对于客户端来说是透明的,客户端并不关心数据是来源于内存、硬盘还是云端,唯一的事实是数据将会到达并被获取。

注意:就示例代码而言,我使用java的文件系统和android的shared preferences实现了一个非常简单且原始的硬盘缓存,这仅是以学习为目的。再次牢记如果已经有能够更好的完成特定工作的第三方库,请不要重复造轮子。

错误处理(Error Handling)

这个话题总是充满争论的,如果你能够分享你的解决方案,那真的是再好不过了。

我的策略是使用回调,这样,一旦数据仓库有事情发生,回调包含两个党法onResponse()和onError()。最后一个方法将异常封装到一个被称作“ErrorBundle”的包装类中,这个方法也带来了一些问题,因为直到错误被传递到presentation层进行处理,错误会在一个回调链中进行传递。这为代码的可读性带来了一些困难。

另外,我也可以实现一个事件总线系统,这样一旦异常发生就主动抛出,但是这种解决方案类似于GOTO,并且,就我而言,当你订阅多个事件时,如果你不对其小心控制,那么你将很容易感到迷惑。

测试(Testing)

就测试而言,我针对不同层选择了多种方案:

  • 表示层(Presentation Layer):使用Android instrumentation或espresso进行集成和功能测试。
  • 领域层(Domain Layer):使用JUnit和mockito进行单元测试。
  • 数据层(Data Layer):使用Robolectric(因为这层有Android依赖)和junit、mockito进行集成和单元测试。

示例代码(Show me the code)

我知道你可能在想代码在哪里?好吧,https://github.com/android10/Android-CleanArchitecture,你可以在这里找到我所做的一切。关于项目的目录结构,需要提及的是,每一层都是用了模块(module)来表示。

  • Presentation:表示层的模块。
  • domain:纯java模块,领域层。
  • data:获取数据的数据层。
  • data-test:数据层的测试。由于使用Robolectric有些限制,我不得不在新的Java模块中使用。

总结(Conclusion)

就像BOb大叔说的那样,“架构在于意图,而非框架”,我完全赞同这种说法。当然,做一件事情的方式有多种,我坚信你(像我一样)每天也都面临着很多挑战。但是通过使用这种技巧,你可以确保你的应用程序将会拥有一下特性:

  • 易于维护
  • 易于测试
  • 非常内聚
  • 解耦

最后,我强烈建议你尝试一下这种架构,并且分享你的结论和使用体验以及任何你觉得更好的方法。我们坚信持续不断的改进总是有益无害的事情。我希望这篇文章对你是有用的,并且欢迎各种回馈结果。