feat(image): [android] adding force-cache cache control option (#47426)

Summary:
This PR follows up on https://github.com/facebook/react-native/issues/47182 and https://github.com/facebook/react-native/issues/47348 by adding `force-cache`, the final missing option to align caching controls with the existing behavior on iOS.

Local caching behavior remains unchanged: if a cached image is available locally, it will be returned; otherwise, a network request will be made.

When an image request is sent over the network, the `force-cache` option sent from the sent fJS side will now use the `okhttp3.CacheControl.FORCE_CACHE` directive.

## Changelog:

[ANDROID] [ADDED] - Image `force-cache` caching control option

Pull Request resolved: https://github.com/facebook/react-native/pull/47426

Test Plan:
New example added to the RNTester under the cache policy examples. Then inspecting that the cache control is set correctly before sending it in the `okhttp3.Request` builder.

```kt
FLog.w("ReactNative", "fetching uri: %s, with cacheControl: %s", uri, cacheControlBuilder.build().toString())
// fetching uri: https:...png?cacheBust=force-cache, with cacheControl: no-store, max-stale=2147483647, only-if-cached
```

This case was a bit more tricky to test in terms of e2e as it would involve some caching in the server as well, I'm open to suggestions to make this more complete.

Reviewed By: javache

Differential Revision: D65490360

Pulled By: Abbondanzo

fbshipit-source-id: f807a9793f85caea39c59a370d057b9a1d450a78
This commit is contained in:
Mateo Guzmán 2024-11-12 10:44:09 -08:00 committed by Facebook GitHub Bot
parent c69e330324
commit a0be88fd72
7 changed files with 30 additions and 8 deletions

View File

@ -50,8 +50,6 @@ export interface ImageURISource {
* its age or expiration date. If there is no existing data in the cache corresponding
* to a URL load request, no attempt is made to load the data from the originating source,
* and the load is considered to have failed.
*
* @platform ios (for `force-cache`)
*/
cache?: 'default' | 'reload' | 'force-cache' | 'only-if-cached' | undefined;
/**

View File

@ -65,8 +65,6 @@ export interface ImageURISource {
* its age or expiration date. If there is no existing data in the cache corresponding
* to a URL load request, no attempt is made to load the data from the originating source,
* and the load is considered to have failed.
*
* @platform ios (for `force-cache`)
*/
+cache?: ?('default' | 'reload' | 'force-cache' | 'only-if-cached');

View File

@ -3334,6 +3334,7 @@ public final class com/facebook/react/modules/fresco/FrescoModule$Companion {
public final class com/facebook/react/modules/fresco/ImageCacheControl : java/lang/Enum {
public static final field DEFAULT Lcom/facebook/react/modules/fresco/ImageCacheControl;
public static final field FORCE_CACHE Lcom/facebook/react/modules/fresco/ImageCacheControl;
public static final field ONLY_IF_CACHED Lcom/facebook/react/modules/fresco/ImageCacheControl;
public static final field RELOAD Lcom/facebook/react/modules/fresco/ImageCacheControl;
public static fun getEntries ()Lkotlin/enums/EnumEntries;

View File

@ -15,6 +15,12 @@ public enum class ImageCacheControl {
* be used to satisfy a URL load request.
*/
RELOAD,
/**
* The existing cache data will be used to satisfy a request, regardless of its age or expiration
* date. If there is no existing data in the cache corresponding to a URL load request, the data
* is loaded from the originating source.
*/
FORCE_CACHE,
/**
* The existing cache data will be used to satisfy a request, regardless of its age or expiration
* date. If there is no existing data in the cache corresponding to a URL load request, no attempt

View File

@ -12,6 +12,7 @@ import com.facebook.imagepipeline.backends.okhttp3.OkHttpNetworkFetcher
import com.facebook.imagepipeline.producers.NetworkFetcher
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.modules.network.OkHttpCompat
import java.util.concurrent.TimeUnit
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import okhttp3.Request
@ -35,21 +36,26 @@ internal class ReactOkHttpNetworkFetcher(private val okHttpClient: OkHttpClient)
fetchState.submitTime = SystemClock.elapsedRealtime()
val uri = fetchState.uri
var requestHeaders: Map<String, String>? = null
val cacheControlBuilder = CacheControl.Builder().noStore()
val cacheControlBuilder = CacheControl.Builder()
if (fetchState.context.imageRequest is ReactNetworkImageRequest) {
val networkImageRequest = fetchState.context.imageRequest as ReactNetworkImageRequest
requestHeaders = getHeaders(networkImageRequest.headers)
when (networkImageRequest.cacheControl) {
ImageCacheControl.RELOAD -> {
cacheControlBuilder.noCache()
cacheControlBuilder.noStore().noCache()
}
ImageCacheControl.FORCE_CACHE -> {
cacheControlBuilder.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
}
ImageCacheControl.ONLY_IF_CACHED -> {
cacheControlBuilder.onlyIfCached()
cacheControlBuilder.onlyIfCached().maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
}
ImageCacheControl.DEFAULT -> {
// No-op
cacheControlBuilder.noStore()
}
}
} else {
cacheControlBuilder.noStore()
}
val headers = OkHttpCompat.getHeadersFromMap(requestHeaders)
val request =

View File

@ -312,6 +312,7 @@ public class ReactImageView(
null,
"default" -> ImageCacheControl.DEFAULT
"reload" -> ImageCacheControl.RELOAD
"force-cache" -> ImageCacheControl.FORCE_CACHE
"only-if-cached" -> ImageCacheControl.ONLY_IF_CACHED
else -> ImageCacheControl.DEFAULT
}

View File

@ -666,6 +666,18 @@ function CacheControlAndroidExample(): React.Node {
key={reload}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>Force-cache</RNTesterText>
<Image
source={{
uri: fullImage.uri + '?cacheBust=force-cache',
cache: 'force-cache',
}}
style={styles.base}
key={reload}
onError={e => console.log(e.nativeEvent.error)}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>
Only-if-cached