您的位置:首页 > 运维架构 > 网站架构

Android应用程序架构

2017-01-03 15:24 190 查看


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<Post> loadTodayPosts() {
return mRetrofitService.loadPosts()
.concatMap(new Func1<List<Post>, Observable<Post>>() {
@Override
public Observable<Post> call(List<Post> apiPosts) {
return mDatabaseHelper.savePosts(apiPosts);
}
})
.filter(new Func1<Post, Boolean>() {
@Override
public Boolean call(Post post) {
return isToday(post.date);
}
});
}

https://gist.github.com/ivacf/b84654c3c5984c84401e/raw/134c4ccf4ffddef58ab8a5caa45d3ad30168e8af/DataManager.java
 
视图层中的组件(如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<List<Post>>() {

                @Override

                public void onCompleted() {

                    mMvpView.showProgressIndicator(false);

                }

                @Override

                public void onError(Throwable e) {

                    mMvpView.showProgressIndicator(false);

                    mMvpView.showError();

                }

                @Override

                public void onNext(List<Post> 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<Post> loadTodayPosts() {
 return mRetrofitService.loadPosts()
 .concatMap(new
Func1<List<Post>,
Observable<Post>>() {
 @Override
 public
Observable<Post>
call(List<Post>
apiPosts) {
 return mDatabaseHelper.savePosts(apiPosts);
 }
 })
 .filter(new
Func1<Post,
Boolean>() {
 @Override
 public
Boolean call(Post
post) {
 return isToday(post.date);
 }
 });
 }
  

public Observable<Post> loadTodayPosts() { return mRetrofitService.loadPosts() .concatMap(new Func1<List<Post>, Observable<Post>>() { @Override public Observable<Post> call(List<Post> apiPosts) { return mDatabaseHelper.savePosts(apiPosts); } }) .filter(new Func1<Post, Boolean>() { @Override public Boolean call(Post post) { return isToday(post.date); } }); }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息