Android緩存庫RxCache原理解析

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的原理分析也基本告一段落。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *