在我这几年的学习和成长中,深入的意识到搭建一个Android应用架构是件很是痛苦的事,它不只要知足不断增加的业务需求,还要保证架构自身的整洁,这让事情变得很是具备挑战,但咱们必须这样作,由于健壮的Android架构是一款优秀APP的基础。本文的代码示例能够从github中得到,仓库地址是*android-easy-cleanarchitecture。html
Android入门要求始终不高,由于Android Framework会帮咱们作不少事,甚至不须要经过深刻的学习就能写出一个简单的APP,好比说在Activity
或Fragment
中摆放几个View
用来展现到屏幕上,后台耗时任务放在Service
中执行,组件之间使用Broadcast
传递数据,由此看来“人人都能成为Android工程师”,真的是这样吗?前端
固然不是!!!java
若是咱们如此天真的开始编程,早晚会为此付出代价。那些依赖关系混乱,灵活性不够高的代码将会成为咱们最大的阻碍,任由发展的后果就是,致使项目一片狼藉,咱们很难加入新的功能,只能对它进行重构甚至推翻重作。在开始编程前,咱们不该该低估一个应用程序的复杂性,应该将你的APP看作一个拥有前端,后端和存储特性的复杂系统。android
另外,在软件工程领域,始终都有一些值得咱们学习和遵照的原则,好比:单一职责原则,依赖倒置原则,避免反作用等等。Android Framework不会强制咱们遵照这些原则,或者说它对咱们没有任何限制,试想那些耦合紧密的实现类,处理大量业务逻辑的Activity或Fragment,随处可见的EventBus,难以阅读的数据流传递和混乱的回调逻辑等等,它们虽然不会致使系统立刻崩溃,但随着项目的发展,它们会变得难以维护,甚至很难添加新的代码,这无疑会成为业务增加的可怕障碍。git
因此说,对于开发者们来说,一个好的架构指导规范,相当重要。github
从事Android工做以来,我始终认为咱们能将APP作的更好,我也遇到过不少好的坏的软件设计,本身也作过不少不一样的尝试,我不断地吸收教训并作出改变,直到我遇到了Clean Architecture,我肯定这就是我想要的,我决定使用它。本文的目标是分享我使用clean Architecture构建项目时所收获的经验,但愿可以为你的项目改进带来灵感。数据库
多是出于“快速迭代”,因而你集成了这个万能的Activity
,它无所不能:编程
甚至突破了全部的约束壁垒:在Android世界里面加入了业务代码;在BaseActivity中定义了全部子类可能用到的变量等等。它如今的确就是个“上帝”,方便且万能的“上帝”!json
随着项目的发展,它已经庞大到没法再添加代码了,因而为它写了不少帮助类,你想重构它:后端
不经意间,你已经埋下了黑色炸弹。
看上去,业务逻辑被转移到了帮助类中,Activity
中的代码减小了,再也不那么臃肿,帮助类缓解了“万能类”的压力,但随着项目的成长,业务的扩大,同时这些帮助类也变多,那个时候又要按照业务继续拆分它们,APIHelperThis
、APIHelperThat
等等。原来的问题又出现了,测试成本还在,维护成本好像又增长了,那些混乱而且难以复用的程序又回来了,咱们的努力好像都白费了。
然而你写这个万能类的初衷是什么,想快捷、方便的使用一些功能函数吗,尤为但愿在子类中可以很快的拿到。
固然,一部分人会根据不一样的业务功能分离出不一样的抽象类,但相对那种业务场景下,它们还是万能的。
不管什么理由这种创造“上帝类”的作法都应该尽可能避免,咱们不该该把重点放在编写那些大而全的类,而是投入精力去编写那些易于维护和测试的低耦合类,若是能够的话,最好不要让业务逻辑进入纯净的Android世界,这也是我一直努力的目标。
这种看起来像“洋葱”的环形图就是**Clean Architecture**,不一样颜色的“环”表明了不一样的系统结构,它们组成了整个系统,箭头则表明了依赖关系,
关于它的组成细节,在这里就不作深刻的介绍了,由于有太多的文章讲的比我好,比我详尽。另外值得一提的是architecture是面向软件设计的,它不该该作语言差别,而本文将主要讲述如何结合Clean Architecture构建你的Android应用程序。
在使用clean架构搭建项目前,我阅读了大量的文章,并付诸了不少实践,个人收获很大,经验和教训告诉我一个架构的清晰和整洁离不开这三个原则:
接下来我就分别阐述一下,我对这些原则的理解,以及背后的缘由。
首先,值得一提的是框架不会限制你对应用程序的具体分层,你能够拥有任意的层数,可是在Android中一般状况下我会划分为3层:
接下来,介绍下这三层所应包含的内容。
一句话:实现层就是Android框架层。这个地方应该是Android framework的具体实现,它应该包括全部Android的东西,也就是说这里的代码应该是解决Android问题的,是与平台特性相关的,是具体的实现细节,如,Activity
的跳转,建立并加载Fragment
,处理Intent
或者开启Service
等。
接口适配层的目的是链接业务逻辑与框架特定代码,担任外层与内层之间的桥梁。
最重要的是业务逻辑层,咱们在这里解决全部业务逻辑,这一层不该该包含Android代码,应该可以在没有Android环境的状况下测试它,也就是说咱们的业务逻辑可以被独立测试,开发和维护,这就是clean架构的主要好处。
依赖规则与箭头方向保持一致,外层”依赖“内层,这里所说的“依赖”并非指你在gradle
中编写的那些dependency语句,应该将它理解成“看到”或者“知道”,外层知道内层,相反内层不知道外层,或者说外层知道内层是如何定义抽象的,而内层殊不知道外层是如何实现的。如前所述,内层包含业务逻辑,外层包含实现细节,结合依赖规则就是:业务逻辑既看不到也不知道实现细节。
对于项目工程来说,具体的依赖方式彻底取决于你。你能够将他们划入不一样的包,经过包结构来管理它们,须要注意的是不要在内部包中使用外部包的代码。使用包来进行管理十分的简单,但同时也暴露了致命的问题,一旦有人不知道依赖规则,就可能写出错误的代码,由于这种管理方式不能阻止人们对依赖规则的破坏,因此我更倾向将他们概括到不一样的Android module中,调整module间的依赖关系,使内层代码根本没法知道外层的存在。
另外值得一提的是,尽管没人可以阻止你跳过相邻的层去访问其它层的代码,但我仍是强烈建议只与相邻层进行数据访问。
在依赖原则中,我已经暗示了抽象原则,顺着箭头方向由两边朝中间移动时,东西就越抽象,相对的,朝两边移动时,东西就越具体。这也是我一直反复强调的,内圈包含业务逻辑,外圈包含实现细节。
接下来我会用一个例子来解释抽象原则:
在内层定一个抽象接口Notification
,一方面,业务逻辑能够直接使用它来向用户显示通知,另外一方面,咱们也能够在外层实现该接口,使用Android framework提供的NotificationManager
来显示通知。业务逻辑使用的只是通知接口,它不了解实现细节,不知道通知是如何实现的,甚至不知道实现细节的存在。
这很好演示了如何使用抽象原则。当抽象与依赖结合后,就会发现使用抽象通知的业务逻辑看不到也不知道使用Android通知管理器的具体实现,这就是咱们想要的:业务逻辑不会注意到具体的实现细节,更不知道它什么时候会改变。抽象原则很好的帮咱们作到了这一点。
按照上面提到的分层原则,我把项目分为了三层,也就是说它有三个Android module,以下图所示:
在Domain中定义业务逻辑规则,在UI中实现界面交互,Model则是业务逻辑的具体实现方式(Android framework)。箭头方向表明依赖关系,内层抽象,外层具体,外层知道内层,内层不了解外层。
具体到Android中的框架结构以下图所示:
你可能有些困惑,为何Domain指向Data?既然Domain包含业务逻辑,它就应该是应用程序的中心,它不该该依赖Model,按照前面提到的原则,Domain是抽象的,Model是具体的,应该是Model依赖Domain,而不是Domain依赖Model。
其实这很好理解,也是我始终强调的,这里所说的“依赖”并非指配置在gradle
中的dependency,你应该将它理解为“知道”,“了解”,“意识”,图中的箭头表明了调用关系,而非模块间的依赖关系。咱们应该可以理解:抽象是理论,依赖是实践,抽象是应用的逻辑布局,依赖是应用的组合策略。对于框架结构的理解,咱们应该跳出代码层面,不要局限在惯性思惟中,不然很快就会陷入逻辑混乱的怪圈。
与调用关系对应的就是数据流的走向:
在app中接受用户的行为,根据domain中定义的业务规则,访问model中的真实数据,而后依次返回,最终更新界面,这就是一个完整的数据流走向。
为了更方便理解,我对项目进行了简单的拆解,并在图中加上了类的用例描述,它看起来就像这样:
对上图所表示内容作一下总结:
首先,项目被分为三层:
其次,更细节的子模块划分:
视图,包含全部的Android控件,负责UI展现。
处理用户交互,调用适当的业务逻辑,并将数据结果发送到UI进行渲染。也就是说Presenter将担任着接口适配层的责任,链接Android实现和业务逻辑,负责数据的传递和回调。
实体,也就是业务对象,是应用的核心,它表明了应用的主要功能,你应该可以经过查看这些应用来判断这款应用的功能,例如,若是你有一个新闻应用,这些实体将是体育、汽车或者财经等实体类。
用例,即interactor,也就是业务服务,是实体的扩展,同时也是业务逻辑的扩展。它们包含的逻辑并不只针对于一个实体,而是能处理更多的实体。一个好的用例,应该能够用通俗的语言来描述所作的事情,例如,转帐能够叫作TransferMoneyUseCase。
抽象的核心,它们应该被定义为接口,为UseCase提供相应的输入和输出,可以直接对实体进行CRUD等操做。或者它们能够暴露一些更复杂的操做行为,如过滤,聚合等,具体的实现细节能够由外层来实现。
数据库和API的实现都应该放在这里,好比上面示例中,能够将DAO,Retrofit,json解析等放在这里。它们应该可以实如今Repository中定义的接口,是具体的实现细节,可以对实体类进行直接操做。
你能够像前面UML图中演示的那样,组合你的MVPView
和MVPPresenter
,让它们更容易被管理和维护。
首先定义BaseView
和BasePresenter
,在BaseView
中我是用了RxJava的Observable
做为结果类型。:
public interface BaseView<T> {
void showData(Observable<T> data);
void showError(String errorMessage);
}
复制代码
public interface BasePresenter<V> {
void attachView(V view);
void detachView();
}
复制代码
假设你有一个根据城市ID获取该城市已上映电影的需求,那么你能够这样组合你的MovieView
和MoviePresenter
接口:
interface MovieContract {
interface Presenter<Request, Result> extends BasePresenter<View<Result>> {
void loadData(Request request);
}
interface View<Result> extends BaseView<Result> {
void showProgress();
}
}
复制代码
泛型的加入,有效保证了数据的类型安全。
接下来实现你本身的XXXPresenter
和XXXView
接口的实现类,就像这样:
class MoviePresenterImp implements MovieContract.Presenter<MovieUseCase.Request, List<MovieEntity>> {
@Override public void attachView(UserContract.View<List<MovieEntity>> view) {
/*subscribe MovieUseCase and do some initialization*/
}
@Override public void detachView() {
/*unsubscribe MovieUseCase and release resources*/
}
@Override public void loadData(MovieUseCase.Request request) {
/*load data from MovieUseCase*/
}
}
class MovieActivity extends AppCompatActivity implements MovieContract.View<List<MovieEntity>> {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*also initialize the corresponding presenter*/
}
@Override public void showData(Observable<List<MovieEntity>> data) {
/*show data and hide progress*/
}
@Override public void showError(String errorMessage) {
/*show error message and hide progress*/
}
@Override public void showProgress() {
/*show progress*/
}
}
复制代码
关于示例中的UseCase.Request
来自于Clean Architecture: Dynamic Parameters in Use Cases:在XXXUseCase
中建立静态内部类Request
做为动态请求参数的容器。其实这很好理解,并且也彻底正确,由于UseCase
就是你定义业务规则的地方,把业务(请求)条件与业务规则定义组合在一块儿不只容易理解也更方便管理。不过我会在下篇文章中介绍另外一种动态参数方式,也是我一直在使用的。
我相信你和我同样,在搭建框架的过程当中遭遇着各式各样的挑战,从错误中吸收教训,不断优化代码,调整依赖关系,甚至从新组织模块结构,这些你作出的改变都是想让架构变得更健壮,咱们一直但愿应用程序可以变得易开发易维护,这才是真正意义上的团队受益。
不得不说,搭建应用架构的方式多种多样,并且我认为,没有万能的,一劳永逸的架构,它应该是不断迭代更新,适应业务的。因此说,你能够按照文中提供的思路,尝试着结合业务来构建你的应用程序。
另外值得一提的是,若是你想作的更好,能够为你的项目加入模板化,组件化等策略,由于并无说一个项目只能使用一种框架结构。: )
最后,但愿这篇文章可以对你有所帮助,若是你有其余更好的架构思路,欢迎分享或与我交流。