关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android应用架构变更背后的经验、失误与推论

    Android应用架构变更背后的经验、失误与推论

    2015-12-18 00:00:00 出处:Sam
    分享

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

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

    Android应用架构变更背后的经验、失误与推论

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

    过去

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

    Android应用架构变更背后的经验、失误与推论

    初始架构

    代码共分两层:控制从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 ,方便用户修改、筛选或合并数据。

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

    Android应用架构变更背后的经验、失误与推论

    与头一个方法类似,这个架构也可以分为两层,分别是数据层与视图层。数据层包含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使得嵌套回调不再有必要。

    Android应用架构变更背后的经验、失误与推论

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

    还有什么问题呢?

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

    集成模型视图显示

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

    Android应用架构变更背后的经验、失误与推论

    基于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应用的更佳途径。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    抖音安全与信任开放日:揭秘推荐算法,告别单一标签依赖

    ultraedit编辑器打开文件时,总是提示是否转换为DOS格式,如何关闭?

    Cornell大神Kleinberg的经典教材《算法设计》是最好入门的算法教材

    从 Microsoft 下载中心安装 Windows 7 SP1 和 Windows Server 2008 R2 SP1 之前要执行的步骤

    Llama 2基于UCloud UK8S的创新应用

    火山引擎DataTester:如何使用A/B测试优化全域营销效果

    腾讯云、移动云继阿里云降价后宣布大幅度降价

    字节跳动数据平台论文被ICDE2023国际顶会收录,将通过火山引擎开放相关成果

    这个话题被围观超10000次,火山引擎VeDI如此解答

    误删库怎么办?火山引擎DataLeap“3招”守护数据安全

    IT头条

    平替CUDA!摩尔线程发布MUSA 4性能分析工具

    00:43

    三起案件揭开侵犯个人信息犯罪的黑灰产业链

    13:59

    百度三年开放2.1万实习岗,全力培育AI领域未来领袖

    00:36

    工信部:一季度,电信业务总量同比增长7.7%,业务收入累计完成4469亿元

    23:42

    Gartner:2024年全球半导体营收6559亿美元,AI助力英伟达首登榜首

    18:04

    技术热点

    iOS 8 中如何集成 Touch ID 功能

    windows7系统中鼠标滑轮键(中键)的快捷应用

    MySQL数据库的23个特别注意的安全事项

    Kruskal 最小生成树算法

    Ubuntu 14.10上安装新的字体图文教程

    Ubuntu14更新后无法进入系统卡在光标界面解怎么办?

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。