Realm
Realm在移动端数据库中也算是比较有名的一款了,以其跨平台和惊人的速度而闻名。啊,对了,还有文档多。
这里要黑的就是文档问题,Realm虽然乍一看文档很多,但是老实说,写的挺乱的。不过总体来说,实践和应用中感觉还不错,性能好,也比较方便,比起不稳定的DBFlow和麻烦至极的GreenDao来好了太多了,唯一的美中不足就是so比较大,会增大包的体积1MB。
引入
从Realm 0.90开始,用法与之前有了改变:
在root的build.gralde中:
1 2 3 4 5 6 7 8 | buildscript { repositories { jcenter() } dependencies { classpath "io.realm:realm-gradle-plugin:0.90.1" } } |
然后在对应需要应用到Realm的,比如data module的build.gradle:
1 | apply plugin: 'realm-android' |
即可使用Realm。
使用
使用起来也很方便,比如我们想要缓存用户的信息
1 2 3 4 5 6 7 | public class UserPo extends RealmObject { @PrimaryKey public String id; public String name; public String headerUrl; public long updateTime; } |
这样就对应了一个表,其主键为id,另外有3列name, headerUrl, 以及updateTime。
如果想要查询,只需要:
1 2 3 | UserPo user = getRealm().where(UserPo.class) .equalTo("id", userId) .findFirst(); |
如果要写入一条记录:
1 2 3 4 5 6 7 8 9 | User user = new UserPo(); user.setName(userInfoEntity.getNickName()); user.setId(userInfoEntity.getUserId()); user.setHeaderUrl(userInfoEntity.getHeaderImageUrl()); user.setUpdateTime(System.currentTimeMillis()); getRealm().beginTransaction(); getRealm().copyToRealmOrUpdate(user); getRealm().commitTransaction(); |
就是这么简单。
如果想要直接和Retrofit一起应用,去进行串行化,可以参考该Gist。
1 2 3 4 5 6 7 8 9 10 | // 结合 Realm, Retrofit 和 RxJava (使用了Retrolambda以简化符号)的例子。 // 读取所有Person,然后与从GitHub获取的最新状态merge到一起 Realm realm = Realm.getDefaultInstance(); GitHubService api = retrofit.create(GitHubService.class); realm.where(Person.class).isNotNull("username").findAllAsync().asObservable() .filter(persons.isLoaded) .flatMap(persons -> Observable.from(persons)) .flatMap(person -> api.user(person.getGithubUserName()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(user -> showUser(user)); |
更多详情可以去官网看,migration/relationship等等支持应有尽有,我只能说,文档实在太长太长了。
内存
内存,也就是直接使用变量存储在对应repository中,如果非空则优先直接返回内存中的变量。
LruCache
LruCache限定了最大的entry数量,近期最少使用算法保证了淘汰机制的合理性。使用场景如用户信息缓存,会淘汰那些最近没有访问过的用户的信息缓存。使用可参考Google官网:LruCache。
变量存储
变量存储很简单,直接在Repository实现类中直接变量存储上一次的返回结果,在下一次请求的时候优先使用内存缓存。使用场景如请求后直接刷新本地的变量,下次调用repository方法使用啊concat先返回内存里的变量,然后再使用网络数据进行刷新。
统一异常处理
作为Repository层,本身不会,也不应该去处理任何异常和错误(比如请求的错误码),一切都将作为Exception异常抛给上层去做统一处理,而RxJava的onError机制也帮助我们能优雅地去做这件事。
Observable.error
类似在上一篇中提到的方法,我们可以使用Observable.error返回异常,供上层根据该异常做对应处理。无论是网络异常,数据库异常,亦或是服务器response异常等等,都可以进行分类创建对应的Exception类,抛给上层。
1 2 3 4 5 6 7 8 9 10 11 12 | public static <T> Observable<T> extractData(Observable<MrResponse> observable, Class<T> clazz) { return observable.flatMap(response -> { if (response == null) { return Observable.error(new NetworkConnectionException()); } else if (response.getStatusCode() == ResponseException.STATUS_CODE_SUCCESS) { return Observable.just(mGson.fromJson(mGson.toJson(response.data), clazz)); } else { Logger.e(TAG, response.data); return Observable.error(new ResponseException(response)); } }); } |
Subscriber.onError
我们使用Subscriber的基类来处理通用错误,其他所有Subscriber继承它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class MrSubscriber<T> extends DefaultSubscriber<T> { @Override public void onError(Throwable e) { super.onError(e); if (!handleCommonResponseError((Exception) e)) { if (e != null && e.getMessage() != null) { Logger.w(TAG, e.getMessage()); } showErrorMessage(new DefaultErrorBundle((Exception) e)); } } } protected void showErrorMessage(ErrorBundle errorBundle) { String errorMessage = ErrorMessageFactory.create(this, errorBundle.getException()); showErrorMessage(errorMessage); } protected void showErrorMessage(String errorMessage) { ToastUtils.show(this, errorMessage); } |
DefaultErrorBundle
是exception的wrapper,管理了其错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class DefaultErrorBundle implements ErrorBundle { private static final String DEFAULT_ERROR_MSG = "Unknown error"; private final Exception exception; public DefaultErrorBundle(Exception exception) { this.exception = exception; } @Override public Exception getException() { return exception; } @Override public String getErrorMessage() { return (exception != null) ? this.exception.getMessage() : DEFAULT_ERROR_MSG; } } |
ErrorMessageFactory
是错误消息工厂,根据exception创建对应的错误消息提示,让用户不至于碰到错误莫名其妙。
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 | /** * Factory used to create error messages from an Exception as a condition. */ public class ErrorMessageFactory { private static final String TAG = "ErrorMessageFactory"; private ErrorMessageFactory() { //empty } /** * Creates a String representing an error message. * * @param context Context needed to retrieve string resources. * @param exception An exception used as a condition to retrieve the correct error message. * @return {@link String} an error message. */ public static String create(Context context, Exception exception) { if (StringUtils.isNotEmpty(exception.getMessage())) { Logger.e(TAG, exception.getMessage()); } String message = context.getString(R.string.exception_message_generic); if (exception instanceof NetworkConnectionException) { message = context.getString(R.string.exception_message_no_connection); } else if (exception instanceof NotFoundException) { message = context.getString(R.string.exception_message_not_found); } else if (exception instanceof ResponseException) { message = exception.getMessage(); } else if (exception instanceof HttpException) { message = exception.getMessage(); } return message; } } |
handleCommonResponseError
通常,服务器会返回错误信息,我们需要根据一些code进行对应处理,MrSubscriber的onError就调用了handleCommonResponseError
来处理这些通用错误:
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 | protected boolean handleCommonResponseError(Exception exception) { boolean handled = false; if (exception instanceof ResponseException) { ResponseException responseException = (ResponseException) exception; switch (responseException.getStatusCode()) { case ResponseException.ERROR_CODE_NEED_LOGIN: handled = true; getUserSystem().setVuser(""); getNavigator().navigateToLoginPage(this); break; case ResponseException.ERROR_CODE_NEED_PERFECT_PROFILE: handled = true; if (responseException.getVuser() != null) { getUserSystem().setVuser(responseException.getVuser().getVuser()); } getNavigator().navigateToPerfectProfile(this); break; case ResponseException.ERROR_CODE_NEED_THIRD_PARTY_BIND: handled = true; getNavigator().navigateToThirdPartyBind(this); break; } } return handled; } |
Log & 上报
出错了当然要上报啦,bugly、友盟,本地写文件打zip包上传,Logger做的就是写文件log了,这些常见的app都会去做,这里就不赘述了。
总结和下集预告
本系列两篇文章描述了Android项目中,Repository层的设计与实现,也可以理解它为data或者model层。一个好的Repository层和上层相对独立,内聚完成业务逻辑的数据部分,即便内部有修改,比如添加了缓存,对外仍然保持一致。而好的异常处理设计一方面让代码中不会充斥着杂七杂八的 try & catch,另一方,恰当的错误展示也让用户知道究竟出了什么错,不至于莫名其妙。