自从开始开发安卓应用,我一直感觉我可以做得更好。我看过不少烂代码,其中当然有我写的。安卓系统的复杂性加上烂代码势必酿成灾祸,所以从错误中成长就很重要。我Google了如何更好地开发应用,发现了这个叫做Clean架构的东西。于是我尝试将它应用于安卓开发,根据我在类似项目中的经验做了一些改善,写出了这篇我觉得较为实用、值得分享的文章。
我会在这篇文章中手把手教你在Android应用中使用Clean架构。我最近一直用这种方式优雅地编写应用。
什么是Clean架构?
有许多文章已经很好地回答了这个问题。我在这里讲一讲Clean架构的核心概念。
一般来说,在Clean架构中,代码被分层成洋葱形,层层包裹,其中有一个依赖性规则:内层不能依赖外层,即内层不知道有关外层的任何事情,所以这个架构是向内依赖的。看个图感受一下:
图片由Bob大叔提供
Clean架构可以使你的代码有如下特性:
- 独立于架构
- 易于测试
- 独立于UI
- 独立于数据库
- 独立于任何外部类库
我将通过下面的例子解释这些特性是怎么来的。如果你想深入了解Clean架构,不妨看这篇文章和这个视频
Clean在Android中如何表现
一般来说,一个应用可以有任意数目的层,但除非你的应用到处是企业级功能逻辑,一般需要这三层:
- 外层:实现层
- 中层:接口适配层
- 内层:逻辑层
接口实现层是体现架构细节的地方。实现架构的代码是所有不用来解决问题的代码,这包括所有与安卓相关的东西,比如创建Activity和Fragment,发送Intent以及其他联网与数据库的架构相关的代码。
添加接口适配层的目的就是桥接逻辑层和架构层的代码。
最重要的是逻辑层,这里包含了真正解决问题的代码。这一层不包含任何实现架构的代码,不用模拟器也应能运行这里的代码。这样一来你的逻辑代码就有了易于测试、开发和维护的优点。这就是Clean架构的一个主要的好处。
每一个位于核心层外部的层都应能将外部模型转成可以被内层处理的内部模型。内层不能持有属于外层的模型类的引用。这也是由于刚才说的依赖性规则,这样内外层可以很好地分离。
为什么要进行模型转换呢?举个例子,当逻辑层的模型不能直接很优雅地展现给用户,或是需要同时展示多个逻辑层的模型时,最好创建一个ViewModel类来更好的进行UI展示。这样一来,你就需要一个属于外层的Converter类来将逻辑层模型转换成合适的ViewModel。
再举一个例子:你从外部数据库层获得了ContentProvider的Cursor对象,外层首先要将这个对象转换成内层模型,再将它传给内层处理。
在文章的最后我还提供了一些学习资源。我们已经知道了Clean架构的基本原则,现在我们来实践一下。我会在下一部分中使用Clean架构构建一个示例功能。
如何开始写Clean应用?
我已经写好了一个样板项目,里面把准备工作做好了。这相当于是一个Clean的底层包,可以直接在它的基础上进行开发。请随意下载、修改。项目包:Android Clean Boilerplate
开始写用例
这一部分会详细说明如何用在样例项目的基础之上以Clean方式进行开发。首先让我们看一下应用的结构,当这只是我的习惯,不需要完全按这个进行。
结构
一般来说一个安卓应用的结构如下:
- 外层项目包:UI,Storage,Network等等。
- 中层项目包:Presenter,Converter。
- 内层项目包:Interactor,Model,Repository,Executor。
看不懂不要紧,下面有具体解释。
外层
外层体现了框架的细节。
UI – 包括所有的Activity,Fragment,Adapter和其他UI相关的Android代码。
Storage – 用于让交互类获取和存储数据的接口实现类,包含了数据库相关的代码。包括了如ContentProvider或DBFlow等组件。
Network – 网络操作。
中层
桥接实现代码与逻辑代码的Glue Code。
Presenter – presenter处理UI事件,如单击事件,通常包含内层Interactor的回调方法。
Converter – 负责将内外层的模型互相转换。
内层
内层包含了最高级的代码,里面都是POJO类,这一层的类和对象不知道外层的任何信息,且应能在任何JVM下运行。
Interactor – Interactor中包含了解决问题的逻辑代码。这里的代码在后台执行,并通过回调方法向外层传递事件。在其他项目中这个模块被称为用例Use Case。一个项目中可能有很多小Interactor,这符合单一职责原则,而且这样更容易让人接受。
Model – 在业务逻辑代码中操作的业务模型。
Repository – 包含接口让外层类实现,如操作数据库的类等。Interactor用这些接口的实现类来读取和存储数据。这也叫资源库模式Repository Pattern。
Executor – 通过Worker Thread Executor让Interactor在后台执行。一般不需要修改这个包里的代码。
以下是例子
在这个简单例子中,我们的use case是在应用启动时读取数据库中的欢迎语句并展示。下面演示如何编写代码包让use case运行起来。
- presentation包
- storage包
- domain包
前两个包属于外层,最后一个包属于内层(核心层)。
presentation包负责将信息展示在屏幕上,而且包含整个MVP栈,即同时包含UI和presenter这两个属于不同层的组件。下面上码。
写一个内层的Interactor
你可以从任何一层开始编写,我建议从内层的逻辑代码写起。因为逻辑代码写好之后可以测试,不需要activity也可以正常运行。
所以我们先写一个Interactor,这个Interactor包含了处理业务逻辑的代码。**所有的Interactor都应该在后台运行,而不应影响UI展示。**我在这里先编写一个WelcomingInteractor。
1 2 3 4 5 6 |
public interface WelcomingInteractor extends Interactor { interface Callback { void onMessageRetrieved(String message); void onRetrievalFailed(String error); } } |
Callback负责与主线程的UI组件联通。将它放在WelcomingInteractor中可以避免给所有Callback接口起不同的名字而又能将它们有效区分。而后我们要实现获取消息的逻辑。现在已经有一个接口MessageRepository用于获取数据:
1 2 3 |
public interface MessageRepository { String getWelcomeMessage(); } |
现在我们可以用业务逻辑代码来实现Interactor接口了。注意要实现AbstractInteractor接口,这样代码就会在后台执行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public class WelcomingInteractorImpl extends AbstractInteractor implements WelcomingInteractor { ... private void notifyError() { mMainThread.post(new Runnable() { @Override public void run() { mCallback.onRetrievalFailed("Nothing to welcome you with :("); } }); } private void postMessage(final String msg) { mMainThread.post(new Runnable() { @Override public void run() { mCallback.onMessageRetrieved(msg); } }); } @Override public void run() { // 获取消息 final String message = mMessageRepository.getWelcomeMessage(); // 检查是否获取失败 if (message == null || message.length() == 0) { // 在主线程中通知错误 notifyError(); return; } // 已成功获取消息,通知UI postMessage(message); } } |
这段代码获取了数据,并向UI层发送数据或报错。这里通过Callback向UI发送信息,这个Callback扮演的是presenter的角色。这段代码是逻辑的核心,其他代码都是依赖框架的。看一下这个类的引用:
1 2 3 4 5 |
import com.kodelabs. import com.kodelabs. 我会在这篇文章中手把手教你在Android应用中使用Clean架构。我最近一直用这种方式优雅地编写应用。 什么是Clean架构?有许多文章已经很好地回答了这个问题。我在这里讲一讲Clean架构的核心概念。 一般来说,在Clean架构中,代码被分层成洋葱形,层层包裹,其中有一个依赖性规则:内层不能依赖外层,即内层不知道有关外层的任何事情,所以这个架构是向内依赖的。看个图感受一下: Clean架构可以使你的代码有如下特性:
我将通过下面的例子解释这些特性是怎么来的。如果你想深入了解Clean架构,不妨看这篇文章和这个视频 Clean在Android中如何表现一般来说,一个应用可以有任意数目的层,但除非你的应用到处是企业级功能逻辑,一般需要这三层:
接口实现层是体现架构细节的地方。实现架构的代码是所有不用来解决问题的代码,这包括所有与安卓相关的东西,比如创建Activity和Fragment,发送Intent以及其他联网与数据库的架构相关的代码。 添加接口适配层的目的就是桥接逻辑层和架构层的代码。 最重要的是逻辑层,这里包含了真正解决问题的代码。这一层不包含任何实现架构的代码,不用模拟器也应能运行这里的代码。这样一来你的逻辑代码就有了易于测试、开发和维护的优点。这就是Clean架构的一个主要的好处。 每一个位于核心层外部的层都应能将外部模型转成可以被内层处理的内部模型。内层不能持有属于外层的模型类的引用。这也是由于刚才说的依赖性规则,这样内外层可以很好地分离。 为什么要进行模型转换呢?举个例子,当逻辑层的模型不能直接很优雅地展现给用户,或是需要同时展示多个逻辑层的模型时,最好创建一个ViewModel类来更好的进行UI展示。这样一来,你就需要一个属于外层的Converter类来将逻辑层模型转换成合适的ViewModel。 再举一个例子:你从外部数据库层获得了ContentProvider的Cursor对象,外层首先要将这个对象转换成内层模型,再将它传给内层处理。 在文章的最后我还提供了一些学习资源。我们已经知道了Clean架构的基本原则,现在我们来实践一下。我会在下一部分中使用Clean架构构建一个示例功能。 如何开始写Clean应用?我已经写好了一个样板项目,里面把准备工作做好了。这相当于是一个Clean的底层包,可以直接在它的基础上进行开发。请随意下载、修改。项目包:Android Clean Boilerplate 开始写用例这一部分会详细说明如何用在样例项目的基础之上以Clean方式进行开发。首先让我们看一下应用的结构,当这只是我的习惯,不需要完全按这个进行。 结构一般来说一个安卓应用的结构如下:
看不懂不要紧,下面有具体解释。 外层外层体现了框架的细节。 UI – 包括所有的Activity,Fragment,Adapter和其他UI相关的Android代码。 Storage – 用于让交互类获取和存储数据的接口实现类,包含了数据库相关的代码。包括了如ContentProvider或DBFlow等组件。 Network – 网络操作。 中层桥接实现代码与逻辑代码的Glue Code。 Presenter – presenter处理UI事件,如单击事件,通常包含内层Interactor的回调方法。 Converter – 负责将内外层的模型互相转换。 内层内层包含了最高级的代码,里面都是POJO类,这一层的类和对象不知道外层的任何信息,且应能在任何JVM下运行。 Interactor – Interactor中包含了解决问题的逻辑代码。这里的代码在后台执行,并通过回调方法向外层传递事件。在其他项目中这个模块被称为用例Use Case。一个项目中可能有很多小Interactor,这符合单一职责原则,而且这样更容易让人接受。 Model – 在业务逻辑代码中操作的业务模型。 Repository – 包含接口让外层类实现,如操作数据库的类等。Interactor用这些接口的实现类来读取和存储数据。这也叫资源库模式Repository Pattern。 Executor – 通过Worker Thread Executor让Interactor在后台执行。一般不需要修改这个包里的代码。 以下是例子在这个简单例子中,我们的use case是在应用启动时读取数据库中的欢迎语句并展示。下面演示如何编写代码包让use case运行起来。
前两个包属于外层,最后一个包属于内层(核心层)。 presentation包负责将信息展示在屏幕上,而且包含整个MVP栈,即同时包含UI和presenter这两个属于不同层的组件。下面上码。 写一个内层的Interactor你可以从任何一层开始编写,我建议从内层的逻辑代码写起。因为逻辑代码写好之后可以测试,不需要activity也可以正常运行。 所以我们先写一个Interactor,这个Interactor包含了处理业务逻辑的代码。**所有的Interactor都应该在后台运行,而不应影响UI展示。**我在这里先编写一个WelcomingInteractor。
Callback负责与主线程的UI组件联通。将它放在WelcomingInteractor中可以避免给所有Callback接口起不同的名字而又能将它们有效区分。而后我们要实现获取消息的逻辑。现在已经有一个接口MessageRepository用于获取数据:
现在我们可以用业务逻辑代码来实现Interactor接口了。注意要实现AbstractInteractor接口,这样代码就会在后台执行了。
这段代码获取了数据,并向UI层发送数据或报错。这里通过Callback向UI发送信息,这个Callback扮演的是presenter的角色。这段代码是逻辑的核心,其他代码都是依赖框架的。看一下这个类的引用: |