Android开发架构设计之健壮且可读的安卓架构(下篇)

来源:互联网 发布:淘宝怎样发布二手宝贝? 编辑:程序博客网 时间:2024/06/05 21:55

原文地址:http://blog.joanzapata.com/robust-and-readable-part-2-introducing-async-service/

              健壮且可读的安卓架构(二)


注意:阅读这篇文章之前请阅读第一部分
我收到很多关于我的架构设计的评论和反馈,特别感谢每周安卓社区(Android weekly),你们中的一切人注意到我的架构中的一些弱点,或者一些替代方案,还有的人问我要源码。也有许多人问我在结尾处所说的第二部分什么时候写。
在我写完第一部分的几周后,关于我如何开始开发CandyShopApp是一个模糊描述,我们在Redmill实验室中,从CandyShop转到Midpic。在这过程中,我得到了一个绝佳的机会去将这个架构去转为一个开源库:AsyncService

一、基础用法

这是一个基本的AsyncService的用法,通过用户名称去检索用户
@AsyncServicepublic class UserService {     public User getUser(String name) {      return ...;   }}
当你在activity里面注入这个UserService的时候,你可以在任何线程里面回调getUser()这个方法,它会立即返回null并开始异步执行,结果会以一个消息的形式返回,你可以通过构造一个带有合适的参数方法拿到这个结果。

public class MyActivity extends Activity {   @InjectService public UserService userService;   public void onCreate(Bundle savedInstanceState){      // Runs injections and detect @OnMessage callback methods      AsyncService.inject(this);      // Starts method asynchronously (never catch the result here, would be null)      userService.getUser("Joan");   }   // Callback method for getUser   @OnMessage void onUser(User e) {      // Runs on UI thread.   }}

注意:

AsyncService是在编译的时候生成代码,一个MyActivity类会在编译的时候创建,当你调用inject(this)的时候,系统会通过反射来实例化这个注入器,这点非常重要,因为在android上反射是非常慢的。
正如你所见,这个机制非常的像我在第一文章第一部分中所提到的eventBus,只是这次你不需要注册和注销事件总线,它会全部处理好。在Service里面,你也不用通过事件总线去传递User这个对象,它仅仅是一个返回值,所以代码会更加的简洁。
那么,现在回有人注意到使用事件总线会有一个巨大的缺点:
如果你有多个调用者在同一时间调用了getUser这个方法,onUser这个方法会在意想不到的时候回调很多次。而AsyncService可以解决这个问题,在AsyncService.inject注入的时候,它会绑定好回调者,这意味着你只会通过你自己注入的UserService实例来获得你回调的消息。

当然,如果你需要接受来自整个应用程序任何地方的消息,这是一个非常有用的管理消息通知机制,你可以在回调函数上加上OnMessage(from=ALL)来实现

调用缓存:

当我在写第一篇文章的时候,我所关心的就是不要让用户等待,所以我描述了如何直接展示一些东西给用户,如图所示:
快速提示:服务通过事件总线直接发送缓存数据,然后通过相关API调用并发送更新后的数据。通过使用一个特殊的线程(serial=CACHE)去处理新的请求,我确定缓存会直接发送,然后在通过一个特殊的线程(serial=NETWORK)去处理网络请求,这样我更容易处理get-after-post上的麻烦。
通过使用AndroidAnnomations@serial的注释、 EventBus和snappyDB,代码看起来是这样的
@Background(serial = CACHE)public void getUser() {     postIfPresent(KEY_USER, UserFetchedEvent.class);   getUserAsync();}@Background(serial = NETWORK)private void getUserAsync() {     cacheThenPost(KEY_USER, new UserFetchedEvent(candyshopApi.fetchUser()));}
这样,依旧需要很多个引用代码去写每个请求,AsyncService有这样一个注释:
@CacheThenCallpublic User getUser(){     return ...;}
这样很简洁,对吗?它依旧以相同的方式在后台工作。
如果getUser()方法带参数,比如getUser(String name),你可以使用一个特殊的键值来标记这个缓存,但是这个键值必须在整个应用程序中唯一。
@CacheThenCall(key="UserService.getUser({name})")public User getUser(String name){     return ...;}

一般来说,缓存键值的默认值形式为"<Class.name>.<MethodName>({arg1},{arg2},...)",所以在这个地方我们实际上并不需要去标记它。

错误管理

在上一篇文章中,我没有写到错误管理,在AsyncService中有这样的错误管理机制:
@AsyncService(errorMapper = StatusCodeMapper.class)@ErrorManagement({   @Mapping(on = 0,  send = NoNetworkError.class),   @Mapping(on = 500,  send = ServerInternalError.class),   @Mapping(on = 503,  send = ServerMaintenanceError.class),    ...})public class UserService {   @ErrorManagement({      @Mapping(on = 404, send = UserNotFoundError.class),      ...})   public User getUser(String username){      return ...;   }}

正如你所看到的,AsyncService定义了一个ErrorManager,这是一个将Throwable错误类转换成int型数据的接口,比如,通过exception提取HTTP请求的错误状态码,我们会马上看见它。
如果一个错误在getUser()中发生了,那么ErrorManager将会把这个错误转换成一个int数据,然后去和@Mappingannotations里的值匹配,如果匹配成功,那么匹配成功的类会被实例化并当做消息发送。
一个基础的ErrorMapper接口可以是这样:
public class StatusCodeMapper implements ErrorMapper {   @Override   public int mapError(Throwable throwable) {      if (throwable instanceof HttpStatusCodeException)           return ((HttpStatusCodeException) throwable).getStatusCode().value();      if (isConnectivityError(throwable))         return 0;      return SKIP;   }}
如果返回的是SKIP,那么意味着这个错误没有捕获,那么它会发送到UncaughtExceptionHandler(这个类是全局异常捕获类,之前写的文章里有说明),如果没听过这个类,这个类就是所有你未捕获的错误所去的地方。一些错误报告工具例如:ACRA\Crashlytic等,捕获错误并报告。
也许第一次写这些很反感,但你只需要这么写一次就好。之后就可以定义什么错误发生在每个方法中。在Midpic这个项目中,我的ErrorManager有一点点大,因为我们的服务端响应头里面有这些无意义代码{code:"1002",message:"blah blah blah"},所以我通过匹配1002条错误代码去读取错误。这样使得我的代码完全镜像服务器API。
关于ErrorManager的最后一点,如果在getUser(String name)上注释了错误标签,404映射到了UserNotFoundError,那么在activity中你可以这样捕获这个错误:
@OnMessage void onError(UserNotFoundError e){      Toast.makeText(this, "User does not exist.", LENGTH_LONG).show();}
当错误发生在getUser(String name)方法时,你可以更进一步的捕获到用户名。这样,你可以定义一个参数在错误信息中:
public class UserNotFoundError {     public UserNotFound(@ThrowerParam("username") String username){      this.username = username;   }   ...
那么现在你可以更加准确的展示信息:
@OnMessage void onError(UserNotFoundError e){      Toast.makeText(this, String.format("User %s does not exist.", e.getUsername()), LENGTH_LONG).show();}

结论

在这篇文章中,我介绍了一些AsyncService的优点,例如缓存和错误管理机制,如果想得到更完整的功能列表,请阅读github项目。正如我们所看到的,这是一个对AndroidAnnotations,eventBus,snappyDB组合的提升,这个框架已经用在Midpic项目上,目前为止还没有发现任何bug。
如今AsyncService已经实现了它的价值在Midpic项目上,但是我希望它可以帮助到更多的人并且我已经将它发布到社区上,我很乐意接受相关意见和反馈!

终于翻译完了。。



0 0
原创粉丝点击