流行框架源码分析(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 provideApiService(Class 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 findUser(String name, int age, String sex); }

可以看到这里的接口类跟retrofit极其相似,不同的是这里进行的是数据库的存储,然后我们看最终的调用使用代码:
mApiDataBase.findUser("sharkchao", 20, "男") .registerDbFindCallBack(new DbResult.DbFindCallBack() { @Override public void onFirstFindResult(RealmResults realmResults) { UserData.setmResults(realmResults); Toast.makeText(MainActivity.this, "成功!"+ realmResults.size(), Toast.LENGTH_SHORT).show(); }@Override public void onChange(RealmResults 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 create(final Class 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; }

【流行框架源码分析(18)-UnifyStorage统一的数据库存储,key-value存储,mock网络数据的一个库】我们看到上面的代码用的跟retrofit一样的代理模式,关于代理模式的文章可以查看流行框架源码分析(14)-Proxy代理设计模式,我们接着看关键的代码ServiceMethod.parseAnnotations(this, method),这个代码调用了ServiceMethod类的静态方法:
@SuppressWarnings("unchecked") static ServiceMethod 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 DBServiceMethod parseAnnotations( UStorage storage, Method method, Class table) {return new DBServiceMethod<>(method, table); }

这里就是一句简单的代码,初始化了DBServiceMethod对象,我们跟进构造函数看看:
private DBServiceMethod(Method method, Class 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 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 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 query = UStorage.realm.where(this.table); RealmQuery whereFilteredQuery = FindConditionUtil.whereFilter(where, query, args , parameterTypes); RealmQuery otherFilteredQuery = FindConditionUtil.otherFilter(whereFilteredQuery, orderBy, limit, distinct); RealmResults result = otherFilteredQuery.findAllAsync(); //这个地方所有的查询操作都是用异步的方式 result.addChangeListener(new OrderedRealmCollectionChangeListener() { @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 findUser(String name, int age, String sex);

查询方法里面主要就是解析出来where条件,主要的解析方法是应用了正则表达式,方法实现主要在whereFilter方法,这个方法会再调用setFilter方法,具体如下:
public static RealmQuery setFilter(String set, String where, RealmQuery 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子库的代码了,当然不会的可以留言问我,或者加我都可以。

    推荐阅读