阅读更多



软件代码库各个不同的部分应当彼此独立,其整体却犹如一部运转良好的机器

Android的开发生态系统发展迅速,每周都有变化,人们不停地创建新工具、更新资源库、撰写博文、发表演讲。只要享受一个月的假期,回来的时候支持库和/或Play Services都更新换代了。

笔者与ribot团队合作开发Android应用已有超过三年时间。在这段时间里,我们用来构建Android应用的架构与技术一直在不断进化。在本文中,我们将具体阐述这些架构变更背后的经验、失误还有推论。

过去
早在2012年,我们的代码库总是采用基础架构,并未使用任何网络库,还是用老一套的AsyncTasks。下面的图表粗略地演示了这个架构。



初始架构


代码共分两层:控制从REST API检索/保存数据的数据层(data layer),还有负责在UI上控制与展示数据的视图层(view layer)。

APIProvider提供方法,让Activities和Fragments能够很容易地与REST API交互。运用URLConnection和AsyncTasks来执行单独线程中的网络调用,并通过回调向Activities返回结果。

类似地,CacheProvider包含了从SharedPreferences或SQLite数据库检索存储数据的方式,通过回调将结果返回给Activities。

问题

这个办法的主要问题在于,视图层责任过大。试想一个简单的通用场景:应用程序在加载文章列表时,将其缓存到SQLite数据库中,并最终展示在ListView中。具体执行如下:
  • 调用APIProvider中的loadPosts(回调)方法;
  • 等待APIProvider成功回调,然后调用CacheProvider中的savePosts(回调);
  • 等待CacheProvider成功回调,然后在ListView中显示文章;
  • 分别处理APIProvider和CacheProvider的回调错误。

这是个简单的例子。在真实案例场景中,REST API可能不会按照浏览所需的那样返回数据,因此Activity会设法在展示数据之前对其进行转换或过滤。另一个常见案例:在使用loadPosts() 方法获取需要从别处拿到的参数时,比如由Play Services SDK提供的电子邮件地址,很有可能SDK会通过回调异步返回邮件,也就是说我们现在有三层嵌套回调(nested callbacks)。如果复杂性继续增加,这个方法会导致所谓的回调地狱(callback hell)。

总结:
  • Activities和Fragments逐渐过大而难以维护;
  • 嵌套回调太多,导致代码丑陋不堪,难以理解与修改,也不好增加新功能;
  • 单元测试也颇有难度,即便勉强进行,由于Activities或Fragments中包含有大量逻辑,相关工作也会相当费劲。

由RxJava驱动的新架构

差不多在两年时间中,我们都在采用前面描述的那种架构。在那段时间里,我们做了一些修正,但是解决问题时收效甚微。例如,我们增加了一些helper类,以减少Activities和Fragments中的代码,并开始在APIProvider中使用Volley。尽管如此,在应用代码测试时还是面临测试友好性问题与回调地狱频繁出现的问题。

直到2014年我们发现了RxJava,在尝试了几个样例项目后,我们发现这可能是解决嵌套回调问题的终极解决办法。如果对响应式编程不熟悉的话,可以参考这篇简介。简单来讲,RxJava允许用户通过异步流管理数据,并提供很多可用在事件流中的operator,方便用户修改、筛选或合并数据。

考虑到前些年遭受的痛苦,我们开始考虑新应用的架构是什么样的,然后得出了这个。



与头一个方法类似,这个架构也可以分为两层,分别是数据层与视图层。数据层包含DataManager,还有一系列helper。视图层由诸如Fragments、Activities、ViewGroups等Android框架组件构成。

Helper类(图表第三列)包含具体的职责,同时执行方式也很简洁。例如大多项目包含访问REST API的helper,从数据库读取数据的helper或者与第三方SDK交互的helper。不同的应用程序包含不同数量的helper,不过最常见的helper有:
  • PreferencesHelper:在SharedPreferences中读取与保存数据。
  • DatabaseHelper:处理SQLite数据库的访问。
  • Retrofit服务:从REST API执行调用。我们使用Retrofit来代替Volley,因为它提供了对RxJava的支持,也更好用。

大多数helper类中的公共方法会返回RxJava Observables。

DataManager是这个架构的核心,它广泛运用了RxJava operator来合并、筛选与转换从helper类中获得的数据。DataManager的目标是通过提供准备显示的数据,来减少Activities和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);
                        }
                    });
    }

像Activities或Fragments之类的视图层组件会简单调用这个方法,并订阅返回的Observable。一旦订阅完成,Observable所发出的不同文章就能直接加入到Adapter中,以便在RecyclerView或类似组件中显示。

这个架构的最后一个元素是Eventbus(事件总线),它允许我们将数据层的事件进行广播,因此视图层的多个组件能够订阅这些事件。例如,DataManager中的signOut()方法可以在Observable完成时发布一个事件,让多个订阅这个事件的Activities修改UI,显示为登出状态。

为什么这个方法更好?
  • RxJava Observables和operators使得嵌套回调不再有必要。
  • DataManager接管了之前视图层的部分职责,从此Activities和Fragments更为轻量。
  • 将代码从Activities和Fragments中转移到DataManager和helpers中,意味着单元测试写起来更简单。
  • 明确的职责分离,加上使用DataManager作为唯一与数据层的交互点,这些做法让这个架构测试时更为友好。Helper类或DataManager很容易模拟。

还有什么问题呢?
  • 对于非常复杂的大型项目来说,DataManager可能会过于庞大而难以维护。
  • 尽管Activities与Fragments之类的视图层组件逐渐更为轻量级,仍然需要处理相当数量的逻辑,比如管理RxJava订阅、分析错误等。

集成模型视图显示

在过去的一年中,像MVP、MVVM这样的一些架构模型在Android社区受到了热捧。在样例项目文章中研究过这些模型之后,我们发现MVP能够对我们目前的方法带来很有价值的改进。由于我们目前的架构分为两层(视图与数据层),加上MVP也很自然。我们只需增加一个新的展示层(a new layer of presenters),将一部分代码从视图层移过去就可以了。



基于MVP的架构


数据层保持不变,不过现在改名为模型层(Model),以便名符其实。

展示层控制加载来自模型层的数据,并在结果准备好之后调用视图层的正确方法来显示。它订阅DataManager返回的Observables,因此必须处理类似调度与订阅之类的工作。此外,它可以分析错误代码,或者在需要时在数据流中应用额外操作。例如,如果我们需要筛选一些数据,而这个筛选无法在其他地方复用,那么用展示层来实现会比在DataManager实现要更好。

下面是在展示层中公共方法的案例。这部分代码订阅了从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是这个展示层正在assist的视图层组件。一般MVP视图是Activity、Fragment或ViewGroup实例。

就像之前的架构那样,视图层包含像ViewGroups、Fragments或Activities这样的标准框架组件。这些组件的主要区别在于没有直接订阅Observables,而是执行MVP视图,提供一系列类似showError() 或showProgressIndicator()之类的简明方法。视图组件还控制处理类似点击事件之类的与用户交互,并通过调用展示层的正确方法来执行。例如,如果我们有一个加载文章列表的按钮,Activity就会从onClick监听那里调用presenter.loadTodayPosts()。
引用
想要查看基于MVP的完整架构,请查看Android Boilerplate project on GitHub或者ribot’s architecture guidelines

为什么这个方法更好?
  • Activities和Fragments都很轻量。只需负责建立/更新UI,处理用户事件。因此更容易维护。
  • 我们现在能够通过模拟视图层,从展示层书写简单的单元测试了。之前这些代码是视图层的一部分,没办法进行单元测试。而且整体架构对测试更加友好。
  • 如果DataManager过于庞大,我们可以通过将一些代码挪到presenter中缓解这个问题。

还有什么问题?
  • 在代码库变得非常庞大与复杂时,单一的DataManager仍是个问题。我们尚未触及到真实问题点,不过迟早会碰到。

需要注意的是,这个架构并不完美。事实上,认为它是唯一而且完美的架构,能够一劳永逸的解决问题这样的想法太过天真。Android的生态系统会继续保持高速发展,我们必须持续探索、阅读、实验,才能找到构建优秀Android应用的更佳途径。

文章来源:Android Application Architecture
  • 大小: 112.2 KB
  • 大小: 33.1 KB
  • 大小: 58.3 KB
  • 大小: 68 KB
0
0
评论 共 2 条 请登录后发表评论
2 楼 shuhen2011 2016-04-03 19:12
YiBuXiaoCai 写道
程序员群 2177712 

该群正在解散中
1 楼 YiBuXiaoCai 2015-12-18 10:13
程序员群 2177712 

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 英语吵架百句

    英语吵架百句 作者:劳尔(xxx.xxx.xxx.xxx) 2002/08/02 08:53  当前论坛: 幽默天地 [humorous.xilubbs.com]  互换联接:http://yaxlik214.xilubbs.com  1. Stop complaining! 别发牢骚! 2. You make me sick! 你真让我恶心! 3. What’s wrong with you

  • 英文脏话大全

    想要知道如何用英文骂人吗? 想要知道如何有文化地骂人吗? 想要知道如何在骂人时还能显得很厉害吗? 想要知道如何在骂人时惊艳所有人吗?

  • xxoo

    time = input(‘请输入日期 YYYY-MM-DD:’) date = time.split(’-’) year = int(date[0]) month = int(date[1]) day = int(date[2]) li = [31,28,31,30,31,30,31,31,30,31,30,31] num = 0 if((year%40))and(year%100!=0)or(...

  • 极路由3(HC5861)刷Padavan固件教程

    1.准备工作可以先看看7620老毛子Padavan固件的介绍帖http://www.right.com.cn/forum/thread-161324-1-1.html。(1)下载并安装“WinSCP”软件,它可以向路由器里面传输文件。安装的时候,一路下一步就行。下载“Putty”软件,是一个exe文件,不需要安装。刷机工具及极路由3的资源:https://pan.lanzou.com/i0l0f3...

  • 计算机考证照片一级

    电子照片采集标准: 一、背景要求:背景布选取浅蓝色,要求垂感和吸光好。 可以是棉布,毛涤等。 二、成像要求:成像区上下要求头上部空1/10,头部占7/10,肩部占1/5,左右各空1/10。采集的 图象大小为192 ×144(高×宽)。成像区大小为48mm×33mm(高×宽)。 三、文件格式要求:要求存储为JPG格式,图象文件名为*JPG,其中“*”为考生身份证号。 ...

  • 推荐10个常用的国外BT种子下载网站

    这些都是国外较为知名的BT种子下载站点,其上的资源一般是很丰富的。各位务必搜藏起来,总有它们派上用场的时候。 1. Mininova特点:东西比较多资格比较老,地球人都知道资格老的总能排第一位。 2. The Pirate Bay特点:东西比较多而且有中文,找东西方便,可惜速度有点慢。 3. IsoHunt特点:更新比较快,速度也不错。 4. Torrentz特点:这个网址比较好记,随...

  • av_seek_frame 帧 乱码 马赛克

    在执行av_seek_frame 之前,要先执行avcodec_flush_buffers,zheyan

  • 哪十句英文脏话不能乱说‏

    1. I'm so fed up with your BS. Cut the crap. 我受够了你的废话,少说废话吧。  美女(美国的女人)是不喜欢说shit这个不雅的字的,所以她们就说shoot,或是BS(=Bull shit)来表示她们还是很有气质的. "Cut your crap."是当你听到对方废话连篇,讲个不停时,你就可以说, "Cut the crap."相当于中文里的废话...

  • 英语面试常用口语900句

    (一) 高频词汇: 可以拿来形容自己的形容词。除开我们都熟知的一些基本的词汇可以用来形容自己,比如honest, reliable, trustworthy等,我们还可以运用一些“高级词汇”。   用形容词的形式来形容自己 1. committed 投入的,坚决的 例句:I am committed to meeting deadlines. 我致力于在截止期限前完成工作。 ...

  • 使用频率最高的美语口语296句(本人精心整理,按使用频率排序)

    1. Have a nice day. 祝你今天愉快 2. So far, so good. 目前为止一切都好 3. Take it or leave it. 要就要,不要就拉倒 4. Keep it up! 继续努力,继续加油 5. Good for you. 好啊!做得好! 6. Time flies!时光如梭 7. Time is money. 时间就是...

  • 嵌套字典和集合练习范例

    嵌套字典和集合练习范例 嵌套字典练习: av_catalog = { &quot;欧美&quot;:{ &quot;www.youporn.com&quot;: [&quot;很多免费的,世界最大的&quot;,&quot;质量一般&quot;], &quot;www.pornhub.com&quot;: [&quot;很多免费的,也很大&quot;,&quot;质量比yourporn高点&quot;], &qu

  • 转 ABAP_ALV_Function方式与OO方式(较为简单、普通的ALV)

    ABAP_ALV_Function方式与OO方式(较为简单、普通的ALV) 分类:SAP ABAP2013-01-31 09:581511人阅读评论(0)收藏举报 目录 一、ALV简介 1、简介 2、ALV_GRID介绍 3、其它描述 二、开发ALV的基本流程 三、ALV相关开发细节 1、标准ALV与对象ALV的共同开发细节 2、标准ALV开发相关细节 ...

  • 看美国影片必然要了解的英文粗语脏话

    看美国影片必然要了解的英文粗语脏话1,能融入情节,不愤无以表达2,知道这是下识人的,要不被人鸟了还蒙着3,恶俗点,会去下识人一,优雅骂人 1. Stop complaining! 别发牢骚! 2. You make me sick! 你真让我恶心! 3. What’s wrong with you? 你怎么回事? 4. You shouldn’t have done that! 你真不应该

  • ClamAV实战

    关于ClamAV ClamAV是一个C语言开发的开源病毒扫描工具用于检测木马/病毒/恶意软件等。可以在线更新病毒库,Linux系统的病毒较少,但是并不意味着病毒免疫,尤其是对于诸如邮件或者归档文件中夹杂的病毒往往更加难以防范,而ClamAV则能起到不少作用。 安装: 安装后查看版本信息 使用-h查看帮助 简单的扫描一个文件如图报错时 是因为此时需要有可用的病毒库文件,同时用户和组的权...

  • 欢迎使用CLamav杀毒软件管理Linux系统

    Linux系统安全防御

  • flamingo的中文意思是什么,《flamingo什么意思》在线 - 闰看365影院

    这是关于《flamingo什么意思》的简单介绍:We return to the story of Faith Howells some 18 months after her husband Evan returned to Abecorran in time to see his wife in the arms of Steve Baldini. Evan has pulled Faith ...

  • 流媒体播放

    1. 这里的流媒体地址是指服务端那边已经调好格式的可以在ios上播放的视频流。 下面提供几个视频流的地址: NSString *linkStr = http://61.160.227.6/rtencode_m3u8?bl=/f4v/61/140783661.h264_2.f4v&amp;t=8&amp;em=1&amp;se=c629000050724fef&amp;k=8bb5b375a...

  • 看美片必备的英语常识

    看美片必备的英语常识 1.What the *** is going on?(到底他母亲的怎么回事?)        通常此话出于黑人之口,且口气最宜为疑惑,不解,愤怒等等。        若是白人则多数时候会说-What the hell is going on?        意义相同而适用于更多场合。        说此话之人身份通常为上级,且相处较久。        不过如果你出差回家时...

Global site tag (gtag.js) - Google Analytics