流行框架源码分析(18)-UnifyStorage统一的数据库存储,key-value存储,mock网络数据的一个库

主目录见:Android高级进阶知识(这是总目录索引)
项目目录:https://github.com/yuzhijun/UnifyStorage

这里先给大家道歉一下,最近因为要学习的方向实在是比较大,所以文章已经好久没有更新,如果有什么需要可以留言问我,有什么东西很想要了解的也可以交流,文笔生疏了,见谅。

一.目标

写这个库的开始是源于一个小的需求,当然,这个库也是小巧的。而且做这个库的初衷就是为了能将网络,数据库存储,本地key-value存储统一起来,这样管理和扩展都会更加方便,对于用户来说是统一的接口。

如果不知道怎么使用,这个库有详细的入门文档:https://www.kancloud.cn/sharkchao/unifystorage/864979#65_summinmaxaverage_324,看完文档你一定知道是怎么操作的,所以我这里就讲讲怎么实现的吧,其实也是简单的,主要是借助retrofit的方式,结合realm数据库和mmkv来做的。

二.源码分析

1.基本使用

首先还是跟其他源码的入手点一样,我们先来看看最基本的使用方法,依赖这方面文档里面已经很清楚了,我就直接讲使用部分:

public class ApiServiceModule {
    private volatile static ApiServiceModule mInstance;

    private ApiServiceModule(){

    }
    public static ApiServiceModule getInstance(){
        if (null == mInstance){
            synchronized (ApiServiceModule.class){
                if (null == mInstance){
                    mInstance = new ApiServiceModule();
                }
            }
        }
        return mInstance;
    }

    private UStorage provideUStorage(){
        return new UStorage.Builder()
                .setSchemaVersion(1)
                .build();
    }

    <T> T provideApiService(Class<T> apiDataBase){
        return provideUStorage().create(apiDataBase);
    }
}

很简单,用过retrofit的人都知道,需要先获取业务接口类,这里的provideUStorage().create(apiDataBase)方法就是获取接口类的方法,那我们先看下接口类的实现,这里接口类以一个查询为例,都列举出来太多了,不利于查看:

public interface ApiDataBase {
    @DB(table = User.class)
    @FIND(where = "name = ? and (age > ? or sex = ?)",limit = 10,orderBy = "age desc")
    DbResult<User> findUser(String name, int age, String sex);
}

可以看到这里的接口类跟retrofit极其相似,不同的是这里进行的是数据库的存储,然后我们看最终的调用使用代码:

   mApiDataBase.findUser("sharkchao", 20, "男")
                .registerDbFindCallBack(new DbResult.DbFindCallBack<User>() {
                    @Override
                    public void onFirstFindResult(RealmResults<User> realmResults) {
                        UserData.setmResults(realmResults);
                        Toast.makeText(MainActivity.this, "成功!"+ realmResults.size(), Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onChange(RealmResults<User> realmResults) {

                    }
                });

这就是查询的使用方法了,我们以这个入口点进行源码分析。

2.代码框架分析

我们看到使用的时候先要进行UStorage的建造,这里使用的是建造者模式,主要是为了构造数据库所需的必要参数,如代码所示:

new UStorage.Builder()
                .setSchemaVersion(1)
                .build();

我们直接跟进代码里面查看,首先查看UStorage类中的Builder

 public static final class Builder {
        private static final String DEFAULT_DB_NAME = "winningStorage.realm";

        private String dbName;
        private int schemaVersion = 0;
        private BaseMigration migration;
        private Realm realmDefault;

        public Builder() {
        }

        public Builder setDbName(String dbName) {
            this.dbName = dbName;
            return this;
        }

        public Builder setSchemaVersion(int schemaVersion) {
            this.schemaVersion = schemaVersion;
            return this;
        }

        public Builder setMigration(BaseMigration migration) {
            this.migration = migration;
            return this;
        }

        public UStorage build() {
            configDB();

            return new UStorage(this);
        }

        private void configDB(){
            RealmConfiguration.Builder otherConfigBuilder = new RealmConfiguration.Builder()
                    .name(CommonUtil.isEmptyStr(dbName) ? DEFAULT_DB_NAME : dbName)
                    .schemaVersion(schemaVersion);

            if (null == migration){
                otherConfigBuilder.deleteRealmIfMigrationNeeded();
            }else {
                otherConfigBuilder.migration(migration);
            }

            RealmConfiguration otherConfig = otherConfigBuilder.build();
            Realm.setDefaultConfiguration(otherConfig);

            realmDefault = Realm.getDefaultInstance();
        }
    }

上面的代码主要是对realm数据库的初始化,没有什么有难度的代码,接着我们看provideUStorage().create(apiDataBase)中的create()方法:

//这里的代码跟retrofit是一摸一样的
  public <T>  T create(final Class<T> service) {
        CommonUtil.validateServiceInterface(service);

        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
                new InvocationHandler() {
                    private final Object[] emptyArgs = new Object[0];

                    @Override
                    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
                        // If the method is a method from Object then defer to normal invocation.
                        if (method.getDeclaringClass() == Object.class) {
                            return method.invoke(this, args);
                        }

                        return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
                    }
                });
    }

    ServiceMethod<?> loadServiceMethod(Method method) {
        ServiceMethod<?> result = serviceMethodCache.get(method);
        if (result != null) return result;

        synchronized (serviceMethodCache) {
            result = serviceMethodCache.get(method);
            if (result == null) {
              //这里是主要的解析注解的代码
                result = ServiceMethod.parseAnnotations(this, method);
                if (result == null){
                    throw new IllegalArgumentException("annotation is not exits! please check your code");
                }
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }

我们看到上面的代码用的跟retrofit一样的代理模式,关于代理模式的文章可以查看流行框架源码分析(14)-Proxy代理设计模式,我们接着看关键的代码ServiceMethod.parseAnnotations(this, method),这个代码调用了ServiceMethod类的静态方法:

  @SuppressWarnings("unchecked")
    static <T> ServiceMethod<T> parseAnnotations(UStorage storage, Method method) {
        StorageFactory storageFactory = StorageFactory.parseAnnotations(storage, method);

        return storageFactory.getServiceMethod();
    }

我们看到这里代码又调用了StorageFactory.parseAnnotations(storage, method)方法,这个方法主要是根据注解@DB@JSON@GETJSON来创建不同的ServiceMethod对象,进行不同的策略选择,这也是策略模式的一个变种,我们跟进StorageFactory类中的方法:

    static StorageFactory parseAnnotations(UStorage storage, Method method) {
        return new Builder(storage, method).build();
    }

我们看到这个方法又调用了Builder类中的build()方法:

 static final class Builder {
        final UStorage storage;
        final Method method;
        final Annotation[] methodAnnotations;

        ServiceMethod<?> serviceMethod;


        Builder(UStorage storage, Method method) {
            this.storage = storage;
            this.method = method;
            this.methodAnnotations = method.getAnnotations();
        }

        StorageFactory build() {
            for (Annotation annotation : methodAnnotations) {
                parseMethodAnnotation(annotation);
            }

            return new StorageFactory(this);
        }

        private void parseMethodAnnotation(Annotation annotation){
            if (annotation instanceof DB){
                DB db = (DB) annotation;
                this.serviceMethod = DBServiceMethod.parseAnnotations(this.storage, this.method, db.table());
            }else if (annotation instanceof JSON){
                JSON json = (JSON) annotation;
                this.serviceMethod = JSONServiceMethod.parseAnnotations(this.storage, this.method, json.key(), json.convert());
            }else if (annotation instanceof GETJSON){
                GETJSON json = (GETJSON) annotation;
                this.serviceMethod = JSONServiceMethod.parseAnnotations(this.storage, this.method, json.key(), json.convert());
            }
        }

我们看到这里parseMethodAnnotation方法里面就是判断是哪个注解,然后分别初始化serviceMethod这个全局变量,最终会返回回去这个变量给外部的create方法中存进map中。因为获取到了ServiceMethod对象了,我们看create方法后面干了啥loadServiceMethod(method).invoke(args != null ? args : emptyArgs);,这里直接就调用了方法invoke方法,这个方法就是具体的每个ServiceMethod实现类中的方法,我们还是以查询为例:

 private void parseMethodAnnotation(Annotation annotation){
            if (annotation instanceof DB){
                DB db = (DB) annotation;
                this.serviceMethod = DBServiceMethod.parseAnnotations(this.storage, this.method, db.table());
            }else if (annotation instanceof JSON){
                JSON json = (JSON) annotation;
                this.serviceMethod = JSONServiceMethod.parseAnnotations(this.storage, this.method, json.key(), json.convert());
            }else if (annotation instanceof GETJSON){
                GETJSON json = (GETJSON) annotation;
                this.serviceMethod = JSONServiceMethod.parseAnnotations(this.storage, this.method, json.key(), json.convert());
            }
        }

从上面的代码入手,我们走DB注解这条路径,调用DBServiceMethod.parseAnnotations(this.storage, this.method, db.table());方法:

static <ReturnT> DBServiceMethod<ReturnT> parseAnnotations(
            UStorage storage, Method method, Class<? extends RealmObject> table) {

        return new DBServiceMethod<>(method, table);
    }

这里就是一句简单的代码,初始化了DBServiceMethod对象,我们跟进构造函数看看:

    private DBServiceMethod(Method method, Class<? extends RealmObject> table){
        this.parameterTypes = method.getGenericParameterTypes();
        this.parameterAnnotationsArray = method.getParameterAnnotations();
        this.table = table;

        if (null != method){
            for (Annotation annotation : method.getAnnotations()){
                parseHandler(annotation, method.getAnnotations());
            }
        }
    }

我们看到前几行都是赋值操作,将方法里面的参数类型,方法的参数注解,table注解里面的表名这些基本信息赋值,然后遍历方法上面的注解,调用parseHandler方法:

  private void parseHandler(Annotation annotation, Annotation[] annotations) {
        if (annotation instanceof FIND){
            this.storageHandler = FindHandler.parseAnnotations(annotations, this.table);
        }else if(annotation instanceof SAVE){
            this.storageHandler = SaveHandler.parseAnnotations(annotations, this.table);
        }else if(annotation instanceof SAVEORUPDATE){
            this.storageHandler = SaveOrUpdateHandler.parseAnnotations(annotations, this.table);
        }else if(annotation instanceof UPDATE){
            this.storageHandler = UpdateHandler.parseAnnotations(annotations, this.table);
        }else if(annotation instanceof DELETE){
            this.storageHandler = DeleteHandler.parseAnnotations(annotations, this.table);
        }
    }

这个代码和前面的代码有点类似,这个地方也是分别判断注解是哪个注解,然后进行什么样的操作,这里因为我们是取查询为例,所以我们看第一个FindHandler.parseAnnotations(annotations, this.table);方法:

  private FindHandler(Annotation[] annotations, Class<? extends RealmObject> table){
        this.table = table;
        buildField(annotations);
    }

    private void buildField(Annotation[] annotations) {
        if (null != annotations){
            for (Annotation annotation : annotations){
                if (annotation instanceof FIND){
                    FIND find = (FIND) annotation;
                    this.orderBy = find.orderBy();
                    this.where = find.where();
                    this.distinct = find.distinct();
                    this.limit = find.limit();
                    this.eager = find.eager();
                }
            }
        }
    }

    public static HandlerAdapter parseAnnotations(Annotation[] annotations,final Class<? extends RealmObject> table){
        return new FindHandler(annotations, table);
    }

因为代码较为简单,所以我都贴出来,这里就是取到注解里面的东西,进行赋值,最重要的方法还是在FindHandler类中的invoke方法,因为这个就是create方法里面调用的invoke方法:

  @SuppressWarnings("unchecked")
    @Override
    public DbResult invoke(Object[] args, Type[] parameterTypes, Annotation[][] parameterAnnotationsArray) {
        dbResult = new DbResult();
        try{
            RealmQuery<? extends RealmObject> query = UStorage.realm.where(this.table);
            RealmQuery<? extends RealmObject> whereFilteredQuery = FindConditionUtil.whereFilter(where, query, args , parameterTypes);

            RealmQuery<? extends RealmObject> otherFilteredQuery = FindConditionUtil.otherFilter(whereFilteredQuery, orderBy, limit, distinct);

            RealmResults result = otherFilteredQuery.findAllAsync();//这个地方所有的查询操作都是用异步的方式
            result.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults>() {
                @Override
                public void onChange(RealmResults realmResults, OrderedCollectionChangeSet changeSet) {
                    dbResult.setDbFindCallBack(realmResults, changeSet);
                }
            });
        }catch (Exception e){
            e.printStackTrace();
        }

        return dbResult;
    }

这里面就是最重要的查询操作了,首先我们回顾下我们查询的业务接口实现:

  @DB(table = User.class)
    @FIND(where = "name = ? and (age > ? or sex = ?)",limit = 10,orderBy = "age desc")
    DbResult<User> findUser(String name, int age, String sex);

查询方法里面主要就是解析出来where条件,主要的解析方法是应用了正则表达式,方法实现主要在whereFilter方法,这个方法会再调用setFilter方法,具体如下:

 public static RealmQuery<? extends RealmObject> setFilter(String set, String where, RealmQuery<? extends RealmObject> query, Object[] args, Type[] parameterTypes){
        linkCondition.clear();
        if (!CommonUtil.isEmptyStr(where)){//判断where条件是否为空,如果不为空才需要添加条件查询
            Pattern linkPattern = Pattern.compile(AND_OR);
            Matcher linkMatcher = linkPattern.matcher(where);

            while (linkMatcher.find()){//查找出来看有多少个and或者or,存储到ArrayList中
                linkCondition.add(linkMatcher.group());
            }

            int whereLength;
            //说明有复合条件查询
            if (linkCondition.size() > 0){
                String[] whereArray = where.split(AND_OR);//将And或者or两边的条件分割出来
                whereLength = whereArray.length;
                if (CommonUtil.isEmptyStr(set)){
                    if (args.length != whereArray.length || parameterTypes.length != whereArray.length){
                        throw new IllegalArgumentException("parameter size is not equal to ?");
                    }
                }

                for (int i = 0;i < whereArray.length;i ++){//对每一个语句进行构建查询
                    String whereCondition = whereArray[i];
                    Object parameter = args[i];
                    Type parameterType = parameterTypes[i];
                    //构造查询条件
                    buildWhereCondition(query, whereCondition, parameter, parameterType);

                    if (linkCondition.size() - 1 >= i){
                        String condition = linkCondition.get(i);
                        if ("and".equalsIgnoreCase(condition)){
                            query.and();
                        }else {
                            query.or();
                        }
                    }

                    if (!CommonUtil.isEmptyStr(whereCondition) && whereCondition.contains(")")){
                        query.endGroup();
                    }
                }
            }else {//说明是单一条件
                buildWhereCondition(query, where, args.length == 0 ? null : args[0],parameterTypes.length == 0 ? null : parameterTypes[0]);
                whereLength = 1;
            }

            buildSetFilter(set, args,whereLength,parameterTypes);
        }

        return query;
    }

这个方法比较长,实现也比较负责,我这里简要说下思路,这里首先用AND_OR这个正则表达式把where条件拆分开来,这样多个and和or中间的部分拆出来了,然后把这个中间部分分别用正则表达式去匹配,主要在buildWhereCondition(query, whereCondition, parameter, parameterType);方法里面,当然有时会有()这个操作为了区别优先级的操作,我们这里会判断遇到(或者)的时候进行添加上query.beginGroup();或者query.endGroup();用来包装成是一个模块的条件。

到这里数据库查询的功能代码讲解已经说完,代码算是比较简单,以后我们改变数据库,或者key-value的实现,只要在各种的Handler中实现就可以,这个库实现简单,但是思路还是非常好的,易于扩展,降低了耦合。当然代码还有mock网络的方式,这里就不分析了,因为涉及到要讲解retrofit的源码,这里推荐讲解retrofit源码的地方:流行框架源码分析(9)-Retrofit2源码解析看完这篇应该就可以看懂工程UnifyStorage里面的unifystorage_mock子库的代码了,当然不会的可以留言问我,或者加我都可以。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容