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),你可以使用一个特殊的键值来标记这个缓存,但是这个键值必须在整个应用程序中唯一。
一般来说,缓存键值的默认值形式为"<Class.name>.<MethodName>({arg1},{arg2},...)",所以在这个地方我们实际上并不需要去标记它。
@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 ...; }}
如果一个错误在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
- Android开发架构设计之健壮且可读的安卓架构(下篇)
- Android开发架构设计之健壮且可读的安卓架构(上篇)
- 健壮且可读的安卓架构设计
- 健壮且可读的安卓架构设计
- 健壮且可读的安卓架构设计
- 健壮且可读的安卓架构设计
- 健壮且可读的安卓架构设计
- 健壮且可读的安卓架构设计
- 健壮且可读的安卓架构设计
- 健壮且可读的安卓架构设计
- 健壮且可读的安卓架构设计
- 三层架构之下篇
- 安卓设计架构
- 一个健壮且可扩展的 CSS 架构所需的8个简单规则
- 【安卓】android-架构
- 安卓开发 第二篇 我的安卓应用架构设计
- 安卓开发 第三篇 我的安卓应用架构设计-----Responce类
- 安卓开发 第四篇 我的安卓应用架构设计-----Presenter类
- jQuery和CSS3商品图片预览轮播图插件
- P51 第4题 编写一个程序,判断用户输入的字符是否是数字,若是数字,则输出“a numerical character”,否则输出“other character”.
- CentOS 6.6下安装SQLite
- sea.js+grunt学习笔记
- Josephus问题
- Android开发架构设计之健壮且可读的安卓架构(下篇)
- 【CF】529B Group Photo 2 (online mirror version)
- 11种超酷CSS3复选框样式美化效果
- test命令
- Asterisk 13.2.0 current.tar.gz安装步骤详解
- Java堆栈介绍
- MySQL drop table操作风险
- 《python核心编程》序列类型小结2
- iOS 反射获取 类属性列表