Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand the Retrofit cache RxCache

2025-03-30 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

Retrofit cache RxCache how to understand, in view of this problem, this article introduces the corresponding analysis and solutions in detail, hoping to help more partners who want to solve this problem to find a more simple and easy way.

Preface

Retrofit is undoubtedly the most popular network request library at present, and it is standard for every project to use it with brother Okhttp. Because Okhttp has its own cache, many people don't care about other caches, but friends who have used Okhttp cache must know that Okhttp cache must be used with Header, which is troublesome and inflexible, so now I recommend a cache library specially built for Retrifit.

Project address: RxCache Demo address: RxCacheSample

Brief introduction

RxCache uses annotations to configure cache information for Retrofit, and internally uses dynamic proxies and Dagger to implement it. There are relatively few materials in this library, and the official tutorials are all in English, which undoubtedly increases the difficulty for developers to use. In fact, my English is not good, but the source code is universal, so I explain this library from the point of view of source code. In fact, the source code of this library is all in Dagger injection. Let me first explain the usage for you. Later will write an article to explain the source code, in learning Dagger friends not only suggest to see my MVPArms, but also look at the source code of this RxCache, can learn a lot of things, first give Zhang RxCache architecture map, let you try something new, please look forward to my later source code analysis.

Use

1. Define an interface, which is similar to Retrofit. Each method in the interface corresponds to the method in the Retrofit interface. The return value of the corresponding Retrofit interface method must be passed into the parameters of each method (the return value must be Observable, otherwise an error is reported). Several other parameters, DynamicKey,DynamicKeyGroup and EvictProvider, are not necessary, but only one object can be passed in each if it is to be passed, otherwise the meaning of these parameters is the most confused for beginners. It will be analyzed later.

/ * this is RxCache official Demo * / public interface CacheProviders {@ LifeCache (duration = 2, timeUnit = TimeUnit.MINUTES) Observable getRepos (Observable oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey); @ LifeCache (duration = 2, timeUnit = TimeUnit.MINUTES) Observable getUsers (Observable oUsers, DynamicKey idLastUserQueried, EvictProvider evictProvider); Observable getCurrentUser (Observable oUser, EvictProvider evictProvider);}

two。 Instantiate the interface, which is similar to the way Retrofit is built. Pass in the interface through the using method and return a dynamic proxy object of the interface. Caching can be achieved by calling the method of this object and passing in corresponding parameters. Some custom configurations can be achieved by annotating and passing in different parameters, so easy~

CacheProviders cacheProviders = new RxCache.Builder () .persistence (cacheDir, new GsonSpeaker ()) .using (CacheProviders.class)

Detailed explanation

In fact, the use of RxCache is relatively simple, the above two steps can easily implement the cache, the features of this library mainly focus on the custom configuration of the cache, so I will mainly talk about those parameters and comments.

Parameters.

Observable

The meaning of this Observable is that you need to pass in the Retrofit API you want to cache as a parameter (the return value must be Observable). When there is no cache, or the cache has expired, or the EvictProvider is true, the RxCache will re-request * * data through this Retrofit API, and the result returned by the server will be packaged as Reply, and one copy will be saved to the memory cache and disk cache before the return.

It is worth mentioning that if you need to know where the returned result comes from (local, memory or network) and whether it is encrypted, you can use Observable as the return value of the method, so that RxCache will wrap the result with Reply. If not, declare the data type Observable of the result directly in the paradigm.

Exception

If useExpiredDataIfLoaderNotAvailable is set to true when building RxCache, it will ignore the situation that EvictProvider is true or cache expires when the data is empty or when an error occurs, and continue to use cache (provided that a cache has been requested before).

DynamicKey & DynamicKeyGroup

What many developers are most confused about is the meaning of these two parameters. Will it matter if the two are passed together or not? At this point, I will mention how RxCache stores the cache. RxCache does not store and obtain the cache by using URL as an identifier.

What is that?

Yes, RxCache combines these two objects with the method names declared in the above CacheProviders interface to form an identifier through which the cache is stored and obtained.

The rules for identifiers are:

Method name + $dsimpldpromod$ "+ dynamicKey.dynamicKey +" $gaccoungSecretg$ "+ DynamicKeyGroup.group

If dynamicKey or DynamicKeyGroup is empty, an empty string is returned, that is, the identifier that passes nothing is:

"the name of the method is $DobbldExcept."

What do you mean?

For example, RxCache's in-memory cache uses Map as its Key,put and get data (the local cache uses this identifier as the file name and uses the stream to write or read the file to store or obtain the cache). If the identifiers stored and obtained are not the same, the desired cache will not be obtained.

What does it have to do with us?

For example, one of our interfaces has a paging function. We use RxCache to set a cache for him for 3 minutes. If neither of these two objects is passed in parameters, it will default to use the method name of this interface to store and obtain the cache, meaning that we used this interface to obtain the data of * * pages. We called this API several times within three minutes to request other paging data. The cache returned is still the data of * * pages until the cache expires, so if we want to have paging function, we must pass in a key for internal storage of DynamicKey,DynamicKey. When we pass in the number of pages when we build, RxCache will save a cache according to the number of pages. What it does internally is to change the method name + DynamicKey into an identifier of String type to obtain and store the cache.

What does DynamicKey have to do with DynamicKeyGroup?

DynamicKey stores a Key,DynamicKey application scenario: if you request the same interface, you need to return different data according to different variables, such as paging, and just pass in the number of pages during construction.

Two key,DynamicKeyGroup stored in DynamicKeyGroup are enhanced versions based on DynamicKey. Application scenarios: requesting the same API requires not only paging, but also returning different data according to different registrants. When you construct a DynamicKeyGroup, you can pass the number of pages with * parameters in the constructor and pass the user identifier in the second parameter.

In theory, DynamicKey and DynamicKeyGroup only need to pass one of them according to different requirements, but you can also pass both parameters. Take the above requirement as an example, if both parameters are passed, it will first take the Key (page count) of DynamicKey and then the second Key (user identifier) of DynamicKeyGroup, plus the API name to form an identifier to obtain and store data, so that the * Key (number of pages) of DynamicKeyGroup will be ignored.

EvictProvider & EvictDynamicKey & EvictDynamicKeyGroup

There is a boolean field stored inside these three objects, which means whether to expel (use or delete) the cache. When RxCache fetches an unexpired cache, it will consider whether to use this cache according to this boolean field. If it is true, it will re-obtain new data through Retrofit, and if it is false, it will use this cache.

What is the relationship between these three objects?

These three objects inherit from each other, and the inheritance relationship is EvictProvider.

< EvictDynamicKey < EvictDynamicKeyGroup,这三个对象你只能传其中的一个,多传一个都会报错,按理说你不管传那个对象都一样,因为里面都保存有一个boolean字段,根据这个字段判断是否使用缓存. 不同在哪呢? 如果有未过期的缓存,并且里面的boolean为false时,你传这三个中的哪一个都是一样的,但是在boolean为true时,这时就有区别了,RxCache会在Retrofit请求到新数据后,在boolean为true时删除对应的缓存. 删除规则是什么呢? 还是以请求一个接口,该接口的数据会根据不同的分页返回不同的数据,并且同一个分页还要根据不同用户显示不同的数据为例 三个都不传,RxCache会自己new EvictProvider(false);,这样默认为false就不会删除任何缓存 EvictDynamicKeyGroup 只会删除对应分页下,对应用户的缓存. EvictDynamicKey 会删除那个分页下的所有缓存,比如你请求的是***页下user1的数据,它不仅会删除user1的数据还会删除当前分页下其他user2,user3...的数据. EvictProvider 会删除当前接口下的所有缓存,比如你请求的是***页的数据,它不仅会删除***页的数据,还会把这个接口下其他分页的数据全删除. 所以你可以根据自己的逻辑选择传那个对象,如果请求的这个接口没有分页功能,这时你不想使用缓存,按理说你应该传EvictProvider,并且在构造时传入true,但是你如果传EvictDynamicKey和EvictDynamicKeyGroup达到的效果也是一样. 注解 @LifeCache @LifeCache顾名思义,则是用来定义缓存的生命周期,当Retrofit获取到***的数据时,会将数据及数据的配置信息封装成Record,在本地和内存中各保存一份,Record中则保存了@LifeCache的值(毫秒)和当前数据请求成功的时间(毫秒)timeAtWhichWasPersisted. 以后每次取缓存时,都会判断timeAtWhichWasPersisted+@LifeCache的值是否小于当前时间(毫秒),小于则过期,则会立即清理当前缓存,并使用Retrofit重新请求***的数据,如果EvictProvider为true不管缓存是否过期都不会使用缓存. @EncryptKey & @Encrypt 这两个注解的作用都是用来给缓存加密,区别在于作用域不一样 @EncryptKey是作用在接口上 @EncryptKey("123")public interface CacheProviders { } 而@Encrypt是作用在方法上 @EncryptKey("123")public interface CacheProviders { @Encrypt Observable getCurrentUser(Observable oUser, EvictProvider evictProvider); } } 如果需要给某个接口的缓存做加密的操作,则在对应的方法上加上@Encrypt,在存储和获取缓存时,RxCache就会使用@EncryptKey的值作为Key给缓存数据进行加解密,因此每个Interface中的所有的方法都只能使用相同的Key. 值得注意的时,RxCache只会给本地缓存进行加密操作,并不会给内存缓存进行加密,给本地数据加密使用的是Java自带的CipherInputStream,解密使用的是CipherOutputStream @Expirable 还记得我们在构建RxCache时,有一个setMaxMBPersistenceCache方法,这个可以设置,本地缓存的***容量,单位为MB,如果没设置则默认为100MB. 这个***容量和@Expirable又有什么关系呢? 当然有!还记得我之前说过在每次Retrofit重新获取***数据时,返回数据前会将***数据在内存缓存和本地缓存中各存一份. 存储完毕后,会检查现在的本地缓存大小,如果现在本地缓存中存储的所有缓存大小加起来大于或者等于setMaxMBPersistenceCache中设置的大小(默认为100MB)的百分之95,RxCache就会做一些操作,将总的缓存大小控制在百分之70以下. 做的什么操作? 很简单,RxCache会遍历,构建RxCache时传入的cacheDirectory中的所有缓存数据,一个个删除直到总大小小于百分70,遍历的顺序不能保证,所以搞不好对你特别重要的缓存就被删除了,这时@Expirable就派上用场了,在方法上使用它并且给它设置为false(如果没使用这个注解,则默认为true),就可以保证这个接口的缓存数据,在每次需要清理时都幸免于难. @Expirable(false) Observable getCurrentUser(Observable oUser, EvictProvider evictProvider); 值得注意的是: 构建RxCache时persistence方法传入的cacheDirectory,是用来存放RxCache本地缓存的文件夹,这个文件夹里***不要有除RxCache之外的任何数据,这样会在每次需要遍历清理缓存时,节省不必要的开销,因为RxCache并没检查文件名,不管是不是自己的缓存,他都会去遍历获取 @SchemeMigration & @Migration 这两个注解是用来数据迁移的,用法: @SchemeMigration({ @Migration(version = 1, evictClasses = {Mock.class}), @Migration(version = 2, evictClasses = {Mock2.class}) }) interface Providers {} 什么叫数据迁移呢? 简单的说就是在***的版本中某个接口返回值类型内部发生了改变,从而获取数据的方式发生了改变,但是存储在本地的数据,是未改变的版本,这样在反序列化时就可能发生错误,为了规避这个风险,作者就加入了数据迁移的功能. 有什么应用场景呢? 可能上面的话,不是很好理解,举个非常简单的例子: public class Mock{ private int id; } Mock里面有一个字段id,现在是一个整型int,能满足我们现在的需求,但是随着产品的迭代,发现int不够用了 public class Mock{ private long id; } 为了满足现在的需求,我们使用long代替int,由于缓存中的Mock还是之前未改变的版本,并且未过期,在使用本地缓存时会将数据反序列化,将int变为long,就会出现问题. 数据迁移是怎么解决上面的问题呢? 其实非常简单,就是使用注解声明,之前有缓存并且内部修改过的class,RxCache会把含有这些class的缓存全部清除掉. RxCache是怎么操作的呢? 值得一提的是,在每次创建接口的动态代理时,也就是在每次调用RxCache.using(CacheProviders.class)时,会执行两个操作,清理含有@Migration中声明的evictClasses的缓存,以及遍历本地缓存文件夹清理所有已经过期的缓存. 每次清理完需要数据迁移的缓存时,会将version值***的@Migration的version值保存到本地. @SchemeMigration({ @Migration(version = 1, evictClasses = {Mock.class}), @Migration(version = 3, evictClasses = {Mock3.class}), @Migration(version = 2, evictClasses = {Mock2.class}) }) interface Providers {} 如上面的声明方式,它会将3保存到本地,每次调用using(),开始数据迁移时会将上次保存的version值从本地取出来,会在@SchemeMigration中查找大于这个version值的@Migration,取出里面evictClasses,去重后,遍历所有本地缓存,只要缓存数据中含有你声明的class,就将这个缓存清除. 比如evictClasses中声明了Mock.class,会把以Observable< List< Mock >

>, Observable

< Map< String,Mock >

>, Observable

< Mock[] >

Or Observable.

< Mock >

Clean up the interface cache as the return value, and then record the * version value locally.

So every time there is a class that needs data migration, a new @ Migration must be added to @ SchemeMigration, and the value of version in the annotation must be + 1, so as to achieve the effect of data migration.

@ SchemeMigration ({@ Migration (version = 1, evictClasses = {Mock.class}), @ Migration (version = 3, evictClasses = {Mock3.class}), @ Migration (version = 2, evictClasses = {Mock2.class}), @ Migration (version = 4, evictClasses = {Mock2.class}) interface Providers {}

If a change occurs within the Mock2 and data migration is needed, a new @ Migration,version = 4 (3x1) should be added. When using () is called, only the class declared in the @ Migration of version = 4 will be migrated (that is, the cached data containing this class will be cleaned).

@ Actionable

This note states in the official introduction that the annotation processor will be used to give the Interface that uses this annotation, automatically generate a class file with the same class name ending with Actionable, and use the APi of this class to facilitate and better perform write operations, without using it, without too much introduction.

A problem has been found in use, if using BaseResponse

< T >

Errors can occur when wrapping data, such as issue#41 and issue#73.

Analyze the problem

As mentioned above, RxCache will encapsulate the data returned by Retrofit into a Record object, and Record will determine which type of data it is, and will first determine whether the data is Collection (parent of List), array or Map. If it is not, it will default that the data is a normal object.

There are three fields in Record that store this data, the name of the container class, the class name of the value in the container, and the Key class name of Map, meaning if the data type is List

< String >

The container class name is List, the value class name is String,Key, and the class name is empty if the data type is Map

< String,Integer >

The container class is named Map, the value class is named Integer,key, and the class is named String.

The purpose of these three fields is that when fetching the local cache, you can use Gson to restore the type of real data based on the field type, and that's the problem, because BaseResponse is used.

< T >

Package data, in the above judgment, he ruled out that the data is List, array or Map, it will only assume that the data is a normal object, then he will only save the median class name in the three fields as BaseResponse, the other is empty, the type of the paradigm it does not pass the field record, so it will not return the type of T correctly when it is taken.

Solve the problem

After knowing where the problem is, let's solve the problem now. there are two directions to solve the problem, one is internal solution and the other is external solution. The external solution can be solved through the way mentioned by issue#73 above.

The so-called internal solution is to change the internal code of the framework. The problem is that when the data is a normal object, Record will not use fields to save the type name of the paradigm, so he cannot recover the data type correctly when fetching the local cache.

The solution is that we must do special treatment when the data is an ordinary object. The simplest way is to judge the instanceof BaseResponse when the data is the object, and repeat the above judgment if it is true.

That is, to determine whether the type of T in BaseResponse is List, array, Map or object?

Then save the corresponding type name with the corresponding field, and when you take the local cache, you can use Gson to restore the correct data type by these fields, but this mandatory judgment of instanceof will greatly reduce the flexibility and expansibility of a framework, so I will seriously consider this problem when I write source code analysis later. If I can, I will Pull Request to Rxcache.

This is the answer to the question about how to understand the Retrofit cache RxCache. I hope the above content can be of some help to you. If you still have a lot of doubts to be solved, you can follow the industry information channel for more related knowledge.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report