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

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

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

一些现有的解决方案

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

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

FollowersRequest request =newFollowersRequest(user);

lastRequestCacheKey = request.createCacheKey();

spiceManager.execute(request, lastRequestCacheKey,

DurationInMillis.ONE_MINUTE,

newRequestListener {

@Override

publicvoidonRequestFailure(SpiceException e) {

// On success

}

@Override

publicvoidonRequestSuccess(FollowerList listFollowers) {

// On failure

}

});

然后是请求的具体代码:

publicclassFollowersRequestextendsSpringAndroidSpiceRequest {

privateString user;

publicFollowersRequest(String user) {

super(FollowerList.class);

this.user = user;

}

@Override

publicFollowerList loadDataFromNetwork()throwsException {

String url = format("https://api.github.com/users/%s/followers", user);

returngetRestTemplate().getForObject(url, FollowerList.class);

}

publicString createCacheKey() {

return"followers."+ user;

}

}

存在的问题

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

- 对于你的每种请求你都需要继承SpiceRequest写一个特定的子类。

- 同样的,对于每种请求你都需要实现一个RequestListener来监听。

- 如果你的缓存过期时间很短,用户就需要花较长时间等待你的每个请求结束。

- RequestListener持有了Activity的隐式引用,那么是不是还需要内存泄露的问题。

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

五步,让程序简洁而健壮

在我开始开发Candyshop的时候,我尝试了其他的方法。我试图通过混合一些拥有有趣特性的库来构造一个简单而健壮的解决方案。这是我用到的库的列表:

* SnappyDB这个库主要用来将一些 Java 对象缓存到本地文件中。

* EventBus 通过 Event Bus 来解耦处理 App 内部组建间的通讯。

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

d797b6252795a630556f079bf967b657.png

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

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

@EBean

publicclassCache {

publicstaticenumCacheKey { USER, CONTACTS, ... }

public T get(CacheKey key, Class returnType) { ... }

publicvoidput(CacheKey key, Object value) { ... }

}

第二步 一个符合REST的Client

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

@Rest(rootUrl ="http://anything.com")

publicinterfaceCandyshopApi {

@Get("/api/contacts/")

ContactsWrapper fetchContacts();

@Get("/api/user/")

User fetchUser();

}

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

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

publicclassCandyshopApplicationextendsApplication {

publicfinalstaticEventBus BUS =newEventBus();

...

}

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

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

@EActivity(R.layout.activity_main)

publicclassMainActivityextendsActivity {

// Inject the service

@BeanprotectedAppService appService;

// Once everything is loaded…

@AfterViewspublicvoidafterViews() {

// … 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.

*/

@UiThreadpublicvoidonEvent(UserFetchedEvent e) {

...

}

@UiThreadpublicvoidonEvent(ContactsFetchedEvent e) {

...

}

// Register the activity in the event bus when it starts

@OverrideprotectedvoidonStart() {

super.onStart();

BUS.register(this);

}

// Unregister it when it stops

@OverrideprotectedvoidonStop() {

super.onStop();

BUS.unregister(this);

}

}

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

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

正如我在上一步说的那样,这里使用的Service并不是Android提供的Service类。其实,一开始的时候,我考虑使用Android提供的Services,不过***还是放弃了,原因还是为了简化。因为 Android提供的Services通常情况下是为那些在没有Activity展示情况下但还需要处理的操作提供服务的。另一种情况,你需要提供一些功能给其他的应用。这其实和我的需求并不完全相符,而且用单例来处理我的后台请求可以让我避免使用复杂的借口,譬如:ServiceConnection,Binder等等……

这一部分可以探讨的地方就多了。为了方便理解,我们从架构切入展示当Activity调用getUser()和getContacts()的时候究竟发生了什么。

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

f9c3e9fe1b069814525d9ba5fd174603.png

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

下面是一些示例代码:

// As I said, a simple class, with a singleton scope

@EBean(scope = EBean.Scope.Singleton)

publicclassAppService {

// (Explained later)

publicstaticfinalString NETWORK ="NETWORK";

publicstaticfinalString CACHE ="CACHE";

// Inject the cache (step 1)

@BeanprotectedCache cache;

// Inject the rest client (step 2)

@RestServiceprotectedCandyshopApi candyshopApi;

// This is what the activity calls, it's public

@Background(serial = CACHE)

publicvoidgetContacts() {

// Try to load the existing cache

ContactsFetchedEvent cachedResult =

cache.get(KEY_CONTACTS, ContactsFetchedEvent.class);

// If there's something in cache, send the event

if(cachedResult !=null) BUS.post(cachedResult);

// Then load from server, asynchronously

getContactsAsync();

}

@Background(serial = NETWORK)

privatevoidgetContactsAsync() {

// Fetch the contacts (network access)

ContactsWrapper contacts = candyshopApi.fetchContacts();

// Create the resulting event

ContactsFetchedEvent event = newContactsFetchedEvent(contacts);

// Store the event in cache (replace existing if any)

cache.put(KEY_CONTACTS, event);

// Post the event

BUS.post(event);

}

}

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

@Background(serial = CACHE)

publicvoidgetUser() {

postIfPresent(KEY_USER, UserFetchedEvent.class);

getUserAsync();

}

@Background(serial = NETWORK)

privatevoidgetUserAsync() {

cacheThenPost(KEY_USER, newUserFetchedEvent(candyshopApi.fetchUser()));

}

那么serial是用来做什么的? 让我们看看文档是怎么说的:

默认情况下,所有@Background的匿名方法都是并行执行的。但是如果两个方法使用了同样名字的serial则会顺序运行在同一个线程中,一个接着一个执行。

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

总结

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

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

原文链接: joanzap   翻译:zerob13

【编辑推荐】

【责任编辑:闫佳明 TEL:(010)68476606】

点赞 0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值