美文网首页Android架构Android 数据库
从零开始的Android新项目6 - Repository层(下

从零开始的Android新项目6 - Repository层(下

作者: MarkZhai | 来源:发表于2016-07-06 17:43 被阅读701次

    承接上篇从零开始的Android新项目5 - Repository层(上) Retrofit、Repository组装,本文继续介绍Realm、缓存,以及统一的异常处理设计。

    Realm

    Realm在移动端数据库中也算是比较有名的一款了,以其跨平台和惊人的速度而闻名。啊,对了,还有文档多。

    这里要黑的就是文档问题,Realm虽然乍一看文档很多,但是老实说,写的挺乱的。不过总体来说,实践和应用中感觉还不错,性能好,也比较方便,比起不稳定的DBFlow和麻烦至极的GreenDao来好了太多了,唯一的美中不足就是so比较大,会增大包的体积1MB。

    引入

    从Realm 0.90开始,用法与之前有了改变:

    在root的build.gralde中:

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath "io.realm:realm-gradle-plugin:0.90.1"
        }
    }
    

    然后在对应需要应用到Realm的,比如data module的build.gradle:

    apply plugin: 'realm-android'
    

    即可使用Realm。

    使用

    使用起来也很方便,比如我们想要缓存用户的信息

    public class UserPo extends RealmObject {
        @PrimaryKey
        public String id;
        public String name;
        public String headerUrl;
        public long updateTime;
    }
    

    这样就对应了一个表,其主键为id,另外有3列name, headerUrl, 以及updateTime。

    如果想要查询,只需要:

    UserPo user = getRealm().where(UserPo.class)
            .equalTo("id", userId)
            .findFirst();
    

    如果要写入一条记录:

    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

    // 结合 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类,抛给上层。

    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继承它:

    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,管理了其错误。

    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创建对应的错误消息提示,让用户不至于碰到错误莫名其妙。

    /**
     * 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来处理这些通用错误:

    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,另一方,恰当的错误展示也让用户知道究竟出了什么错,不至于莫名其妙。

    下一次不知是何时相见,希望能为大家带来我们项目中使用React Native进行混合开发的苦与甜。

    另外,打个小广告,本司的新产品Crew已经在各大Android应用市场上线,专注于职场垂直社交。一搜和兴趣相投的人聊天。iOS版本正在审核中。

    2个字找到志趣相投的职场伙伴,秒搜陌生人同类,智能自动破冰。多关键字叠加,高效率锁定职场同僚。精准匹配兴趣对象,超轻聊天,更能一键组建群聊,加入一群人的狂欢。

    demo没空写了,反正我也没混淆,直接反编译来黑我吧。哈哈。有bug或者功能上的意见建议欢迎直接反馈给我。

    相关文章

      网友评论

      本文标题:从零开始的Android新项目6 - Repository层(下

      本文链接:https://www.haomeiwen.com/subject/hovkjttx.html