RxCache源码解析
来源:互联网 发布:网络人肉搜索现象分析 编辑:程序博客网 时间:2024/06/04 18:11
RxCache
是使用注解为Retrofit
加入二级缓存(内存,磁盘)的缓存库。 开头膜拜大神
项目地址 : RxCache
本文最先发表于简书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()
就创建了接口的实例,和Retrofit
的create()
方法也十分相似,当然内部实现也很相似(都是使用了动态代理)。
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#loadConfigProviderMethod
load出了一个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
,另一种需要传入两个参数method
和objectsMethod
。这里以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; }
上面说到返回的Record
被Observable#just
发射又由Observable#map
转换为Observable<Reply>
,之后有一个判断,就是当Record != null
并且没有被标记需要清空全部缓存的时候才继续讲Record
转换为Reply
并发射。不满足条件的话就执行ProcessorProvidersBehaviour#getDataFromLoader
,就是将使用的时候传入的外部请求(也是Observable
,通常是Retrofit的接口请求方法)进行请求,将数据写入TwoLayersCache
并将数据的Source
标记为CLOUD
(云端),以Observable<Reply>
形式返回。
之后又做了一次Observable#flatMap
和Observable#map
,在map中做了一次判断if(configProvider.requiredDetailedResponse())
,就返回带有详细信息的数据(Reply类型)return new Reply<>(data, reply.getSource(), configProvider.isEncrypted());
否则就返回data
的深拷贝(Object类型),当然最后整个方法ProcessorProvidersBehaviour#getData
的返回还是一个Observable
供外部调用。
最后又回到了实现了InvocationHandler
接口的ProxyProviders#invoke
,还是动态代理哈,将接口的调用转发给ProxyProviders#invoke
,真正的方法在这里面实现就好。
- RxCache源码解析
- Android RxCache原理解析
- RxCache的简单源码分析
- RxCache使用教程
- RxCache使用注意事项
- RxCache中文文档
- Android RxCache使用详解
- RxCache要点解读(四)
- 源码解析
- 源码解析
- 【JDk源码解析之一】ArrayList源码解析
- 【源码解析】-- ArrayList的源码解析
- EventBus源码解析(史上最全的源码解析)
- 【源码】Vector、Stack源码解析
- Sping源码解析-源码下载
- <Android源码>IntentService源码解析
- JAVA源码解析-String源码
- JAVA源码解析-ArrayList源码
- C#winfrom播放器动态加载歌词
- [LeetCode] Valid Parentheses 验证括号
- 《算法概论》习题8.9 碰撞集
- 华软2016校内选拔赛
- Sphinx之匹配方式
- RxCache源码解析
- 跟Google学写代码--Chromium工程中用到的C++11特性(Library Features)
- 一台电脑不能连接另外一台电脑的数据库
- AJAX&JQuery&JSON
- Tab Control控件使用
- Oracle安装Zabbix
- 分布式内存组织框架Apache Ignite 资源分享
- BP神经网络python实现
- Win32线程——在某个线程内终止另一个正在运行的线程(2)(Event对象)