长按图片识别二维码报名参与码云用户见面交流会
自我开始开发 Android 应用以来就有一种感觉——我可以把它做得更好。在我的职业生涯中,我看到过不少烂代码,其中一些还是我自己写的。Android 的复杂性和烂代码势必造成大问题。所以,从错误中汲取教训并持续改善十分重要。在多次尝试寻找更好的开发方式后,我遇到了 Clean 架构(简洁架构)。于是我将其应用在了 Android 开发中,并结合我的开发经验做了调整,写出了这篇我觉得较为实用、值得分享的文章。
最近我用 Clean 架构为客户构建了 app,并收到了很好的反馈。因此,在这篇文章中我会手把手教你如何用 Clean 架构开发 Android 应用。
有许多文章已经对 Clean 架构的概念做过介绍。在此我讲一讲 Clean 架构的核心内容。
通常所说的 Clean,是指代码被分为像洋葱状的多个层,其规则基础:内层不需要知道外层在干什么。即向内依赖。
这是上一段内容的直观呈现:
简洁架构极佳的视觉表现。图片来自
Uncle Bob
。
文中提到的 Clean 架构会给代码提供以下属性:
-
不依赖框架。
-
可测试。
-
不依赖 UI。
-
不依赖数据库。
-
不依赖其它外部力量
我希望你能理解这几点在下面的示例中是如何体现的。更多关于 Clean 架构的解释,我推荐你看看这篇
文章
和这个
视频
。
一般来说,你的应用可以有任意数量的层,除非你的 Android 应用包含企业级的业务逻辑,最常见的是3层:
-
外层:实现层
-
中间层:接口适配层
-
内层:业务逻辑层
实现层
是框架要求所有事情发生的地方。构架代码包括不解决特定问题的代码,比如所有 Android 开发者都喜欢的创建 Activity 和 Fragment,发送 Intent,以及其它网络和数据库相关的框架代码。
接口适配层
的目标是连接业务逻辑和框架代码。
最重要的问题是
业务逻辑层
。这里是你的应用中实际解决问题的地方。这里不会有框架代码,你应该能在没有模拟器支持下运行这部分代码。这样你的业务逻辑代码才容易测试、开发和维护。这是 Clean 架构的主要优势。
核心层之上的每一层都需要为下一层转换模型结构。内层不会引用外层的模型,但外层可以使用内层的模型。这也是前面提到的依赖规则。虽然这样做会导致更大的开销,但能确保各层代码之间的解耦。
为什么需要模型转换?举个例子,当逻辑层的模型不能直接很优雅地展现给用户,或是需要同时展示多个逻辑层的模型时,最好创建一个 ViewModel 类来更好的进行 UI 展示。这样可以在外层使用转换器类将业务模型转换成合适的 ViewModel。
另一个例子:假设你要从外部数据层的 ContentProvider 得到一个 Cursor 对象,外层要先把它转换成内层的业务模型,再送给你的业务逻辑层进行处理。
文末我会给出更多相关资源,以便你了解更多相关信息。现在我们已经了解 Clean 架构的基本原理,接下来我们需要用代码示例进行说明:用 Clean 架构构建一个示例功能。
我做了一个
样板项目
,它为你提供了所有的底层命令。这是一个 Clean 启动包,在设计之初就包含最常用的一些工具包。你可免费下载和修改,还能用它建立自己的应用程序。
你可以在这里找到入门项目:
Android Clean Boilerplate
本节将解释所有需要编写的代码,你可通过上一节提供的样板文件使用 Clean 方法创建一个示例。 一个示例只代表应用程序中的部分独立功能。 用户(例如,在点击时)可以选择启用或不启用。
首先我们来解释这种方法的结构和术语。这里要说的是我如何构建应用程序,其方法并不固定,你可根据你的需求组织不同的结构。
结构
一般的 Android 应用结构如下:
-
外层包:UI、Storage、Network 等。
-
中层包:Presenters, Converters
-
内层包:Interactors、Models、Repositories、Executor
外层
上面已经提到过,这里是框架的细节。
UI —包括 Activite、Fragment、Adapter 和其它用户界面相关的代码。
Storage — 数据库相关代码,实现 Interactor 需要使用的接口,用于访问和存储数据。包含如
ContentProviders
或者像
DBFlow
这样的 ORM。
Network — 类似
Retrofit
的网络操作。
中层
粘合代码层,将实现细节与业务逻辑连接起来。
Presenters — 处理来自 UI 的事件(比如用户单击)或者常用作内层(Interactor)的回调。
Converters — 转换器对象负责把内部模型转换为外部模型,反之亦然。
内层
核心层包含大部分高等级代码。这里的所有类都是 POJO。这一层中的类和对象都不是特定运行在 Android 应用中,可以非常容易的移植到其它 JVM 运行。
Interactors - 这些是实际包含业务逻辑代码的类。这些类在后台运行,并使用回调向上层传递事件。在一些项目中,它们也被称为用例(可能是一个合适的名称)。在您的项目中可能有很多小的用于解决特定问题 Interactor 类,这属正常现象。可以说,它符合
单一责任原则
,而且这样的理解更容易让人接受。
Models - 这些是您在业务逻辑中处理的业务模型。
Repositories - 此包仅包含数据库或其他外层实现的接口。Interactors 使用这些接口来访问和存储数据。也称为
仓库模式
。
Executor - 此包包含用于调用工作线程执行器在后台执行 Interactors 的代码。这个包一般不需要你修改任何部分。
一个简单的示例
在这个示例中,我们的用例是: “在 app 启动时读取存储在数据库中的消息并展示。“ 此示例将会展示如何使用下面三个程序包来完成用例的功能:
-
presentation 包(展示包)
-
storage 包(存储包)
-
domain 包(主包)
前两个属于外层实现,最后一个属于内部/核心层实现。
Presentation 包主要负责所有与屏幕显示相关的部分——包括全部的
MVP
栈,即包括 UI 和 presenter 这两个不同层的组件。
事实上你可以从架构的任意层开始编码,但是我还是推荐你首先从核心业务逻辑开始。因为逻辑代码写好之后可以测试,不需要 activity 也可以正常运行。
所以我们先从创建一个 Interactor 开始。Interactor 是用例主逻辑实现的地方。所有的 Interactors 都运行在后台线程,因此应该不会对 UI 展示造成影响。 我们在这里新建一个 Interactor,叫做 WelcomingInteractor。
Callback 负责和主线程中的 UI 交互,我们之所以将其放在 Interactor 接口中是因为我们不需要将其重新命名为 WelcomingInteractorCallback——用于将其与其他回调区分。下面让我们实现取回消息的逻辑。假设我们有一个 Interactor 的 MessageRepository,可以给我们发送欢迎
消息。
下面让我们参考业务逻辑实现 Interactor 接口。我们的实现必须扩展自 AbstractInteractor,这样代码就能在后台执行了。
WelcomingInteractor 运行方法
这里尝试获取了数据,并发送消息或者错误码到 UI 层用于显示。我们通过 Callback 通知 UI,这个 Callback 扮演的是 presenter 的角色。这段代码是我业务逻辑的关键。其他框架都是依赖于框架本身。
让我们看一下 Interactor 究竟有哪些依赖:
正如你所看到的,这里没有提到任何 Android 代码,这就是 Clean 架构的主要好处。你可以看到框架的独立性。 另外,我们不需要关注 UI 或数据库的细节,我们只是调用外层实现的接口方法。