Android Application Architecture
我们从标准活动和AsyncTasks到由RxJava支持的基于MVP的现代架构的旅程。
Android开发生态系统变得非常快。每周都会创建新工具,更新Lib,写博客文章和发言。如果你去度假一个月,当你回来的时候会有一个新版本的支持库和/或Play服务。
我已经使用ribot团队制作Android应用程序三年多了。在此期间,我们用于构建Android应用程序的架构和技术不断发展。本文将通过解释我们的学习,错误和这些架构变化背后的推理,带您走过这段旅程。
旧时代
回到2012年,我们的代码库用于遵循基本结构。我们没有使用任何网络库,AsyncTasks仍然是我们的朋友。下图显示了大致的架构。
初始架构
代码分为两层:负责从REST API和持久性数据存储检索/保存数据的数据层;和视图层,其职责是在UI上处理和显示数据。
APIProvider提供了使活动和片段能够轻松地与REST API交互的方法。这些方法使用URLConnection和AsyncTasks在单独的线程中执行网络调用,并通过回调将结果返回到活动。
以类似的方式,CacheProvider包含从SharedPreferences或SQLite数据库检索和存储数据的方法。它还使用回调将结果传递回活动。
问题
这种方法的主要问题是View层有太多的责任。想象一个简单的常见场景,其中应用程序必须加载博客帖子列表,将它们缓存在SQLite数据库中,并最终显示在ListView上。活动必须做到以下几点:
在APIProvider中调用loadPosts(回调)方法
等待APIProvider成功回调,然后在CacheProvider中调用savePosts(callback)。
等待CacheProvider成功回调,然后在ListView上显示帖子。
单独处理来自APIProvider和CacheProvider的两个潜在错误回调。
这是一个非常简单的例子。在实际情况下,REST API可能不会返回视图需要的数据。因此,活动必须以某种方式在显示数据之前转换或过滤数据。另一个常见的情况是loadPosts()方法接收需要从其他地方获取的参数,例如Play Services SDK提供的电子邮件地址。很可能SDK将使用回调异步返回电子邮件,这意味着我们现在有三个层次的嵌套回调。如果我们继续增加复杂性,这种方法将导致所谓的回调地狱。
综上所述:
活动和碎片变得非常大,难以维护
太多的嵌套回调意味着代码是丑陋的,很难理解这么痛苦地做出更改或添加新的功能。
单元测试变得具有挑战性,如果不是不可能的,因为很多逻辑生活在活动或片段内,是艰苦的单元测试。
一个由RxJava驱动的新架构
我们按照以前的方法约两年。在此期间,我们做了几个改进,轻微缓解了上述问题。例如,我们添加了几个帮助类来减少活动和片段中的代码,我们开始在APIProvider中使用Volley。尽管有这些变化,我们的应用程序代码还不是测试友好的,回调地狱问题仍然发生得太频繁。
直到2014年,我们才开始阅读关于RxJava。在一些示例项目上尝试之后,我们意识到这最终可以是嵌套回调问题的解决方案。如果你不熟悉反应式编程,你可以阅读这个介绍。简而言之,RxJava允许您通过异步流管理数据,并为您提供了许多运算符,您可以应用于流,以便变换,过滤或组合数据。
考虑到我们在过去几年中经历的痛苦,我们开始考虑一个新应用程序的架构如何看起来。所以我们想出了这个。
RxJava驱动架构
与第一种方法类似,该架构可以分为数据层和视图层。数据层包含DataManager和一组帮助程序。视图层由Android框架组件(如Fragments,Activities,ViewGroups等)构成。
辅助类(图中第三列)具有非常具体的职责,并以简明的方式实现它们。例如,大多数项目都有帮助访问REST API,从数据库读取数据或与第三方SDK交互。不同的应用程序将有不同数量的助手,但最常见的是:
PreferencesHelper:在SharedPreferences中读取和保存数据。
DatabaseHelper:处理访问SQLite数据库。
改进服务:执行对REST API的调用。我们开始使用Retrofit而不是Volley,因为它为RxJava提供了支持。它也更好使用。
辅助类中的大多数公共方法将返回RxJava Observable。
DataManager是架构的大脑。它广泛地使用RxJava运算符组合,过滤和转换从辅助类检索的数据。 DataManager的目的是通过提供准备显示的数据来减少Activity和Fragments必须做的工作量,并且通常不需要任何转换。
下面的代码显示了DataManager方法的外观。此示例方法的工作原理如下:
调用Retrofit服务以从REST API加载博客文章列表
使用DatabaseHelper将帖子保存在本地数据库中以进行缓存。
过滤今天写的博客文章,因为那些是唯一的视图层想要显示的。
public Observable loadTodayPosts() {
return mRetrofitService.loadPosts()
.concatMap(new Func1, Observable>() {
@Override
public Observable call(List apiPosts) {
return mDatabaseHelper.savePosts(apiPosts);
}
})
.filter(new Func1() {
@Override
public Boolean call(Post post) {
return isToday(post.date);
}
});
}
视图层中的组件(如Activities或Fragments)将简单地调用此方法并订阅返回的Observable。一旦订阅完成,Observable发出的不同的帖子可以直接添加到适配器,以便显示在RecyclerView或类似的。
这种架构的最后一个元素是事件总线。事件总线允许我们广播在数据层中发生的事件,以便视图层中的多个组件可以订阅这些事件。例如,DataManager中的signOut()方法可以在Observable完成时发布事件,以便订阅此事件的多个活动可以更改其UI以显示签出状态。
为什么这种方法更好?
RxJava Observables和运算符删除了嵌套回调的需要。
DataManager接管以前是视图图层的一部分的职责。因此,它使Activities和Fragments更轻量级。
将活动和片段中的代码移动到DataManager和助手意味着编写单元测试变得更容易。
清楚地分离职责,使DataManager成为与数据层交互的唯一点,使得这种架构对测试友好。辅助类或DataManager可以轻松地嘲笑。
我们还有什么问题?
对于大型和非常复杂的项目,DataManager可能变得过于。肿和难以维护。
虽然视图层组件(如活动和片段)变得更轻量级,但它们仍然需要处理大量关于管理RxJava订阅,分析错误等的逻辑。
在过去一年中,几个架构模式,如MVP或MVVM已经在Android社区中越来越受欢迎。在对示例项目和文章探索这些模式之后,我们发现MVP可以为我们现有的方法带来非常有价值的改进。因为我们当前的架构分为两层(视图和数据),增加MVP感觉自然。我们只需要添加一个新的演示者层,并将代码的一部分从视图移动到演示者。
基于MVP的架构
数据层保持原样,但它现在称为模型,以更加一致的模式的名称。
演示者负责从模型加载数据,并在结果准备好时调用视图中的正确方法。他们订阅由数据管理器返回的Observables。因此,他们必须处理像调度程序和订阅。此外,如果需要,他们可以分析错误代码或对数据流应用额外的操作。例如,如果我们需要过滤一些数据,并且这个相同的过滤器不可能在其他任何地方重复使用,那么在演示者而不是数据管理器中实现它可能更有意义。
下面你可以看到一个公共方法在演示者看起来像。此代码订阅了我们在上一节中定义的dataManager.loadTodayPosts()方法返回的Observable。
public void loadTodayPosts() {
mMvpView.showProgressIndicator(true);
mSubscription = mDataManager.loadTodayPosts().toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber>() {
@Override
public void onCompleted() {
mMvpView.showProgressIndicator(false);
}
@Override
public void onError(Throwable e) {
mMvpView.showProgressIndicator(false);
mMvpView.showError();
}
@Override
public void onNext(List postsList) {
mMvpView.showPosts(postsList);
}
});
}
mMvpView是此演示者正在协助的视图组件。通常MVP视图是一个Activity,Fragment或ViewGroup的实例。
与以前的架构一样,视图层包含标准框架组件,如ViewGroups,Fragments或Activities。主要的区别是这些组件不直接订阅Observable。它们实现了一个MvpView接口,并提供了简单的方法列表,如showError()或showProgressIndicator()。视图组件还负责处理诸如点击事件的用户交互,并且通过调用演示者中的正确方法来相应地操作。例如,如果我们有一个加载帖子列表的按钮,我们的Activity将从onClick侦听器调用presenter.loadTodayPosts()。
如果你想看到这个基于MVP架构的一个完整的工作示例,你可以在GitHub上查看我们的Android Boilerplate项目。您还可以在ribot的架构指南中阅读更多内容。
为什么这种方法更好?
活动和片段变得非常轻量级。他们唯一的职责是设置/更新UI并处理用户事件。因此,它们变得更容易维护。
我们现在可以通过模拟视图层轻松地为演示者编写单元测试。之前,这段代码是视图层的一部分,所以我们不能单元测试它。整个架构变得非常测试友好。
如果数据管理器变得。肿,我们可以通过将一些代码移动到演示者来缓解这个问题。
我们还有什么问题?
当代码库变得非常大和复杂时,拥有单个数据管理器仍然是一个问题。我们还没有达到这是一个真正的问题,但我们知道,它可能发生。
重要的是要提到这不是完美的建筑。事实上,认为有一个独特和完美的,将永远解决你的所有问题是天真的。 Android生态系统将保持快速发展,我们必须通过探索,阅读和实验,以便我们可以找到更好的方式来继续构建优秀的Android应用程序。
我希望你喜欢这篇文章,你发现它有用。如果是这样,不要忘记点击推荐按钮。此外,我很乐意听到你对我们最新方法的想法。
public Observable loadTodayPosts() {
return mRetrofitService.loadPosts()
.concatMap(new Func1, Observable>() {
@Override
public Observable call(List apiPosts) {
return mDatabaseHelper.savePosts(apiPosts);
}
})
.filter(new Func1() {
@Override
public Boolean call(Post post) {
return isToday(post.date);
}
});
}
public Observable loadTodayPosts() {
return mRetrofitService.loadPosts()
.concatMap(new Func1, Observable>() {
@Override
public Observable call(List apiPosts) {
return mDatabaseHelper.savePosts(apiPosts);
}
})
.filter(new Func1() {
@Override
public Boolean call(Post post) {
return isToday(post.date);
}
});
}
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 借力计算机视觉及深度学习,纽卡斯尔大学开发实时、自动化奶牛跛行检测系统
本文首发自 HyperAI超神经微信公众号~ 内容一览:近期,纽卡斯尔大学联合费拉科学有限公司联合开发了一个针对多头奶牛的自动化、实时跛行检测系统。该系统能够按照跛行评分系统将奶牛进行分类,并且准确度高达 94%-100%。目前,该研究成果已发表在《Natur…