健壮且可读的安卓架构设计

自接触Android以来,我一直在寻找一种比较健壮的开发方法。譬如避免在UI线程进行IO操作,防止重复的网络请求,对重要数据进行缓存并且准确的更新这些缓存等等。当然,代码结构也要保持尽量清晰。

本文并不是给你提供一个权威精准的解决方案,更多的是去探讨在灵活性、可读性和健壮性之间有着很好平衡的App的一种开发方式。

一些现有的解决方案

在Android的初期版本,许多人处理多任务时会选择 AsyncTask 。大体上来说,AsyncTask非常难用,许多文章也提到了它的问题。后来,Honeycomb(3.0)引入了可配置性更好的 Loaders。到了2012年,基于Android Service的开源项目Robospice问世,带来了新的解决方案,这里介绍了 Robospice的工作原理。

Robospice 比起 AsyncTask 的确好太多了,但是依然存在一些问题。比如下面这段常见代码,通过Robospice在Activity中发起一个请求的过程。你并不需要细读,只要有个大概的概念就好:

然后是请求的具体代码:

存在的问题

  1. 你需要为每个请求都做上述的处理,代码会显得很臃肿:

– 对于你的每种请求你都需要继承SpiceRequest写一个特定的子类。
– 同样的,对于每种请求你都需要实现一个RequestListener来监听。
– 如果你的缓存过期时间很短,用户就需要花较长时间等待你的每个请求结束。
RequestListener持有了Activity的隐式引用,那么是不是还需要内存泄露的问题。

综上,这并不是一个很好的解决方案。

五步,让程序简洁而健壮

在我开始开发Candyshop的时候,我尝试了其他的方法。我试图通过混合一些拥有有趣特性的库来构造一个简单而健壮的解决方案。这是我用到的库的列表:
* AndroidAnnotations用来处理后台任务EBean等等……
* Spring RestTemplate用来处理 REST(含状态传输)的网络请求,这个库和AndroidAnnotations配合的非常好。
* SnappyDB这个库主要用来将一些 Java 对象缓存到本地文件中。
* EventBus 通过 Event Bus 来解耦处理 App 内部组建间的通讯。

下图就是我将要详细讲解的整体架构:

article1_global_schema--2-

第一步 一个易于使用的缓存系统

你肯定会需要一个持久化的缓存系统,保持这个系统尽可能简单。

第二步 一个符合REST的Client

这里我通过下面的例子来说明。记得要确保你使用 REST API 放在同一个地方。

第三步 应用级的事件总线(Event Bus)

在程序最初的时候就初始化Event bus对象,然后应用的全局都可以访问到这个对象。在Android中, Application初始化是一个很好的时机。

第四步 处理那些需要数据的Activity

对于这一类的Activity,我的处理方式和Robospice非常类似,同样是基于Service解决。不同的是,我的Service并不是Android提供的那个,而是一个常规的单例对象。这个对象可以被App的各处访问到,具体的代码我们会在第五步进行讲解,在这一步,我们先看看这种处理Activity代码结构是怎么样的。因为,这一步可以看到的是我们简化效果最强烈的部分!

一行代码完成对用户数据的请求,同样也只需要一行代码来解析请求所返回的数据。对于通讯录等其他数据也可以用一样的方式来处理,听起来不错吧!

第五步——单例版的后台服务

正如我在上一步说的那样,这里使用的Service并不是Android提供的Service类。其实,一开始的时候,我考虑使用Android提供的Services,不过最后还是放弃了,原因还是为了简化。因为 Android提供的Services通常情况下是为那些在没有Activity展示情况下但还需要处理的操作提供服务的。另一种情况,你需要提供一些功能给其他的应用。这其实和我的需求并不完全相符,而且用单例来处理我的后台请求可以让我避免使用复杂的借口,譬如:ServiceConnection,Binder等等……
这一部分可以探讨的地方就多了。为了方便理解,我们从架构切入展示当Activity调用getUser()getContacts()的时候究竟发生了什么。

你可以把下图中每个serial当作一个线程:

article1_serials--3-

正如你所看到的,这是我非常喜欢的模式。大部分情况下用户不需要等待,程序的视图会立刻被缓存数据填充。然后,当抓取到了服务端的最新数据,视图数据会被新数据替代掉。与此对应的是,你需要确保你的Activity可以接受多次同样类型的数据。在构建Activity的时候记住这一点就没有任何问题啦。
下面是一些示例代码:

似乎每个请求之中的代码还是有点多!实际上,这是我为了更好说明才进行了展开。不难发现,这些请求都遵守了类似的模式,所以你可以很容易的构造一个 Helper 来简化他们。比如 getUser()可以是这样的:

那么serial是用来做什么的? 让我们看看文档是怎么说的:
> 默认情况下,所有@Background的匿名方法都是并行执行的。但是如果两个方法使用了同样名字的serial则会顺序运行在同一个线程中,一个接着一个执行。

虽然把网络请求放在一个线程中顺序执行可能会导致性能下降,但是这使得“先POST然后GET获得数据”的那类事务处理起来非常容易,这是个特性值得为此牺牲一些性能。退一步讲,如果你真的发现性能不可接受,还是可以很容易使用多个serial来解决。现在版本的Candyshop中,我同时使用了四个不同的serial

总结

这里描述的解决方案是我几个月前想到的很初级的一个想法。今天,我已经解决掉所有遇到的特殊情况,并且非常享受在这样的架构下开发。当然,这个方案中还有一些很棒的东西我想要和大家分享,比如:错误处理、缓存超时机制、POST请求、对无用操作的忽略,但是因为篇幅原因这里我就不继续讲述了。

那么,你是否也找到了能让你享受每天工作的框架?

收藏 5 评论

关于作者:zerob13

一个喜欢折腾的人个人网站: http://zerob13.in,新浪微博: @zerob13 个人主页 · 我的文章 · 1

相关文章

可能感兴趣的话题



直接登录
最新评论
  • afpro   2014/04/30

    以前 我们公司和服务器的交互代码也非常多非常难改
    直到 我做了一个代码生成器 自定义一个类JAVA文法 写接口 自动生成实现

  • 楼主,功底深厚啊。我有个问题想问一下,Handler也是处理异步线程的,与Asyntask和Service又有啥区别,用哪个比较好呢。

    • douzifly   2014/05/08

      Handler是配合带Looper和MessageQueue的线程用的,Looper是线程中的消息循环,处理MessageQueue中的消息。Handler 的作用是向MessageQueue插入消息以及处理Looper对消息的执行,所以 Handler 中 handleMesage 函数并不一定执行在主线程上,而是执行在Looper所在的线程上。

      AsyncTask 是对线程池的一种简单封装,excute的时候,会把doInBackground的代码交给线程池的一个线程来处理,并且在执行前后会在主线程回调相关函数。

      Service跟线程是没有关系的,只是基本Android基本组件之一,提供了类似Actiivty但不一样的生命周期管理。Service默认是在调用线程同步执行的,当然Service内部可以开启线程异步执行,也可以运行在单独的进程中。

      希望对你有帮助,如有错误,请指正,谢谢。

    • juyujuyu Android研发 2014/08/08

      Service的地位是和Activity一样的,只是Service没有界面、可以后台运行。也就是说,当Activity跳转的时候,Activity会被stop掉,但是线程还在运行的,它的运行就包括Service。
      继承Asyntask实现并它的方法,然后实例化一个对象并启动,就可以很容易就实现在另外一个线程处理事情而不是在UI线程。
      Handler比较抽象,用法也比较奇葩,按照楼主的水平,先搞懂线程、Asyntask和Service再来搞定handler比较适合。

  • KuX   2014/07/21

    一开始的代码写错了- -异常处理是on failure,另外获取到模型的是on success。
    如此看来RoboSpice似乎也无法避免跟HandlerLeadk类似的问题,在处理回调事件似乎无法保证不跟context关联。只是如果在应用层与网络层加多层缓存层的话,缓存层使用service的话似乎就没有问题了?0 0

跳到底部
返回顶部