Android緩存庫RxCache原理解析。
動機
RxCache相對於其它Android緩存庫相比,入門難度較大,原因之一就是因為語言的不同,加上原文檔本身內容就晦澀難懂,筆者也嘗試直接翻譯,但對於剛接觸該庫的開發者來說仍然有不低的門檻。
架構
首先來一張RxCache的架構圖:
RxCache使用註解來為Retrofit配置緩存信息,內部使用動態代理和Dagger來實現。
上述圖片及文字基本已經涵蓋RxCache的原理。
原理分析
一.數據的保存
數據的保存包括內存層(Memory)和持久層(Persistence),RxCache通過這兩個接口及其實現類進行數據的保存:
1.持久層
持久層(Persistence)主要依賴Persistence接口的子類Disk(磁盤)進行數據的操作,這個類負責的包括:
數據的序列化和反序列化 緩存數據的加密 緩存數據的存儲文件夾路徑配置
等等,筆者對於源碼分析的目標是,先看明白架構和設計思想,第一次不要過深執著於細節的實現。因此本文僅僅列出Persistence接口的抽象方法,並對其進行簡單介紹,詳細實現有興趣的朋友可以去查看Disk.class進行源碼查看。
public interface Persistence { //保存對象數據 void save(String key, Object object, boolean isEncrypted, String encryptKey); //保存被封裝的數據對象Record void saveRecord(String key, Record record, boolean isEncrypted, String encryptKey); //根據key將對應的持久層數據驅逐 void evict(String key); //驅逐該API的所有持久層數據 void evictAll(); //獲得所有的key List allKeys(); //以MB計算累計的緩存數據大小 int storedMB(); //根據key取出數據(就是取出緩存) T retrieve(String key, Class clazz, boolean isEncrypted, String encryptKey); //根據key取出封裝瞭數據的Record對象 Record retrieveRecord(String key, boolean isEncrypted, String encryptKey); }
其中,Record對象是封裝緩存數據以及該緩存數據的一些配置信息,我們簡單看一下其中部分源碼:
public final class Record { private Source source; private final T data;//data就是緩存數據Object private final long timeAtWhichWasPersisted; private final String dataClassName, dataCollectionClassName, dataKeyMapClassName; private Boolean expirable; //LifeTime requires to be stored to be evicted by EvictExpiredRecordsTask when no life time is available without a config provider private Long lifeTime; //Required by EvictExpirableRecordsPersistence task private transient float sizeOnMb; }
可以看到,Record對象內部除瞭封裝瞭緩存數據,此外還可以從中取得一些可能會用到的配置信息。
2.內存層:
內存層(Memory)主要依賴Memory接口的實現類ReferenceMapMemory, 我們依然以Memory接口為例。
public interface Memory { //根據key獲取對應Record數據 Record getIfPresent(String key); //根據key保存數據到內存 void put(String key, Record record); //獲得key集合 Set keySet(); //根據key驅逐內存中特定數據 void evict(String key); //驅逐內存中所有數據 void evictAll(); }
值得一提的是:
RxCache的內存緩存使用的是Map,它就用這個標識符作為Key,put和get數據(本地緩存則是將這個標識符作為文件名,使用流寫入或讀取這個文件,來儲存或獲取緩存),如果儲存和獲取的標識符不一致那就取不到想取的緩存。
這一點我們可以從實現類ReferenceMapMemory.class中看出:
public final class ReferenceMapMemory implements Memory { //內存緩存本質上使用的是Map進行數據的操作 private final Map referenceMap; public ReferenceMapMemory() { referenceMap = Collections.synchronizedMap(new io.rx_cache2.internal.cache.memory.apache.ReferenceMap()); } //......省略相關代碼 }
好的,現在我們已經初步將內存層和持久層的類和接口進行瞭基本的瞭解,接下來我們來考慮一下操作緩存數據的類TwoLayersCache.
二.數據的操作
1.TwoLayersCache
回到最上方的圖中,我們可以看到,TwoLayersCache這個類下方有很多的附屬類,實際上,這個類的作用如其名,就是負責管理內存層和持久層的緩存數據:
@Singleton public final class TwoLayersCache { private final EvictRecord evictRecord; private final io.rx_cache2.internal.cache.RetrieveRecord retrieveRecord; private final SaveRecord saveRecord; @Inject public TwoLayersCache(EvictRecord evictRecord, io.rx_cache2.internal.cache.RetrieveRecord retrieveRecord, SaveRecord saveRecord) { this.evictRecord = evictRecord; this.retrieveRecord = retrieveRecord; this.saveRecord = saveRecord; } //下面就是處理緩存的方法: //retrieveRecord 取出對應緩存 public Record retrieve(String providerKey, String dynamicKey, String dynamicKeyGroup, boolean useExpiredDataIfLoaderNotAvailable, Long lifeTime, boolean isEncrypted) { return retrieveRecord.retrieveRecord(providerKey, dynamicKey, dynamicKeyGroup, useExpiredDataIfLoaderNotAvailable, lifeTime, isEncrypted); } //saveRecord 保存緩存 public void save(String providerKey, String dynamicKey, String dynamicKeyGroup, Object data, Long lifeTime, boolean isExpirable, boolean isEncrypted) { saveRecord.save(providerKey, dynamicKey, dynamicKeyGroup, data, lifeTime, isExpirable, isEncrypted); } //evictRecord驅逐特定API的緩存 public void evictProviderKey(final String providerKey) { evictRecord.evictRecordsMatchingProviderKey(providerKey); } //evictRecord驅逐特定API下某個key對應的緩存 public void evictDynamicKey(String providerKey, String dynamicKey) { evictRecord.evictRecordsMatchingDynamicKey(providerKey, dynamicKey); } //evictRecord驅逐特定API下某個keygroup對應的緩存 public void evictDynamicKeyGroup(String key, String dynamicKey, String dynamicKeyGroup) { evictRecord.evictRecordMatchingDynamicKeyGroup(key, dynamicKey, dynamicKeyGroup); } //驅逐所有緩存 public void evictAll() { evictRecord.evictAll(); } }
我們來看一下TwoLayersCache的三個成員:
evictRecord 驅逐緩存數據 retrieveRecord 獲取緩存數據 saveRecord 保存緩存數據
這些成員看起來分工明確,確實如此,但是這些對象是怎麼對內存層和持久層的數據進行操作的呢?
2. Action
事實上,這三個對象都是Action的子類,Action是一個抽象類:
abstract class Action { private static final String PREFIX_DYNAMIC_KEY = "$d$d$d$"; private static final String PREFIX_DYNAMIC_KEY_GROUP = "$g$g$g$"; protected final Memory memory;//操作內存數據 protected final Persistence persistence;//操作本地數據 //每次Action的實例化都伴隨著內存層Memory和持久層Persistence的兩個對象的實例化 public Action(Memory memory, Persistence persistence) { this.memory = memory; this.persistence = persistence; } //緩存存儲時的命名規則 protected String composeKey(String providerKey, String dynamicKey, String dynamicKeyGroup) { return providerKey + PREFIX_DYNAMIC_KEY + dynamicKey + PREFIX_DYNAMIC_KEY_GROUP + dynamicKeyGroup; } //根據對應的命名規則,取出對應緩存文件中對應的緩存數據 protected List getKeysOnMemoryMatchingProviderKey(String providerKey) { List keysMatchingProviderKey = new ArrayList<>(); for (String composedKeyMemory : memory.keySet()) { final String keyPartProviderMemory = composedKeyMemory.substring(0, composedKeyMemory.lastIndexOf(PREFIX_DYNAMIC_KEY)); if (providerKey.equals(keyPartProviderMemory)) { keysMatchingProviderKey.add(composedKeyMemory); } } return keysMatchingProviderKey; } protected List getKeysOnMemoryMatchingDynamicKey(String providerKey, String dynamicKey) { List keysMatchingDynamicKey = new ArrayList<>(); String composedProviderKeyAndDynamicKey = providerKey + PREFIX_DYNAMIC_KEY + dynamicKey; for (String composedKeyMemory : memory.keySet()) { final String keyPartProviderAndDynamicKeyMemory = composedKeyMemory.substring(0, composedKeyMemory.lastIndexOf(PREFIX_DYNAMIC_KEY_GROUP)); if (composedProviderKeyAndDynamicKey.equals(keyPartProviderAndDynamicKeyMemory)) { keysMatchingDynamicKey.add(composedKeyMemory); } } return keysMatchingDynamicKey; } //獲得緩存規則的字符串 protected String getKeyOnMemoryMatchingDynamicKeyGroup(String providerKey, String dynamicKey, String dynamicKeyGroup) { return composeKey(providerKey, dynamicKey, dynamicKeyGroup); } }
對此,@Jessyan大神已經在他的文章中描述的很清楚瞭:
RxCache並不是通過使用URL充當標識符來儲存和獲取緩存的.
RxCache就是通過這兩個對象加上上面CacheProviders接口中聲明的方法名,組合起來一個標識符,通過這個標識符來存儲和獲取緩存
標識符規則為:
方法名 + $d$d$d$” + dynamicKey.dynamicKey + “$g$g$g$” + DynamicKeyGroup.group
dynamicKey或DynamicKeyGroup為空時則返回空字符串,即什麼都不傳的標識符為:
“方法名$d$d$d$$g$g$g$”
這些都說明,EvictRecord 、RetrieveRecord、SaveRecord 三個對象都持有內存層數據和持久層數據的引用,卻又各自有各自的職責(數據的驅逐/獲取/保存),分工明確,卻都由上層的TwoLayersCache進行操作。
關於EvictRecord 、RetrieveRecord、SaveRecord這三個類,同樣不細致化去講,有興趣的朋友或者有特殊需求可以去翻看源碼。
那麼這個TwoLayersCache是由誰來控制呢,很顯然,我們已經從圖中得知,TwoLayersCache有且僅有一個類負責操作,那就是ProcessorProvidersBehaviour.
三.額外的操作支持
其實看到TwoLayersCache,我們認為Cache功能已經大致有所瞭解瞭,既然TwoLayersCache負責控制數據不同操作的Action,每個Action下都持有Memory和Persistence 的引用,為何還要再封裝一層ProcessorProvidersBehaviour呢?
換句話說,ProcessorProvidersBehaviour除瞭TwoLayersCache的功能,還負責什麼功能呢?
我們來看源碼:
public final class ProcessorProvidersBehaviour implements ProcessorProviders { private final io.rx_cache2.internal.cache.TwoLayersCache twoLayersCache; private final Boolean useExpiredDataIfLoaderNotAvailable; private final GetDeepCopy getDeepCopy; private final Observable oProcesses; private volatile Boolean hasProcessesEnded; @Inject public ProcessorProvidersBehaviour( io.rx_cache2.internal.cache.TwoLayersCache twoLayersCache, Boolean useExpiredDataIfLoaderNotAvailable, io.rx_cache2.internal.cache.EvictExpiredRecordsPersistence evictExpiredRecordsPersistence, GetDeepCopy getDeepCopy, io.rx_cache2.internal.migration.DoMigrations doMigrations) { this.hasProcessesEnded = false; this.twoLayersCache = twoLayersCache;//兩層緩存 this.useExpiredDataIfLoaderNotAvailable = useExpiredDataIfLoaderNotAvailable;//若Loader不可用,加載過期數據的配置 this.getDeepCopy = getDeepCopy;//深拷貝 this.oProcesses = startProcesses(doMigrations, evictExpiredRecordsPersistence);//數據遷移 } //省略其他 }
從圖中也可以看到,ProcessorProvidersBehaviour 還負責其他功能,比如數據遷移,在DataLoader不可用狀態下是否加載過期數據等等。
我們看到實際上ProcessorProvidersBehaviour 實現瞭ProcessorProviders接口,這個接口很簡單:
public interface ProcessorProviders { //加載對應緩存數據 Observable process(final ConfigProvider configProvider); //清除所有數據 Observable evictAll(); }
這個ConfigProvider中包含我們請求緩存相關所有配置:
public final class ConfigProvider { private final String providerKey; private final Boolean useExpiredDataIfNotLoaderAvailable; private final Long lifeTime; private final boolean requiredDetailedResponse; private final boolean expirable; private final boolean encrypted; private final String dynamicKey, dynamicKeyGroup; private final Observable loaderObservable; private final EvictProvider evictProvider; //省略其他 }
現在我們可以說,如果我們隻要有瞭ConfigProvider 和ProcessorProvidersBehaviour 對象,我們就能自己腦補出RxCache的工作流程瞭!
但是,問題來瞭。
四.我們一無所有
我們一無所有!
確實如此,看起來我們隻需要ConfigProvider 和ProcessorProvidersBehaviour 對象,但是我們實際上需要很多很多依賴,才能創建這兩個對象的實例:
ConfigProvider依賴: 所有配置,包括dynamicKey, dynamicKeyGroup,是否加密,緩存失效時長,緩存文件名(providerkey)等等… ProcessorProvidersBehaviour依賴 :兩層緩存TwoLayersCache (它又包括Memory和Persistence的示例,有瞭他們才能創建對應的三個Action的子類,有瞭這三個子類才有TwoLayersCache 對象)、深拷貝、數據遷移等等…..
現在我們頭大的發現,我們真的是一無所有,或者說,所需要實例化的依賴太多瞭!
現在,站在RxCache作者的角度去思考,接下來該怎麼實現?
五、依賴註入:神兵利器Dagger2
六、動態代理:ProxyProviders
接下來就是核心實現方式瞭,不管你激動沒有,反正我是激動瞭。
還記得筆者引用的一句話嗎:
RxCache使用註解來為Retrofit配置緩存信息,內部使用動態代理和Dagger來實現。
我們看一下動態代理類ProxyProviders:
public final class ProxyProviders implements InvocationHandler { private final io.rx_cache2.internal.ProcessorProviders processorProviders; private final ProxyTranslator proxyTranslator; public ProxyProviders(RxCache.Builder builder, Class providersClass) { //下面實例化的processorProviders就是ProcessorProvidersBehaviour! processorProviders = DaggerRxCacheComponent.builder() .rxCacheModule(new RxCacheModule(builder.getCacheDirectory(), builder.useExpiredDataIfLoaderNotAvailable(), builder.getMaxMBPersistenceCache(), getEncryptKey(providersClass), getMigrations(providersClass), builder.getJolyglot())) .build().providers(); //ProxyTranslator內部提供瞭configProvider的實例化 proxyTranslator = new ProxyTranslator(); } }
對於依賴註入和動態代理不陌生的朋友們,看到段代碼應該就明白瞭,通過依賴註入和註解,取得緩存數據所需要的依賴配置。
而緩存數據的處理,則交給依賴動態代理生成的代理類去做。
至於這個代理類的示例是在哪裡使用的呢?
public final class RxCache { private final Builder builder; private ProxyProviders proxyProviders;//動態代理類 private RxCache(Builder builder) { this.builder = builder; } //就是它,獲取代理類的真實對象 public T using(final Class classProviders) { proxyProviders = new ProxyProviders(builder, classProviders); return (T) Proxy.newProxyInstance( classProviders.getClassLoader(), new Class[] {classProviders}, proxyProviders); } //省略其他代碼和RxCache建造者模式相關配置 }
在我們項目中的代碼中,當我們調用下面這行代碼:
new RxCache.Builder() .persistence(BaseApplication.getApplication().getExternalCacheDir(), new GsonSpeaker()) .using(UserCacheProviders.class);
實際上我們就已經獲得瞭該UserCacheProviders的對象,並可以進行接下來緩存數據的操作瞭。
至此,RxCache的原理分析也基本告一段落。