RxCache源码分析

RxCache

是使用注解为Retrofit加入二级缓存(内存,磁盘)的缓存库。 开头膜拜大神
项目地址 : RxCache

RxCache使用方法

定义接口

public interface CacheProviders {

    @LifeCache(duration = 2, timeUnit = TimeUnit.MINUTES)
    Observable<Reply<List<Repo>>> getRepos(Observable<List<Repo>> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);

    @LifeCache(duration = 2, timeUnit = TimeUnit.MINUTES)
    Observable<Reply<List<User>>> getUsers(Observable<List<User>> oUsers, DynamicKey idLastUserQueried, EvictProvider evictProvider);

    Observable<Reply<User>> getCurrentUser(Observable<User> oUser, EvictProvider evictProvider);
}

十分简洁,使用了注解@LifeCache声明了缓存的超时时间(duration长度,timeUnit时间单位),接口定义了之后,如何实例化这个接口,看下面。

创建CacheProviders对象

providers = new RxCache.Builder()
                .persistence(cacheDir, new GsonSpeaker())
                .using(Providers.class);

之后的使用和Retrofit无异

实例化的过程是比较常见的Builder模式,和Retrofit的API的实例化的方式很像,调用using()就创建了接口的实例,和Retrofitcreate()方法也十分相似,当然内部实现也很相似(都是使用了动态代理)。

RxCache源码分析

using() 方法创建CacheProviders接口实例的过程

public <T> T using(final Class<T> classProviders) {
    proxyProviders = new ProxyProviders(builder, classProviders);

    return (T) Proxy.newProxyInstance(
        classProviders.getClassLoader(),
        new Class<?>[] {classProviders},
        proxyProviders);
  }

创建接口的实例使用的是动态代理技术

简而言之,就是动态生成接口的实现类(当然生成实现类有缓存机制),并创建其实例(称之为代理),代理把对接口的调用转发给 InvocationHandler实例,而在 InvocationHandler的实现中,除了执行真正的逻辑(例如再次转发给真正的实现类对象),我们还可以进行一些有用的操作,例如统计执行时间、进行初始化和清理、对接口调用进行检查等。

为什么要用动态代理? 因为对接口的所有方法的调用都会集中转发到 InvocationHandler#invoke函数中,我们可以集中进行处理,更方便了。你可能会想,我也可以手写这样的代理类,把所有接口的调用都转发到InvocationHandler#invoke呀,当然可以,但是可靠地自动生成岂不更方便?

RxCache 1.5.2版本中,上述ProxyProviders对象的创建方法不是简单的new,而是使用了依赖注入框架dagger,dagger在后面的还有使用,这里只是提一下。

在这里ProxyProviders实现了InvocationHandler,所以代理把对接口的调用转发给了ProxyProviders#invoke,所以我们就看下invoke()方法。

@Override public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    return Observable.defer(new Func0<Observable<Object>>() {
      @Override public Observable<Object> call() {
        return processorProviders.process(proxyTranslator.processMethod(method, args));
      }
    });

method包含了调用方法的基本信息(注解,返回值类型等),args是方法的参数列表。

 ConfigProvider processMethod(Method method, Object[] objectsMethod) {
    ConfigProvider prev = loadConfigProviderMethod(method);

    ConfigProvider configProvider = new ConfigProvider(prev.getProviderKey(),
        null, prev.getLifeTimeMillis(), prev.requiredDetailedResponse(), prev.isExpirable(),
        prev.isEncrypted(), getDynamicKey(method, objectsMethod),
        getDynamicKeyGroup(method, objectsMethod),
        getLoaderObservable(method, objectsMethod),
        evictProvider(method, objectsMethod));

    return configProvider;
  }

ConfigProvider是一个包装类,里面包装了该方法本身的属性(名称,相关注解,超时时间之类的)以及方法的调用参数,。

ProxyTranslator#processMethod这个方法先通过ProxyTranslator#loadConfigProviderMethodload出了一个ConfigProvider对象,这个load方法是先从map中读缓存,没有缓存再新创建实例(下面的configProviderMethodCache是一个HashMap),这里也和Retrofit中的请求方法的缓存方式一样。

private ConfigProvider loadConfigProviderMethod(Method method) {
    ConfigProvider result;
    synchronized (configProviderMethodCache) {
      result = configProviderMethodCache.get(method);
      if (result == null) {
        result = new ConfigProvider(getProviderKey(method),
            null, getLifeTimeCache(method),
            requiredDetailResponse(method), getExpirable(method), isEncrypted(method),
            null, null, null, null);
        configProviderMethodCache.put(method, result);
      }
    }
    return result;
  }

ProxyTranslator中提供了很多的get()方法,这些get方法可以分为两种,一种是只需要传入一个参数method,另一种需要传入两个参数methodobjectsMethod。这里以ProxyTranslator#getLifeTimeCache(一个参数),ProxyTranslator#getDynamicKey(两个参数)为例。

private Long getLifeTimeCache(Method method) {
    LifeCache lifeCache = method.getAnnotation(LifeCache.class);
    if (lifeCache == null) return null;
    return lifeCache.timeUnit().toMillis(lifeCache.duration());
  }

一个参数的很简单,将注解中的超时时间读取出来并换算为毫秒,其他的get()方法也相同,都是为了从注解中取出信息

private String getDynamicKey(Method method, Object[] objectsMethod) {
    DynamicKey dynamicKey = getObjectFromMethodParam(method, DynamicKey.class, objectsMethod);
    if (dynamicKey != null) return dynamicKey.getDynamicKey().toString();

    DynamicKeyGroup dynamicKeyGroup =
        getObjectFromMethodParam(method, DynamicKeyGroup.class, objectsMethod);
    if (dynamicKeyGroup != null) return dynamicKeyGroup.getDynamicKey().toString();

    return "";
  }

两个参数的也不复杂,比较重要的是ProxyTranslator#getObjectFromMethodParam,这个方法传入三个参数method,Class对象(从参数数组中取出参数的类型),objectsMethod(参数数组)

 private <T> T getObjectFromMethodParam(Method method, Class<T> expectedClass,
      Object[] objectsMethod) {
    int countSameObjectsType = 0;
    T expectedObject = null;

    for (Object objectParam : objectsMethod) {
      if (expectedClass.isAssignableFrom(objectParam.getClass())) {
        expectedObject = (T) objectParam;
        countSameObjectsType++;
      }
    }

    if (countSameObjectsType > 1) {
      String errorMessage =
          method.getName() + Locale.JUST_ONE_INSTANCE + expectedObject.getClass().getSimpleName();
      throw new IllegalArgumentException(errorMessage);
    }

    return expectedObject;
  }

通过代码可以知道,传入Class对象是为了通过遍历取出和你期待的参数类型相同的参数(注意这个isAssignableFrom方法,是用来判断一个类和另一个类是否相同或是另一个类的超类或接口,注意countSameObjectsType这个整形,就是为了根据作者的本意的约定,防止有继承相同父类或者实现相同接口的参数类型出现)并返回,至此,两个参数的get()方法意图也很明显根据类型取出从外部调用时的参数对象

从上面分析看来ProxyTranslator#processMethod的过程就是对一次动态代理的invoke()过程的再封装,返回的是方法调用的信息的集合(包括方法的参数,注解包含的信息,返回值等),接下来又回到了动态代理中真正的InvocationHandler实现类ProxyProviders中,继续看真正的invoke()方法

@Override public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    return Observable.defer(new Func0<Observable<Object>>() {
      @Override public Observable<Object> call() {
        return processorProviders.process(proxyTranslator.processMethod(method, args));
      }
    });

又粘贴了下上面的代码哈,这次是ProcessorProviders#process,这个ProcessorProviders点进去看是一个接口,那么真正的实现类呢?

processorProviders = DaggerRxCacheComponent.builder()
        .rxCacheModule(new RxCacheModule(builder.getCacheDirectory(),
            builder.useExpiredDataIfLoaderNotAvailable(),
            builder.getMaxMBPersistenceCache(), getEncryptKey(providersClass),
            getMigrations(providersClass), builder.getJolyglot()))
        .build().providers();

实例化ProcessorProviders的过程在ProxyProviders的构造函数里。使用了依赖注入框架dagger,本文对dagger的用法不做介绍。点进RxCacheModule#provideProcessorProviders里面返回值类型是ProcessorProvidersBehaviour,它正是ProcessorProviders的实现类。我们主要关心ProcessorProviders#process这个方法。

@Override
  public <T> Observable<T> process(final ConfigProvider configProvider) {
    return (Observable<T>) Observable.defer(new Func0<Observable<Object>>() {
      @Override public Observable<Object> call() {
        if (hasProcessesEnded) {
          return getData(configProvider);
        }

        return oProcesses.flatMap(new Func1<Void, Observable<?>>() {
          @Override public Observable<?> call(Void aVoid) {
            return getData(configProvider);
          }
        });
      }
    });
  }

而这个方法主要定位到getData()再由这个方法继续定位下去是TwoLayersCache#retrieve,方法返回值类型为Record而且被Observable#just发射

<T> Observable<T> getData(final ConfigProvider configProvider) {
    return (Observable<T>) Observable.just(
        twoLayersCache.retrieve(configProvider.getProviderKey(), configProvider.getDynamicKey(),
            configProvider.getDynamicKeyGroup(), useExpiredDataIfLoaderNotAvailable,
            configProvider.getLifeTimeMillis(), configProvider.isEncrypted()))
        .map(new Func1<Record, Observable<Reply>>() {
          @Override public Observable<Reply> call(final Record record) {
            if (record != null && !configProvider.evictProvider().evict()) {
              return Observable.just(
                  new Reply(record.getData(), record.getSource(), configProvider.isEncrypted()));
            }

            return getDataFromLoader(configProvider, record);
          }
        }).flatMap(new Func1<Observable<Reply>, Observable<Object>>() {
          @Override
          public Observable<Object> call(Observable<Reply> responseObservable) {
            return responseObservable.map(new Func1<Reply, Object>() {
              @Override
              public Object call(Reply reply) {
                return getReturnType(configProvider, reply);
              }
            });
          }
        });

TwoLayersCache#retrieve接收configProvider传过来的各种参数,直接将参数return到方法RetrieveRecord#retrieveRecord
这个方法也是读取缓存的真正逻辑所在,读取的结果以Record类型返回,RxCache还支持类似于Spring@CacheEvict按key清除缓存功能,看代码和注释。

<T> Record<T> retrieveRecord(String providerKey, String dynamicKey, String dynamicKeyGroup,
      boolean useExpiredDataIfLoaderNotAvailable, Long lifeTime, boolean isEncrypted) {
    String composedKey = composeKey(providerKey, dynamicKey, dynamicKeyGroup);
    //根据key从内存中读数据
    Record<T> record = memory.getIfPresent(composedKey);

    if (record != null) {
      //内存不为空,将source标记为内存
      record.setSource(Source.MEMORY);
    } else {
      try {
        //为空就从磁盘读取,将source标记为磁盘
        record = persistence.retrieveRecord(composedKey, isEncrypted, encryptKey);
        record.setSource(Source.PERSISTENCE);
        memory.put(composedKey, record);
      } catch (Exception ignore) {
        return null;
      }
    }

    record.setLifeTime(lifeTime);

    //判断超时时间
    if (hasRecordExpired.hasRecordExpired(record)) {
      //下面就是根据EvictDynamicKey/EvictDynamicKeyGroup做缓存清除的工作,根据作者Github介绍,RxCache支持类似于Spring的@CacheEvict功能
      if (!dynamicKeyGroup.isEmpty()) {
        evictRecord.evictRecordMatchingDynamicKeyGroup(providerKey, dynamicKey,
            dynamicKeyGroup);
      } else if (!dynamicKey.isEmpty()) {
        evictRecord.evictRecordsMatchingDynamicKey(providerKey, dynamicKey);
      } else {
        evictRecord.evictRecordsMatchingProviderKey(providerKey);
      }

      return useExpiredDataIfLoaderNotAvailable ? record : null;
    }

    return record;
  }

上面说到返回的RecordObservable#just发射又由Observable#map转换为Observable<Reply>,之后有一个判断,就是当Record != null并且没有被标记需要清空全部缓存的时候才继续讲Record转换为Reply并发射。不满足条件的话就执行ProcessorProvidersBehaviour#getDataFromLoader,就是将使用的时候传入的外部请求(也是Observable,通常是Retrofit的接口请求方法)进行请求,将数据写入TwoLayersCache并将数据的Source标记为CLOUD(云端),以Observable<Reply>形式返回。

之后又做了一次Observable#flatMapObservable#map,在map中做了一次判断if(configProvider.requiredDetailedResponse()),就返回带有详细信息的数据(Reply类型)return new Reply<>(data, reply.getSource(), configProvider.isEncrypted());否则就返回data的深拷贝(Object类型),当然最后整个方法ProcessorProvidersBehaviour#getData的返回还是一个Observable供外部调用。

最后又回到了实现了InvocationHandler接口的ProxyProviders#invoke,还是动态代理哈,将接口的调用转发给ProxyProviders#invoke,真正的方法在这里面实现就好。

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

推荐阅读更多精彩内容

  • 简介 刚接触Retrofit的时候,就写了一篇简单的使用介绍:Retrofit 2.0基本使用方法,算是对Retr...
    Whyn阅读 2,833评论 4 24
  • 原文: Dyanmic Proxy Classes 介绍 一个动态代理类是实现了多个接口存在于运行时的类,这样,一...
    半黑月缺阅读 928评论 0 0
  • 推荐:看到如此多的 MVP+Dagger2+Retrofit+Rxjava 项目, 轻松拿 star, 心动了吗?...
    JessYan阅读 46,213评论 68 183
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 安卓开发领域中,很多重要的问题都有很好的开源解决方案,例如Square公司提供网络请求 OkHttp , Retr...
    aaron688阅读 1,902评论 1 20