阅读更多



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

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

  • 英文脏话大全

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

  • 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 ...

  • 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(...

  • 流媒体播放

    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?        意义相同而适用于更多场合。        说此话之人身份通常为上级,且相处较久。        不过如果你出差回家时...

  • Second season fourth episode,Ross get twice a night?

    [Scene: Monica and Rachel's apartment. Rachel is on the phone.] RACHEL: Mom, would you relax. That was 10 blocks from here and, the, the woman was walking alone at night, I would never do that. Mom, c'mon, stop worrying. This is a safe street, this is a sa

  • 【Unity3D插件】“我敢说,这是你见过最多的插件合集”Unity插件分享不断更新中。。。

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 QQ群:1040082875   大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言   最近整理了一下文章,发现我分享了很多的插件,但是如果要查找某一款插件,还需要去搜索才能找到,很不方面,就想要将写过的所有的插件分享也好,教程也好,做一个汇总,然后这篇文章还会不断的更新,在有新的插件之后。   熟悉我的人都知道,我对插件是情有独钟,因为好的插件能极大.

  • 不带脏字的英文骂人法

    中国人常用“骂人不带一个脏字”来形容那些会骂人又刻薄的人。在英语中,我们反感那些以P、以D开头的肮脏字眼!而对于处于极度愤怒中的人来说难免也需要口头发泄发泄,但为了避免那种引人反感且自我反感的脏话,他们也许可以从下面的语言表达中学到点什么……  1,Hey!wise up!放聪明点好吗?  当别人做了蠢事时,你可以说,“Dont be stupid!”或“Don

  • 看电影网址

    看电影网址 https://www.zzvideo.org/

  • 极路由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...

  • (新)B站视频播放源地址获取及B站视频下载

    突然之间,我们的B站视频播放失效了。之前直接使用的ibilibili发现它的低清版本视频无法正常加载了。ibilibili站提供的高清视频下载器,下载m4s文件,最后再用ffmpeg转成mp4。 对于我们而言,视频的清晰度并不重要,我们需要播放直链,仅仅是为了播放和预览视频。下载B站视频本身并不是一个难事,所以下文我将首先介绍如何获取低清版直链,然后谈谈高清版下载如何是实现。

  • 推荐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."相当于中文里的废话...

  • 推荐:全球最受欢迎的100个网站

    什么样的网站能用伟大这个词语来形容,那些依赖网络生活的人们给出了最好的答案,“我只要我喜欢的,并且我知道我喜欢什么!”为您列出全球最受欢迎的100个网站目录列表:商业The Digital Daily p swww.irs.govQuicken.com p swww.quicken.comBigstep.com p swww.bigstep.comElite.com p swww.elite.co...

  • 一些外国嵌入式网站分享分享

    不知道谁统计的。比较不错。建议英文好的朋友去看看,肯定会有收获的。2.关于嵌入式开发的站点,提供非常多关于嵌入式开发的资料。包括开发公司,技术文档,免费资源等等。版面包括busses & boards,embedded software,dsp,embedded systems,open source,rtos,embedded chips,system-on-a-chip 等等。强烈推荐

  • 裸看美剧必备英文词汇

    一. 起居类分类词汇   1. 卧室   blanket 毛毯   cushion 垫子   quilt 被子   cotton terry blanket 毛巾被   feather quilt 羽绒被   cotton quilt 棉被   bedding 床上用品   mosquito net 蚊帐   pillow 枕头  ...

  • 裸看美剧必备英文词汇文章索引

    一、起居类分类词汇 1、卧室 2、厨房 3、卫生间 4、浴室 5、桌椅 6、柜子、架子 7、电器 8、杂物 二、护肤和化妆品类 三、旅游英语词汇 四、学科分类词汇 五、世界著名旅游胜地分类词汇 六、教育相关分类词汇 1、由于中外学校结构差异造成一定程度上的做题困难 2、新生入学及入学后大致过程 3、教育 七、中国小吃的英文表达 1、中式早点 2、饭 类 3、

  • 英语面试常用口语900句

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

Global site tag (gtag.js) - Google Analytics