Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions coil-base/api/coil-base.api
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ public final class coil/ImageLoader$Companion {

public final class coil/ImageLoaderBuilder {
public fun <init> (Landroid/content/Context;)V
public final fun addLastModifiedToFileCacheKey (Z)Lcoil/ImageLoaderBuilder;
public final fun allowHardware (Z)Lcoil/ImageLoaderBuilder;
public final fun allowRgb565 (Z)Lcoil/ImageLoaderBuilder;
public final fun availableMemoryPercentage (D)Lcoil/ImageLoaderBuilder;
Expand Down
18 changes: 16 additions & 2 deletions coil-base/src/androidTest/java/coil/fetch/FileFetcherTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,17 @@ import kotlin.test.assertTrue
class FileFetcherTest {

private lateinit var context: Context
private lateinit var fetcher: FileFetcher
private lateinit var pool: BitmapPool

@Before
fun before() {
context = ApplicationProvider.getApplicationContext()
fetcher = FileFetcher()
pool = BitmapPool(0)
}

@Test
fun basic() {
val fetcher = FileFetcher(true)
val file = context.copyAssetToFile("normal.jpg")

assertTrue(fetcher.handles(file))
Expand All @@ -45,6 +44,7 @@ class FileFetcherTest {

@Test
fun fileCacheKeyWithLastModified() {
val fetcher = FileFetcher(true)
val file = context.copyAssetToFile("normal.jpg")

file.setLastModified(1234L)
Expand All @@ -55,4 +55,18 @@ class FileFetcherTest {

assertNotEquals(secondKey, firstKey)
}

@Test
fun fileCacheKeyWithoutLastModified() {
val fetcher = FileFetcher(false)
val file = context.copyAssetToFile("normal.jpg")

file.setLastModified(1234L)
val firstKey = fetcher.key(file)

file.setLastModified(4321L)
val secondKey = fetcher.key(file)

assertEquals(secondKey, firstKey)
}
}
21 changes: 11 additions & 10 deletions coil-base/src/androidTest/java/coil/util/SystemCallbacksTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,17 @@ class SystemCallbacksTest {
val referenceCounter = BitmapReferenceCounter(weakMemoryCache, bitmapPool, null)
val memoryCache = MemoryCache(weakMemoryCache, referenceCounter, Int.MAX_VALUE, null)
val imageLoader = RealImageLoader(
context,
DefaultRequestOptions(),
bitmapPool,
referenceCounter,
memoryCache,
weakMemoryCache,
OkHttpClient(),
EventListener.Factory.NONE,
ComponentRegistry(),
null
context = context,
defaults = DefaultRequestOptions(),
bitmapPool = bitmapPool,
referenceCounter = referenceCounter,
memoryCache = memoryCache,
weakMemoryCache = weakMemoryCache,
callFactory = OkHttpClient(),
eventListenerFactory = EventListener.Factory.NONE,
registry = ComponentRegistry(),
addLastModifiedToFileCacheKey = true,
logger = null
)
val systemCallbacks = SystemCallbacks(imageLoader, context)

Expand Down
15 changes: 15 additions & 0 deletions coil-base/src/main/java/coil/ImageLoaderBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import okhttp3.Call
import okhttp3.OkHttpClient
import java.io.File

/** Builder for an [ImageLoader]. */
@BuilderMarker
Expand All @@ -46,6 +47,7 @@ class ImageLoaderBuilder(context: Context) {
private var availableMemoryPercentage = Utils.getDefaultAvailableMemoryPercentage(applicationContext)
private var bitmapPoolPercentage = Utils.getDefaultBitmapPoolPercentage()
private var trackWeakReferences = true
private var addLastModifiedToFileCacheKey = true

/**
* Set the [OkHttpClient] used for network requests.
Expand Down Expand Up @@ -185,6 +187,18 @@ class ImageLoaderBuilder(context: Context) {
this.trackWeakReferences = enable
}

/**
* Enables adding [File.lastModified] to the memory cache key when loading an image from a [File].
*
* This allows subsequent requests that load the same file to miss the memory cache if the file has been updated.
* To ensure a cached image is returned synchronously, [File.lastModified] is called on the main thread.
*
* Default: true
*/
fun addLastModifiedToFileCacheKey(enable: Boolean) = apply {
this.addLastModifiedToFileCacheKey = enable
}

/**
* Set a single [EventListener] that will receive all callbacks for requests launched by this image loader.
*/
Expand Down Expand Up @@ -342,6 +356,7 @@ class ImageLoaderBuilder(context: Context) {
callFactory = callFactory ?: buildDefaultCallFactory(),
eventListenerFactory = eventListenerFactory ?: EventListener.Factory.NONE,
registry = registry ?: ComponentRegistry(),
addLastModifiedToFileCacheKey = addLastModifiedToFileCacheKey,
logger = logger
)
}
Expand Down
3 changes: 2 additions & 1 deletion coil-base/src/main/java/coil/RealImageLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ internal class RealImageLoader(
callFactory: Call.Factory,
private val eventListenerFactory: EventListener.Factory,
registry: ComponentRegistry,
addLastModifiedToFileCacheKey: Boolean,
internal val logger: Logger?
) : ImageLoader {

Expand All @@ -120,7 +121,7 @@ internal class RealImageLoader(
// Fetchers
.add(HttpUriFetcher(callFactory))
.add(HttpUrlFetcher(callFactory))
.add(FileFetcher())
.add(FileFetcher(addLastModifiedToFileCacheKey))
.add(AssetUriFetcher(context))
.add(ContentUriFetcher(context))
.add(ResourceUriFetcher(context, drawableDecoder))
Expand Down
10 changes: 8 additions & 2 deletions coil-base/src/main/java/coil/fetch/FileFetcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ import okio.buffer
import okio.source
import java.io.File

internal class FileFetcher : Fetcher<File> {
internal class FileFetcher(private val addLastModifiedToFileCacheKey: Boolean) : Fetcher<File> {

override fun key(data: File) = "${data.path}:${data.lastModified()}"
override fun key(data: File): String {
return if (addLastModifiedToFileCacheKey) {
"${data.path}:${data.lastModified()}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never would have guessed this triggers a strict mode violation 😮

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! It's very unlikely to cause performance issues as we're only reading the file's metadata, but technically we touch the file system from the main thread.

} else {
data.path
}
}

override suspend fun fetch(
pool: BitmapPool,
Expand Down
21 changes: 11 additions & 10 deletions coil-base/src/test/java/coil/RealImageLoaderBasicTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,17 @@ class RealImageLoaderBasicTest {
val referenceCounter = BitmapReferenceCounter(weakMemoryCache, bitmapPool, null)
memoryCache = MemoryCache(weakMemoryCache, referenceCounter, Int.MAX_VALUE, null)
imageLoader = RealImageLoader(
context,
DefaultRequestOptions(),
bitmapPool,
referenceCounter,
memoryCache,
weakMemoryCache,
OkHttpClient(),
EventListener.Factory.NONE,
ComponentRegistry(),
null
context = context,
defaults = DefaultRequestOptions(),
bitmapPool = bitmapPool,
referenceCounter = referenceCounter,
memoryCache = memoryCache,
weakMemoryCache = weakMemoryCache,
callFactory = OkHttpClient(),
eventListenerFactory = EventListener.Factory.NONE,
registry = ComponentRegistry(),
addLastModifiedToFileCacheKey = true,
logger = null
)
}

Expand Down