自接触Android以来,我一直在寻找一种比较健壮的开发方法。譬如避免在UI线程进行IO操作,防止重复的网络请求,对重要数据进行缓存并且准确的更新这些缓存等等。当然,代码结构也要保持尽量清晰。
本文并不是给你提供一个权威精准的解决方案,更多的是去探讨在灵活性、可读性和健壮性之间有着很好平衡的App的一种开发方式。
一些现有的解决方案
在Android的初期版本,许多人处理多任务时会选择 AsyncTask 。大体上来说,AsyncTask非常难用,许多文章也提到了它的问题。后来,Honeycomb(3.0)引入了可配置性更好的 Loaders。到了2012年,基于Android Service的开源项目Robospice问世,带来了新的解决方案,这里介绍了 Robospice的工作原理。
Robospice 比起 AsyncTask 的确好太多了,但是依然存在一些问题。比如下面这段常见代码,通过Robospice在Activity
中发起一个请求的过程。你并不需要细读,只要有个大概的概念就好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
; html-script: false ] FollowersRequest request = new FollowersRequest(user); lastRequestCacheKey = request.createCacheKey(); spiceManager.execute(request, lastRequestCacheKey, DurationInMillis.ONE_MINUTE, new RequestListener<FollowerList> { @Override public void onRequestFailure(SpiceException e) { // On success } @Override public void onRequestSuccess(FollowerList listFollowers) { // On failure } }); |
然后是请求的具体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
; html-script: false ] public class FollowersRequest extends SpringAndroidSpiceRequest<FollowerList> { private String user; public FollowersRequest(String user) { super(FollowerList.class); this.user = user; } @Override public FollowerList loadDataFromNetwork() throws Exception { String url = format("https://api.github.com/users/%s/followers", user); return getRestTemplate().getForObject(url, FollowerList.class); } public String createCacheKey() { return "followers." + user; } } |
存在的问题
- 你需要为每个请求都做上述的处理,代码会显得很臃肿:
– 对于你的每种请求你都需要继承SpiceRequest
写一个特定的子类。
– 同样的,对于每种请求你都需要实现一个RequestListener
来监听。
– 如果你的缓存过期时间很短,用户就需要花较长时间等待你的每个请求结束。
– RequestListener
持有了Activity
的隐式引用,那么是不是还需要内存泄露的问题。
综上,这并不是一个很好的解决方案。
五步,让程序简洁而健壮
在我开始开发Candyshop的时候,我尝试了其他的方法。我试图通过混合一些拥有有趣特性的库来构造一个简单而健壮的解决方案。这是我用到的库的列表:
* AndroidAnnotations用来处理后台任务,EBean等等……
* Spring RestTemplate用来处理 REST(含状态传输)的网络请求,这个库和AndroidAnnotations配合的非常好。
* SnappyDB这个库主要用来将一些 Java 对象缓存到本地文件中。
* EventBus 通过 Event Bus 来解耦处理 App 内部组建间的通讯。
下图就是我将要详细讲解的整体架构:
第一步 一个易于使用的缓存系统
你肯定会需要一个持久化的缓存系统,保持这个系统尽可能简单。
1 2 3 4 5 6 7 8 |
; html-script: false ] @EBean public class Cache { public static enum CacheKey { USER, CONTACTS, ... } public <T> T get(CacheKey key, Class<T> returnType) { ... } public void put(CacheKey key, Object value) { ... } } |
第二步 一个符合REST的Client
这里我通过下面的例子来说明。记得要确保你使用 REST API 放在同一个地方。
1 2 3 4 5 6 7 8 9 10 11 |
; html-script: false ] @Rest(rootUrl = "http://anything.com") public interface CandyshopApi { @Get("/api/contacts/") ContactsWrapper fetchContacts(); @Get("/api/user/") User fetchUser(); } |
第三步 应用级的事件总线(Event Bus)
在程序最初的时候就初始化Event bus对象,然后应用的全局都可以访问到这个对象。在Android中, Application
初始化是一个很好的时机。
1 2 3 4 5 |
; html-script: false ] public class CandyshopApplication extends Application { public final static EventBus BUS = new EventBus(); ... } |
第四步 处理那些需要数据的Activity
对于这一类的Activity
,我的处理方式和Robospice非常类似,同样是基于Service
解决。不同的是,我的Service
并不是Android提供的那个,而是一个常规的单例对象。这个对象可以被App的各处访问到,具体的代码我们会在第五步进行讲解,在这一步,我们先看看这种处理Activity
代码结构是怎么样的。因为,这一步可以看到的是我们简化效果最强烈的部分!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
; html-script: false ] @EActivity(R.layout.activity_main) public class MainActivity extends Activity { // Inject the service @Bean protected AppService appService; // Once everything is loaded… @AfterViews public void afterViews() { // … request the user and his contacts (returns immediately) appService.getUser(); appService.getContacts(); } /* The result of the previous calls will come as events through the EventBus. We'll probably update the UI, so we need to use @UiThread. */ @UiThread public void onEvent(UserFetchedEvent e) { ... } @UiThread public void onEvent(ContactsFetchedEvent e) { ... } // Register the activity in the event bus when it starts @Override protected void onStart() { super.onStart(); BUS.register(this); } // Unregister it when it stops @Override protected void onStop() { super.onStop(); BUS.unregister(this); } } |
一行代码完成对用户数据的请求,同样也只需要一行代码来解析请求所返回的数据。对于通讯录等其他数据也可以用一样的方式来处理,听起来不错吧!
第五步——单例版的后台服务
正如我在上一步说的那样,这里使用的Service
并不是Android提供的Service类。其实,一开始的时候,我考虑使用Android提供的Services
,不过最后还是放弃了,原因还是为了简化。因为 Android提供的Services
通常情况下是为那些在没有Activity
展示情况下但还需要处理的操作提供服务的。另一种情况,你需要提供一些功能给其他的应用。这其实和我的需求并不完全相符,而且用单例来处理我的后台请求可以让我避免使用复杂的借口,譬如:ServiceConnection,Binder等等……
这一部分可以探讨的地方就多了。为了方便理解,我们从架构切入展示当Activity
调用getUser()
和getContacts()
的时候究竟发生了什么。
你可以把下图中每个serial
当作一个线程:
正如你所看到的,这是我非常喜欢的模式。大部分情况下用户不需要等待,程序的视图会立刻被缓存数据填充。然后,当抓取到了服务端的最新数据,视图数据会被新数据替代掉。与此对应的是,你需要确保你的Activity
可以接受多次同样类型的数据。在构建Activity
的时候记住这一点就没有任何问题啦。
下面是一些示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |