流行框架源碼分析(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子庫的代碼了,當然不會的可以留言問我,或者加我都可以。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容