自从接触Android以来,我一直在寻找一种更健壮的开发方式。比如避免UI线程的IO操作,防止重复的网络请求,缓存重要数据并准确更新这些缓存等等。当然,代码结构也要尽量保持清晰。这篇文章并不是要给你提供一个权威的、精确的解决方案,更多的是探讨一种在灵活性、可读性和健壮性之间取得良好平衡的App开发方法。现有的一些解决方案在Android的早期版本中,很多人在多任务处理的时候都会选择AsyncTask。总的来说AsyncTask非常难用,很多文章也提到了它的问题。后来,Honeycomb(3.0)引入了更多可配置的Loader。2012年,基于AndroidService的开源项目Robospice问世,带来了全新的解决方案。下面是Robospice的工作原理。Robospice确实比AsyncTask好很多,但是还是有一些问题。例如下面的普通代码通过Robospice在Activity中发起一个请求流程。不需要仔细阅读,只要你有一个大概的概念即可:@OverrideFailurepublicvoidonRequest(SpiceExceptione){//成功时}@OverridepublicvoidonRequestSuccess(FollowerListlistFollowers){//失败时}});然后是请求的具体代码:publicclassFollowersRequesttextendsSpringAndroidSpiceRequest{privateStringuser;publicFollowersRequest(Stringuser){super(FollowerList.class);this=user;}@OverridepublicFollowerListloadDataFromNetwork()throwsException{Stringurl=format("https://api.github.com/users/%s/followers",用户);returngetRestTemplate().getForObject(url,FollowerList.class);}publicStringcreateCacheKey(){return"followers."+user;}}的问题每一个请求都需要做上面的处理,代码会显得臃肿:-对于你的每一个请求,你都需要继承SpiceRequest并编写一个特定的.-同样,对于每个请求你都需要实现一个RequestListener来监听。-如果你的缓存过期时间很短,用户需要花很长时间等待你的每一个请求结束。-RequestListener持有对Activity的隐式引用,那么是否还需要内存泄漏?总之,这不是一个很好的解决方案。使程序简单健壮的五个步骤当我开始开发Candyshop时,我尝试了其他方法。我试图通过混合一些具有有趣功能的库来构建一个简单而强大的解决方案。以下是我使用的库列表:*用于后台任务的AndroidAnnotations、EBean等...*用于REST(包括状态转移)网络请求的SpringRestTemplate,该库与AndroidAnnotations配合得很好。*SnappyDB库主要用于将一些Java对象缓存到本地文件中。*EventBus使用EventBus来解耦App内部组件之间的通信。下图是我将详细解释的整体架构:Step1一个易于使用的缓存系统你肯定会需要一个持久化的缓存系统,让这个系统尽可能简单。@EBeanpublicclassCache{publicstaticenumCacheKey{USER,CONTACTS,...}publicTget(CacheKeykey,ClassreturnType){...}publicvoidput(CacheKeykey,Objectvalue){...}}第二步是一个RESTClient这里我用下面的例子来说明。请记住确保在同一个地方使用RESTAPI。@Rest(rootUrl="http://anything.com")publicinterfaceCandyshopApi{@Get("/api/contacts/")ContactsWrapperfetchContacts();@Get("/api/user/")UserfetchUser();}第三应用级事件总线(EventBus)在程序开始时初始化Eventbus对象,然后应用程序可以全局访问这个对象。在Android中,应用程序初始化是一个很好的时机。publicclassCandyshopApplicationextendsApplication{publicfinalstaticEventBusBUS=newEventBus();...}Step4处理那些需要数据的活动对于这类活动,我的处理方式和Robospice很相似,也是基于Service。不同的是,我的Service不是Android提供的Service,而是一个普通的单例对象。可以从应用程序的所有部分访问此对象。具体代码我们会在第五步讲解。这一步,我们来看一下Activity代码的结构。因为,你在这一步看到的,是我们简化最有效的部分!@EActivity(R.layout.activity_main)publicclassMainActivityextendsActivity{//Injecttheservice@BeanprotectedAppServiceappService;//Onceeverythingisloaded…@AfterViewspublicvoidafterViews(){//…requesttheuserandhiscontacts(立即返回)appService.getUser();appService.getContacts();}/*TheresultofthepreviouscallswillcomeaseeventstroughtheEventBus.我们可能会更新UI,所以需要使用@UiThread。register(this);}//Unregisteritwhenitstops@OverrideprotectedvoidonStop(){super.onStop();BUS.unregister(this);}}一行代码完成用户数据请求,只需要一行代码解析数据由请求返回。通讯录等其他数据也可以用同样的方法处理,听起来不错!第五步——后台服务的单例版本上一步已经说过,这里使用的Service不是Android提供的Service类。其实一开始我也考虑过使用Android提供的Services,最后还是因为简单而放弃了。因为Android提供的Services通常是为那些需要处理而无需Activity显示的操作提供服务。在另一种情况下,您需要为其他应用程序提供一些功能。这其实并不完全符合我的需求,而使用单例来处理我的后台请求可以让我避免使用复杂的借口,比如:ServiceConnection、Binder等……这部分有很多地方可以讨论。为了便于理解,我们从架构开始展示当Activity调用getUser()和getContacts()时会发生什么。您可以将下图中的每个系列视为一个线程:如您所见,这是我非常喜欢的模式。在大多数情况下,用户不需要等待,程序的视图会立即被缓存的数据填充。然后,当从服务器获取最新数据时,视图数据将被新数据替换。相应的,你需要保证你的Activity可以多次接受同一类型的数据。在构建Activity时牢记这一点并没有错。这是一些示例代码://AsIsaid,asimpleclass,withasingletonscope@EBean(scope=EBean.Scope.Singleton)publicclassAppService{//(稍后解释)publicstaticfinalStringNETWORK="NETWORK";publicstaticfinalStringCACHE="CACHE";//Injectthecache(step1)@BeanprotectedCachecache;//Injecttherestclient(step2)@RestServiceprotectedCandyshopApicandyshopApi;//这就是activity调用的,它是public@Background(serial=CACHE)publicvoidgetContacts(){//尝试加载已有的cacheContactsFetchedEventcachedResult=cache.get(KEY_CONTACTS,ContactsFetchedEvent.class');some//Ifintherecachesendtheeventif(cachedResult!=null)BUS.post(cachedResult);//然后从服务器加载,异步getContactsAsync();}@Background(serial=NETWORK)privatevoidgetContactsAsync(){//Fetchthecontacts(networkaccess)ContactsWrappercontacts=candyshopApis(fetch)CreatetheresultingeventContactsFetchedEventevent=newContactsFetchedEvent(contacts);//Storetheeventincache(replaceexistingifany)cache.put(KEY_CONTACTS,event);//PosttheeventBUS.post(event);}}貌似每次request里面的code还是不少啊!事实上,这是我为了更好的解释而展开的。不难发现,这些请求遵循类似的模式,所以你可以很容易地构造一个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中,我同时使用了四个不同的连续剧。总结这里描述的解决方案是我几个月前想到的一个非常初级的想法。今天遇到的特例都解决了,很享受在这个架构下开发。当然,这个解决方案中还有一些很棒的地方想分享给大家,比如:错误处理、缓存超时机制、POST请求、忽略无用操作等,这里限于篇幅不再赘述原因。那么,你是否也找到了一个能让你每天都享受工作的框架呢?原文链接:joanzap翻译:zerob13翻译链接:http://blog.jobbole.com/66606/