Android Archtecturing...Clean Method

来源:互联网 发布:qq的端口号是多少 编辑:程序博客网 时间:2024/06/05 18:44

Android 架构 … 简洁方法

FROM:http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/
AUTHOR:Fernando Cejas

上个月我和在 Tuenti 的两位同事进行了友好的讨论,我觉得现在是时候写一篇关于介绍Android应用架构的文章了。

本文目的是介绍一种我在过去几个月中一直在思考的以及通过调查和实现得出的方法。

介绍

我们都知道编写高质量的软件是困难和复杂的:因为不仅要哦满足需求,还要做到健壮性、可维护性、可测试性以及足够的灵活性来适应项目的成长和变化。而这正是“清晰架构”所要解决的问题,它是进行Android开发时可以使用的一种好方法。

想法非常简单:清晰的架构定义了一组实践,根据这些实践定义的系统能够:

  • 具有框架独立性
  • 可测试性
  • UI独立性
  • 数据库独立性
  • 外部代理独立性

实际上可以不用完全使用这四层环,因为这里的图仅仅是原理图。但是不论如何,比如要遵循依赖规则:源代码中的依赖仅能指向内部,并且内层环不能知道外层环的任何信息。

这里写图片描述

为了让你更好的熟悉和了解,这里给出了一些词汇说明:

  • 实体(Entities):是应用程序的业务对象(bussiness object)
  • 用例(User Case):负责组织来自实体和去往实体的数据流,也叫交互器(Interactor)
  • 接口适配器(Interface Adapters):一组适配器,将数据从最方便的格式转换而来以供用例和实体使用。表示器和控制器也属于其范围。
  • 框架和驱动(Framework and Driver):所有的细节都属于该范围,包括UI、工具、框架等。

要更好的理解,可以阅读这篇文章,看一下这个视频。

场景

以一个简单场景为例,创建一个简单的应用,展示朋友或用户的列表,数据从云端取得;当列表项被点击时,弹出新屏幕以显示用户的详细信息。

// 为了更加形象的说明,下面的视频演示了效果(需翻墙)

架构

架构的目标是关注点分离,让业务规则对外界一无所知,这样业务规则就可以在不需要任何外部依赖的情况下进行测试。

为了实现该目标,我的建议是将项目分成三个不同的层次,其中的每个层次都有自己的目的要实现,并且不需要依赖其他层次。

需要注意的是每个层次使用自己的数据模型,这样便能实现层次的独立性(在代码中你可以看到,为了实现在不同层次间传输数据的目标,在层次间需要数据映射器(data mapper),这就是你不想要使用跨整个应用的模型的需要付出的代价)。原理图如下:

注意:我没有使用任何外部库(除了用于解析 JSON 的Gson 和用于测试的 Junit、Mockito、robolectric 和 espresso)。这样做会让示例更加简单。但是实际项目中还是推荐使用那些你熟悉的库,比如添加一个 ORM 框架用来进行数据存储或者依赖注入框架,这会让你的生活变得更加轻松(记住:重复发明轮子并不是好的做法)。

这里写图片描述

表示层

与视图和动画相关的逻辑就位于表示层。该层仅使用MVP模式,当然你也可以使用 MVC 或者 MVVM 。这里我不会进行详细说明,Activity 和 Fragment 仅作为视图,它们中除了 UI 逻辑不包含任何其他内容,这里也是渲染进行的地方。

表示器由交互器(用例)构成,执行任务会在非UI线程中进行,会开启另一个线程执行任务,并在任务执行完毕后通过回调返回数据,用于视图的渲染。

详细了解MVP和MVVM的使用,参考Effective Android UI。

这里写图片描述

领域层

所有的业务逻辑都定义在领域层。在Android项目中,所有的交互器(用例)的实现也定义在该层。

该层没有任何的Android依赖,仅仅是纯 Android 代码。当要连接业务对象时,外部组件都要通过表示器接口来访问。

这里写图片描述

数据层

应用所需的全部数据都来自UserRepo的实现(其实现了领域层中定义的数据接口),其使用 Repository 模式,通过工厂根据不用的条件来选取不同的数据源。

比如,当通过id查询用户时,如果用户在磁盘中有数据则选用磁盘数据源,没有则从远端获取然后缓存到本地磁盘中。

核心思想是数据的来源对请求者来说是透明的,数据是来自内存、磁盘还是远端,请求者并不关心,请求者关心的仅是数据是否被取回。

注意:代码中我实现了一个非常简单的和基本的磁盘缓存(使用文件系统和Android Preference实现),仅用于学习目的,实际中还需要完善。再次提醒,如果存在可用的工具,不要重复发明轮子。

这里写图片描述

错误处理

欢迎在这里讨论。我的策略是使用回调,比如,如果Data Repo 中发生变化,回调中就会有两个方法 onReponse() 和 onError()。onError 中将异常封装在 ErrorBundle 中:不过这种方法也有一些麻烦,错误要抵达表示层地方过程中,异常会一层一层的向外传递,最终形成一个调用链。这样,代码的可读性会受到影响。

另一方面,我还实现了一个事件总线系统,如果有错误发生系统就会抛出异常,但是这种解决方案就像是 GOTO 一样,在我看来,如果没有对订阅的事件进行很好的管理,很快便会迷失。

测试

关于测试,我为不同的层选用了不同的测试方案:

  • 表示层:用 Android Instrumentation 和 Espresso 进行集成和功能测试
  • 领域层:用 Junit 加 Mockito 进行单元测试
  • 数据层:用 Robolectic 和 Junit 加 Mokito 进行集成和单元测试

代码

代码

  • presentation:表示层的代码
  • domain:没有Android 依赖的 Java 模块
  • data:Android 模块,用于提供应用需要使用的数据
  • data-test:数据层的测试代码,由于使用 Robolectric 有些限制,所以将其作为单独的模块

总结

正如Bob 大叔所说,“架构是中倾向,而不是框架”,我非常同意这点。当然架构有各种各样的方法,我相信你每天都要面临着许多挑战,但是通过使用该架构,至少能够确保你的应用:

  • 易于维护
  • 易于测试
  • 低耦合
  • 重用性高
0 0
原创粉丝点击