@nativescript-community/ui-image 5.0.12 → 5.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,12 +5,15 @@ import android.os.Looper;
5
5
  import androidx.annotation.GuardedBy;
6
6
  import androidx.annotation.MainThread;
7
7
  import androidx.annotation.Nullable;
8
+ import com.bumptech.glide.Glide;
8
9
  import com.bumptech.glide.load.Key;
9
10
  import com.bumptech.glide.load.Options;
10
11
  import com.bumptech.glide.load.Transformation;
11
12
  import com.bumptech.glide.load.engine.cache.DiskCache;
12
13
  import com.bumptech.glide.load.engine.cache.MemoryCache;
13
14
  import com.bumptech.glide.load.engine.cache.LruResourceCache;
15
+ import com.bumptech.glide.load.engine.cache.ModelSignatureDiskLruCacheWrapper;
16
+
14
17
  import java.util.concurrent.ExecutorService;
15
18
  import java.util.concurrent.Executors;
16
19
  import java.util.concurrent.atomic.AtomicBoolean;
@@ -35,18 +38,29 @@ import android.util.Log;
35
38
  * - Callbacks are always posted on the main thread.
36
39
  */
37
40
  public final class EvictionManager {
38
- private static final String TAG = "EvictionManager";
41
+ private static final String TAG = "JS";
42
+
39
43
  private static final EvictionManager INSTANCE = new EvictionManager();
40
44
 
45
+ // Cached reflection fields/methods for ActiveResources access
46
+ private static volatile java.lang.reflect.Field engineField;
47
+ private static volatile java.lang.reflect.Field activeResourcesField;
48
+ private static volatile java.lang.reflect.Field activeEngineResourcesField;
49
+
50
+ private static volatile boolean reflectionInitialized = false;
51
+ private static volatile boolean reflectionFailed = false;
52
+
41
53
  private final ExecutorService diskExecutor = Executors.newSingleThreadExecutor();
42
54
  private final Handler mainHandler = new Handler(Looper.getMainLooper());
43
55
 
44
56
  @GuardedBy("this")
45
- private DiskCache diskCache;
57
+ private ModelSignatureDiskLruCacheWrapper diskCache;
46
58
  @GuardedBy("this")
47
- private LruResourceCache memoryCache;
59
+ private ModelSignatureMemoryCache memoryCache;
60
+
61
+ private Glide glideInstance;
48
62
 
49
- // in-memory store for captured info and engineKey object
63
+ // in-memory store for captured info
50
64
  private final CacheKeyStore inMemoryKeyStore = new CacheKeyStore();
51
65
 
52
66
  // optional persistent store (SharedPref). If null, fallback to in-memory only.
@@ -60,11 +74,15 @@ public final class EvictionManager {
60
74
  return INSTANCE;
61
75
  }
62
76
 
63
- public synchronized void setDiskCache(DiskCache diskCache) {
77
+ public void setGlideInstance(Glide glideInstance) {
78
+ this.glideInstance = glideInstance;
79
+ }
80
+
81
+ public synchronized void setDiskCache(ModelSignatureDiskLruCacheWrapper diskCache) {
64
82
  this.diskCache = diskCache;
65
83
  }
66
84
 
67
- public synchronized void setMemoryCache(LruResourceCache memoryCache) {
85
+ public synchronized void setMemoryCache(ModelSignatureMemoryCache memoryCache) {
68
86
  this.memoryCache = memoryCache;
69
87
  }
70
88
 
@@ -73,10 +91,12 @@ public final class EvictionManager {
73
91
  }
74
92
 
75
93
  /**
76
- * Expose the in-memory store so callers/listeners can put engineKey objects
77
- * there.
94
+ * Expose the in-memory store so callers/listeners can put key objects there.
78
95
  */
79
96
  public CacheKeyStore getKeyStore() {
97
+ if (persistentStore != null) {
98
+ return persistentStore;
99
+ }
80
100
  return inMemoryKeyStore;
81
101
  }
82
102
 
@@ -85,45 +105,23 @@ public final class EvictionManager {
85
105
  */
86
106
  public synchronized void saveKeys(final String id, final CacheKeyStore.StoredKeys newStored) {
87
107
  if (id == null || newStored == null) {
88
- Log.w(TAG, "saveKeys called with null id or newStored");
89
108
  return;
90
109
  }
91
110
 
92
111
  try {
93
- // 1) Load existing stored keys (check persistent store first, fallback to
94
- // in-memory)
95
- CacheKeyStore.StoredKeys existing = null;
96
- if (persistentStore != null) {
112
+ // 1) Load existing stored keys
113
+ CacheKeyStore.StoredKeys existing = inMemoryKeyStore.get(id);
114
+ if (existing == null && persistentStore != null) {
97
115
  existing = persistentStore.get(id);
98
116
  }
99
- if (existing == null) {
100
- existing = inMemoryKeyStore.get(id);
101
- }
102
117
 
103
- // 2) If existing has an engineKey and newStored doesn't, merge the engineKey in
118
+ // 2) Just save the new keys directly
104
119
  CacheKeyStore.StoredKeys toPersist = newStored;
105
- if (existing != null && existing.engineKey != null && toPersist.engineKey == null) {
106
- // create a new StoredKeys that is identical to newStored but keeps
107
- // existing.engineKey
108
- toPersist = new CacheKeyStore.StoredKeys(
109
- toPersist.sourceKey,
110
- toPersist.signature,
111
- toPersist.width,
112
- toPersist.height,
113
- toPersist.transformation,
114
- toPersist.transformationKeyBytes,
115
- toPersist.decodedResourceClass,
116
- toPersist.options,
117
- toPersist.optionsKeyBytes,
118
- existing.engineKey // preserve captured engine key
119
- );
120
- }
121
-
122
- // 3) Persist to persistent store if available, else to in-memory store
120
+ // Log.d(TAG, "EvictionManager saveKeys " + id + " " + toPersist);
121
+ // 3) Save to both stores
122
+ inMemoryKeyStore.put(id, toPersist);
123
123
  if (persistentStore != null) {
124
124
  persistentStore.put(id, toPersist);
125
- } else {
126
- inMemoryKeyStore.put(id, toPersist);
127
125
  }
128
126
  } catch (Throwable t) {
129
127
  Log.w(TAG, "saveKeys failed for id=" + id, t);
@@ -142,13 +140,9 @@ public final class EvictionManager {
142
140
 
143
141
  /** Disk presence callback for async check. */
144
142
  public interface DiskPresenceCallback {
145
- void onResult(boolean sourcePresent, boolean transformedPresent);
143
+ void onResult(boolean present);
146
144
  }
147
145
 
148
- // -----------------------
149
- // New: clear APIs
150
- // -----------------------
151
-
152
146
  /**
153
147
  * Clear the entire disk cache. Runs on a background thread. Callback invoked on
154
148
  * main thread.
@@ -157,7 +151,7 @@ public final class EvictionManager {
157
151
  diskExecutor.execute(() -> {
158
152
  boolean success = true;
159
153
  Exception ex = null;
160
- DiskCache dc;
154
+ ModelSignatureDiskLruCacheWrapper dc;
161
155
  synchronized (EvictionManager.this) {
162
156
  dc = diskCache;
163
157
  }
@@ -169,6 +163,9 @@ public final class EvictionManager {
169
163
  ex = e;
170
164
  }
171
165
  }
166
+ if (persistentStore != null) {
167
+ persistentStore.clearAll();
168
+ }
172
169
  if (callback != null) {
173
170
  final boolean finalSuccess = success;
174
171
  final Exception finalEx = ex;
@@ -191,18 +188,20 @@ public final class EvictionManager {
191
188
  mainHandler.post(() -> {
192
189
  boolean success = true;
193
190
  Exception ex = null;
194
- LruResourceCache mc;
191
+ ModelSignatureMemoryCache mc;
195
192
  synchronized (EvictionManager.this) {
196
193
  mc = memoryCache;
197
194
  }
198
195
  if (mc != null) {
199
196
  try {
200
197
  mc.clearMemory();
198
+ clearAllActiveResources();
201
199
  } catch (Exception e) {
202
200
  success = false;
203
201
  ex = e;
204
202
  }
205
203
  }
204
+ inMemoryKeyStore.clearAll();
206
205
  if (callback != null) {
207
206
  callback.onComplete(success, ex);
208
207
  }
@@ -225,7 +224,7 @@ public final class EvictionManager {
225
224
  diskExecutor.execute(() -> {
226
225
  boolean diskSuccess = true;
227
226
  Exception firstEx = null;
228
- DiskCache dc;
227
+ ModelSignatureDiskLruCacheWrapper dc;
229
228
  synchronized (EvictionManager.this) {
230
229
  dc = diskCache;
231
230
  }
@@ -242,13 +241,14 @@ public final class EvictionManager {
242
241
  mainHandler.post(() -> {
243
242
  boolean memSuccess = true;
244
243
  Exception memEx = null;
245
- LruResourceCache mc;
244
+ ModelSignatureMemoryCache mc;
246
245
  synchronized (EvictionManager.this) {
247
246
  mc = memoryCache;
248
247
  }
249
248
  if (mc != null) {
250
249
  try {
251
250
  mc.clearMemory();
251
+ clearAllActiveResources();
252
252
  } catch (Exception e) {
253
253
  memSuccess = false;
254
254
  if (finalFirstEx == null)
@@ -257,6 +257,10 @@ public final class EvictionManager {
257
257
  }
258
258
  boolean combined = finalDiskSuccess && memSuccess;
259
259
  Exception combinedEx = finalFirstEx != null ? finalFirstEx : memEx;
260
+ inMemoryKeyStore.clearAll();
261
+ if (persistentStore != null) {
262
+ persistentStore.clearAll();
263
+ }
260
264
  if (callback != null)
261
265
  callback.onComplete(combined, combinedEx);
262
266
  });
@@ -268,10 +272,6 @@ public final class EvictionManager {
268
272
  clearAll(null);
269
273
  }
270
274
 
271
- // -----------------------
272
- // Eviction-by-id APIs
273
- // -----------------------
274
-
275
275
  /**
276
276
  * Evict both disk (source + transformed) and memory for id. Callback invoked on
277
277
  * main thread.
@@ -279,22 +279,70 @@ public final class EvictionManager {
279
279
  public void evictAllForId(final String id, @Nullable final EvictionCallback callback) {
280
280
  final CacheKeyStore.StoredKeys s = readStoredKeysPreferPersistent(id);
281
281
  if (s == null) {
282
- // fallback conservative attempt (source only)
283
- evictDiskForId(id, callback);
282
+ // fallback conservative attempt (disk only, then memory)
283
+ evictDiskForId(id, new EvictionCallback() {
284
+ @Override
285
+ public void onComplete(boolean diskSuccess, @Nullable Exception diskError) {
286
+ // After disk eviction, try memory eviction (will fail gracefully if no keys)
287
+ mainHandler.post(() -> {
288
+ evictMemoryForId(id, new EvictionCallback() {
289
+ @Override
290
+ public void onComplete(boolean memSuccess, @Nullable Exception memError) {
291
+ // Combine results: success only if both succeeded (or memory failed gracefully)
292
+ boolean combined = diskSuccess && memSuccess;
293
+ Exception combinedEx = diskError != null ? diskError : memError;
294
+ if (callback != null) {
295
+ callback.onComplete(combined, combinedEx);
296
+ }
297
+ }
298
+ });
299
+ });
300
+ }
301
+ });
284
302
  return;
285
303
  }
286
- final byte[] transformationBytes = getTransformationBytesFromStoredKeys(s);
287
- final byte[] optionsBytes = s.optionsKeyBytes;
288
- performEvictionTasks(
289
- s.sourceKey,
290
- s.signature,
291
- s.width,
292
- s.height,
293
- transformationBytes,
294
- s.decodedResourceClass,
295
- optionsBytes,
296
- s.engineKey,
297
- callback);
304
+
305
+ // Check if we have an enhanced disk cache
306
+ ModelSignatureDiskLruCacheWrapper dc;
307
+ synchronized (this) {
308
+ dc = diskCache;
309
+ }
310
+
311
+ if (s.sourceKey != null && s.signature != null) {
312
+ // Use efficient model/signature-based eviction
313
+ // Log.i(TAG, "EvictionManager evictAllForId using efficient disk cache eviction for: " + id);
314
+ evictDiskByModelAndSignature(s.sourceKey, s.signature, new EvictionCallback() {
315
+ @Override
316
+ public void onComplete(boolean diskSuccess, @Nullable Exception diskError) {
317
+ // After disk eviction, evict from memory
318
+ mainHandler.post(() -> {
319
+ boolean memSuccess = true;
320
+ Exception memError = null;
321
+
322
+ ModelSignatureMemoryCache mc;
323
+ synchronized (EvictionManager.this) {
324
+ mc = memoryCache;
325
+ }
326
+
327
+ if (mc != null && s.sourceKey != null && s.signature != null) {
328
+ try {
329
+ mc.removeByModelAndSignature(s.sourceKey, s.signature);
330
+ } catch (Exception e) {
331
+ memSuccess = false;
332
+ memError = e;
333
+ }
334
+ }
335
+
336
+ boolean combined = diskSuccess && memSuccess;
337
+ Exception combinedEx = diskError != null ? diskError : memError;
338
+
339
+ if (callback != null) {
340
+ callback.onComplete(combined, combinedEx);
341
+ }
342
+ });
343
+ }
344
+ });
345
+ }
298
346
  }
299
347
 
300
348
  /** Backwards-compatible no-callback variant */
@@ -308,61 +356,24 @@ public final class EvictionManager {
308
356
  public void evictDiskForId(final String id, @Nullable final EvictionCallback callback) {
309
357
  final CacheKeyStore.StoredKeys s = readStoredKeysPreferPersistent(id);
310
358
  if (s == null) {
311
- // fallback to deleting source by id-only ObjectKey
312
- final Key fallbackSource = new com.bumptech.glide.signature.ObjectKey(id);
313
- diskExecutor.execute(() -> {
314
- boolean success = true;
315
- Exception ex = null;
316
- DiskCache dc;
317
- synchronized (EvictionManager.this) {
318
- dc = diskCache;
319
- }
320
- if (dc != null) {
321
- try {
322
- dc.delete(fallbackSource);
323
- } catch (Exception e) {
324
- success = false;
325
- ex = e;
326
- }
327
- }
328
- if (callback != null) {
329
- final boolean finalSuccess = success;
330
- final Exception finalEx = ex;
331
- mainHandler.post(() -> callback.onComplete(finalSuccess, finalEx));
332
- }
333
- });
334
359
  return;
335
360
  }
336
361
 
337
362
  diskExecutor.execute(() -> {
338
363
  boolean success = true;
339
364
  Exception ex = null;
340
- DiskCache dc;
365
+ ModelSignatureDiskLruCacheWrapper dc;
341
366
  synchronized (EvictionManager.this) {
342
367
  dc = diskCache;
343
368
  }
344
369
  if (dc != null) {
370
+ // Delete source data using DataCacheKey (sourceKey + signature)
345
371
  try {
346
- dc.delete(s.sourceKey);
372
+ dc.removeByModelAndSignature(s.sourceKey, s.signature);
347
373
  } catch (Exception e) {
348
374
  success = false;
349
375
  ex = e;
350
376
  }
351
- try {
352
- Key resourceKey = new RecreatedResourceKey(
353
- s.sourceKey,
354
- s.signature,
355
- s.width,
356
- s.height,
357
- getTransformationBytesFromStoredKeys(s),
358
- s.decodedResourceClass,
359
- s.optionsKeyBytes);
360
- dc.delete(resourceKey);
361
- } catch (Exception e) {
362
- if (ex == null)
363
- ex = e;
364
- success = false;
365
- }
366
377
  }
367
378
  if (callback != null) {
368
379
  final boolean finalSuccess = success;
@@ -377,86 +388,23 @@ public final class EvictionManager {
377
388
  evictDiskForId(id, null);
378
389
  }
379
390
 
380
- /** Evict only source/raw bytes (disk). */
381
- public void evictSourceForId(final String id, @Nullable final EvictionCallback callback) {
382
- final CacheKeyStore.StoredKeys s = readStoredKeysPreferPersistent(id);
383
- final Key sourceKey = (s != null && s.sourceKey != null) ? s.sourceKey
384
- : new com.bumptech.glide.signature.ObjectKey(id);
385
- diskExecutor.execute(() -> {
386
- boolean success = true;
387
- Exception ex = null;
388
- DiskCache dc;
389
- synchronized (EvictionManager.this) {
390
- dc = diskCache;
391
- }
392
- if (dc != null) {
393
- try {
394
- dc.delete(sourceKey);
395
- } catch (Exception e) {
396
- success = false;
397
- ex = e;
398
- }
399
- }
400
- if (callback != null) {
401
- final boolean finalSuccess = success;
402
- final Exception finalEx = ex;
403
- mainHandler.post(() -> callback.onComplete(finalSuccess, finalEx));
404
- }
405
- });
406
- }
407
-
408
- /** Evict only the transformed resource (disk). */
409
- public void evictTransformedForId(final String id, @Nullable final EvictionCallback callback) {
410
- final CacheKeyStore.StoredKeys s = readStoredKeysPreferPersistent(id);
411
- if (s == null) {
412
- if (callback != null)
413
- mainHandler.post(() -> callback.onComplete(false, null));
414
- return;
415
- }
416
- final byte[] transformationBytes = getTransformationBytesFromStoredKeys(s);
417
- final byte[] optionsBytes = s.optionsKeyBytes;
418
-
419
- diskExecutor.execute(() -> {
420
- boolean success = true;
421
- Exception ex = null;
422
- DiskCache dc;
423
- synchronized (EvictionManager.this) {
424
- dc = diskCache;
425
- }
426
- if (dc != null) {
427
- try {
428
- Key resourceKey = new RecreatedResourceKey(s.sourceKey, s.signature, s.width, s.height, transformationBytes,
429
- s.decodedResourceClass, optionsBytes);
430
- dc.delete(resourceKey);
431
- } catch (Exception e) {
432
- success = false;
433
- ex = e;
434
- }
435
- }
436
- if (callback != null) {
437
- final boolean finalSuccess = success;
438
- final Exception finalEx = ex;
439
- mainHandler.post(() -> callback.onComplete(finalSuccess, finalEx));
440
- }
441
- });
442
- }
443
-
444
391
  /**
445
392
  * Evict only from memory (must be called on main thread). Callback invoked on
446
393
  * main thread.
447
394
  */
448
395
  @MainThread
449
396
  public void evictMemoryForId(final String id, @Nullable final EvictionCallback callback) {
450
- final CacheKeyStore.StoredKeys s = (persistentStore != null) ? persistentStore.get(id) : inMemoryKeyStore.get(id);
451
- final Key engineKey = (s != null) ? s.engineKey : null;
397
+ // Use the merged read to get engineKey from in-memory store (not persisted)
398
+ final CacheKeyStore.StoredKeys s = readStoredKeysPreferPersistent(id);
399
+ // final Key engineKey = (s != null) ? s.engineKey : null;
452
400
  boolean success = true;
453
401
  Exception ex = null;
454
- if (engineKey == null) {
402
+ if (s == null) {
455
403
  if (callback != null)
456
404
  callback.onComplete(false, null);
457
405
  return;
458
406
  }
459
- LruResourceCache mc;
407
+ ModelSignatureMemoryCache mc;
460
408
  synchronized (this) {
461
409
  mc = memoryCache;
462
410
  }
@@ -466,7 +414,8 @@ public final class EvictionManager {
466
414
  return;
467
415
  }
468
416
  try {
469
- mc.remove(engineKey);
417
+ mc.removeByModelAndSignature(s.sourceKey, s.signature );
418
+ clearAllActiveResources();
470
419
  } catch (Exception e) {
471
420
  success = false;
472
421
  ex = e;
@@ -494,18 +443,17 @@ public final class EvictionManager {
494
443
  */
495
444
  @MainThread
496
445
  public boolean isInMemoryCache(final String id) {
497
- final CacheKeyStore.StoredKeys s = (persistentStore != null) ? persistentStore.get(id) : inMemoryKeyStore.get(id);
498
- if (s == null || s.engineKey == null)
446
+ final CacheKeyStore.StoredKeys s = readStoredKeysPreferPersistent(id);
447
+ if (s == null || s.sourceKey == null || s.signature == null)
499
448
  return false;
500
- final Key engineKey = s.engineKey;
501
- LruResourceCache mc;
449
+ ModelSignatureMemoryCache mc;
502
450
  synchronized (this) {
503
451
  mc = memoryCache;
504
452
  }
505
453
  if (mc == null)
506
454
  return false;
507
455
 
508
- return mc.contains(engineKey);
456
+ return mc.containsByModelAndSignature(s.sourceKey, s.signature);
509
457
  }
510
458
 
511
459
  /**
@@ -518,36 +466,32 @@ public final class EvictionManager {
518
466
  boolean sourcePresent = false;
519
467
  boolean transformedPresent = false;
520
468
 
521
- final CacheKeyStore.StoredKeys s = (persistentStore != null) ? persistentStore.get(id) : inMemoryKeyStore.get(id);
522
-
523
- DiskCache dc;
469
+ final CacheKeyStore.StoredKeys s = readStoredKeysPreferPersistent(id);
470
+ ModelSignatureDiskLruCacheWrapper dc;
524
471
  synchronized (EvictionManager.this) {
525
472
  dc = diskCache;
526
473
  }
527
474
 
528
475
  if (dc == null) {
529
- mainHandler.post(() -> callback.onResult(false, false));
476
+ mainHandler.post(() -> callback.onResult(false));
530
477
  return;
531
478
  }
532
479
 
533
480
  try {
534
- if (s != null && s.sourceKey != null) {
535
- sourcePresent = dc.get(s.sourceKey) != null;
536
- byte[] transformationBytes = getTransformationBytesFromStoredKeys(s);
537
- Key resourceKey = new RecreatedResourceKey(s.sourceKey, s.signature, s.width, s.height, transformationBytes,
538
- s.decodedResourceClass, s.optionsKeyBytes);
539
- transformedPresent = dc.get(resourceKey) != null;
481
+ if (hasValidStoredKeys(s)) {
482
+ // Log.d(TAG, "EvictionManager isInDiskCacheAsync " + id + " " + s.sourceKey+ " " + s.sourceKey.getClass().getName()+ " " + s.signature);
483
+ sourcePresent = dc.containsByModelAndSignature(s.sourceKey, s.signature);
540
484
  } else {
541
485
  // fallback: check ObjectKey(id) as source
542
486
  Key fallback = new com.bumptech.glide.signature.ObjectKey(id);
543
487
  sourcePresent = dc.get(fallback) != null;
488
+ // Log.d(TAG, "EvictionManager fallback check with ObjectKey: " + sourcePresent);
544
489
  }
545
- } catch (Exception ignored) {
490
+ } catch (Exception e) {
491
+ Log.e(TAG, "EvictionManager isInDiskCacheAsync exception for " + id, e);
546
492
  }
547
-
548
- final boolean finalSource = sourcePresent;
549
- final boolean finalTransformed = transformedPresent;
550
- mainHandler.post(() -> callback.onResult(finalSource, finalTransformed));
493
+ final Boolean result = sourcePresent;
494
+ mainHandler.post(() -> callback.onResult(result));
551
495
  });
552
496
  }
553
497
 
@@ -556,180 +500,234 @@ public final class EvictionManager {
556
500
  * only.
557
501
  * Returns array [sourcePresent, transformedPresent].
558
502
  */
559
- public boolean[] isInDiskCacheBlocking(final String id) {
560
- final CacheKeyStore.StoredKeys s = (persistentStore != null) ? persistentStore.get(id) : inMemoryKeyStore.get(id);
561
- DiskCache dc;
503
+ public boolean isInDiskCacheBlocking(final String id) {
504
+ final CacheKeyStore.StoredKeys s = readStoredKeysPreferPersistent(id);
505
+ ModelSignatureDiskLruCacheWrapper dc;
562
506
  synchronized (EvictionManager.this) {
563
507
  dc = diskCache;
564
508
  }
565
509
  boolean sourcePresent = false;
566
510
  boolean transformedPresent = false;
567
511
  if (dc == null)
568
- return new boolean[] { false, false };
512
+ return false;
569
513
  try {
570
- if (s != null && s.sourceKey != null) {
571
- sourcePresent = dc.get(s.sourceKey) != null;
572
- Key resourceKey = new RecreatedResourceKey(s.sourceKey, s.signature, s.width, s.height,
573
- getTransformationBytesFromStoredKeys(s), s.decodedResourceClass, s.optionsKeyBytes);
574
- transformedPresent = dc.get(resourceKey) != null;
514
+ if (hasValidStoredKeys(s)) {
515
+ sourcePresent = dc.containsByModelAndSignature(s.sourceKey, s.signature);
575
516
  } else {
576
517
  Key fallback = new com.bumptech.glide.signature.ObjectKey(id);
577
518
  sourcePresent = dc.get(fallback) != null;
578
519
  }
579
520
  } catch (Exception ignored) {
580
521
  }
581
- return new boolean[] { sourcePresent, transformedPresent };
522
+ return sourcePresent;
582
523
  }
583
524
 
525
+ @Nullable
526
+ public CacheKeyStore.StoredKeys readStoredKeysPreferPersistent(String id) {
527
+ CacheKeyStore.StoredKeys s = null;
528
+
529
+ // First check in-memory
530
+ CacheKeyStore.StoredKeys inMem = inMemoryKeyStore.get(id);
531
+ CacheKeyStore.StoredKeys persistent = null;
532
+
533
+ if (persistentStore != null) {
534
+ persistent = persistentStore.get(id);
535
+ }
536
+
537
+ // Return whichever we have (prefer persistent)
538
+ return persistent != null ? persistent : inMem;
539
+ }
540
+
541
+ /**
542
+ * Helper to check if StoredKeys has all required components for disk cache operations.
543
+ * @param s the StoredKeys to check
544
+ * @return true if s has non-null sourceKey and signature
545
+ */
546
+ private static boolean hasValidStoredKeys(@Nullable CacheKeyStore.StoredKeys s) {
547
+ return s != null && s.sourceKey != null && s.signature != null;
548
+ }
549
+
550
+
551
+ // -----------------------
552
+ // Enhanced disk cache eviction methods using ModelSignatureDiskLruCacheWrapper
553
+ // -----------------------
554
+
584
555
  /**
585
- * Helper used by the eviction methods to coordinate deletes + memory removal.
556
+ * Evict all disk cache entries (both source and transformed) matching model and signature
557
+ *
558
+ * @param model The model (e.g., URI)
559
+ * @param signature The signature Key
560
+ * @param callback Optional callback invoked on main thread
586
561
  */
587
- private void performEvictionTasks(
588
- final Key sourceKey,
589
- final Key signature,
590
- final int width,
591
- final int height,
592
- final byte[] transformationKeyBytes,
593
- final Class<?> decodedResourceClass,
594
- final byte[] optionsKeyBytes,
595
- final Key engineKey,
562
+ public void evictDiskByModelAndSignature(
563
+ @Nullable final Object model,
564
+ @Nullable final Key signature,
596
565
  @Nullable final EvictionCallback callback) {
566
+
567
+ if (model == null || signature == null) {
568
+ if (callback != null) {
569
+ mainHandler.post(() -> callback.onComplete(false, new IllegalArgumentException("model and signature must not be null")));
570
+ }
571
+ return;
572
+ }
597
573
 
598
- int tasks = 0;
599
- if (sourceKey != null)
600
- tasks++;
601
- if (signature != null && sourceKey != null)
602
- tasks++;
603
- if (tasks == 0) {
604
- // only memory removal possible
605
- if (engineKey != null) {
606
- mainHandler.post(() -> {
607
- MemoryCache mc;
608
- synchronized (EvictionManager.this) {
609
- mc = memoryCache;
610
- }
611
- if (mc != null) {
612
- try {
613
- mc.remove(engineKey);
614
- } catch (Exception ignored) {
615
- }
616
- }
617
- if (callback != null)
618
- callback.onComplete(true, null);
619
- });
620
- } else {
621
- if (callback != null)
622
- mainHandler.post(() -> callback.onComplete(true, null));
574
+ diskExecutor.execute(() -> {
575
+ boolean success = false;
576
+ Exception ex = null;
577
+ int removed = 0;
578
+
579
+ ModelSignatureDiskLruCacheWrapper dc;
580
+ synchronized (EvictionManager.this) {
581
+ dc = diskCache;
582
+ }
583
+
584
+ removed = dc.removeByModelAndSignature(model, signature);
585
+ success = true;
586
+
587
+ if (callback != null) {
588
+ final boolean finalSuccess = success;
589
+ final Exception finalEx = ex;
590
+ mainHandler.post(() -> callback.onComplete(finalSuccess, finalEx));
591
+ }
592
+ });
593
+ }
594
+
595
+ /**
596
+ * Remove all disk cache entries that do NOT match the given signature.
597
+ * Useful for cache invalidation when changing signature versions.
598
+ *
599
+ * @param currentSignature The current/valid signature to keep
600
+ * @param callback Optional callback invoked on main thread
601
+ */
602
+ public void evictDiskExceptSignature(
603
+ @Nullable final Key currentSignature,
604
+ @Nullable final EvictionCallback callback) {
605
+
606
+ if (currentSignature == null) {
607
+ if (callback != null) {
608
+ mainHandler.post(() -> callback.onComplete(false, new IllegalArgumentException("currentSignature must not be null")));
623
609
  }
624
610
  return;
625
611
  }
626
612
 
627
- final AtomicInteger remaining = new AtomicInteger(tasks);
628
- final AtomicBoolean failed = new AtomicBoolean(false);
629
- final AtomicReference<Exception> firstException = new AtomicReference<>(null);
613
+ diskExecutor.execute(() -> {
614
+ boolean success = false;
615
+ Exception ex = null;
616
+ int removed = 0;
617
+
618
+ ModelSignatureDiskLruCacheWrapper dc;
619
+ synchronized (EvictionManager.this) {
620
+ dc = diskCache;
621
+ }
622
+
623
+ removed = dc.removeAllExceptSignature(currentSignature);
624
+ success = true;
625
+
626
+ if (callback != null) {
627
+ final boolean finalSuccess = success;
628
+ final Exception finalEx = ex;
629
+ mainHandler.post(() -> callback.onComplete(finalSuccess, finalEx));
630
+ }
631
+ });
632
+ }
630
633
 
631
- // delete source bytes
632
- if (sourceKey != null) {
633
- diskExecutor.execute(() -> {
634
- DiskCache dc;
635
- synchronized (EvictionManager.this) {
636
- dc = diskCache;
637
- }
638
- if (dc != null) {
639
- try {
640
- dc.delete(sourceKey);
641
- } catch (Exception e) {
642
- failed.set(true);
643
- firstException.compareAndSet(null, e);
644
- }
645
- }
646
- if (remaining.decrementAndGet() == 0) {
647
- mainHandler.post(() -> {
648
- if (engineKey != null) {
649
- MemoryCache mc;
650
- synchronized (EvictionManager.this) {
651
- mc = memoryCache;
652
- }
653
- if (mc != null) {
654
- try {
655
- mc.remove(engineKey);
656
- } catch (Exception ignored) {
657
- }
658
- }
659
- }
660
- if (callback != null)
661
- callback.onComplete(!failed.get(), firstException.get());
662
- });
663
- }
664
- });
634
+ /**
635
+ * Get disk cache statistics.
636
+ *
637
+ * @return Array: [indexSize, diskCacheSize] or null if not supported
638
+ */
639
+ public long[] getDiskCacheStats() {
640
+ ModelSignatureDiskLruCacheWrapper dc;
641
+ synchronized (this) {
642
+ dc = diskCache;
665
643
  }
644
+
645
+ return dc.getStats();
646
+ }
666
647
 
667
- // delete transformed resource
668
- if (signature != null && sourceKey != null) {
669
- diskExecutor.execute(() -> {
670
- DiskCache dc;
671
- synchronized (EvictionManager.this) {
672
- dc = diskCache;
673
- }
674
- if (dc != null) {
648
+ /**
649
+ * Initialize reflection fields/methods for ActiveResources access (once only)
650
+ */
651
+ private static void initActiveResourcesReflection() {
652
+ if (reflectionInitialized || reflectionFailed) {
653
+ return;
654
+ }
655
+ synchronized (EvictionManager.class) {
656
+ if (reflectionInitialized || reflectionFailed) {
657
+ return;
658
+ }
659
+ try {
660
+ // Get Glide.engine field
661
+ engineField = com.bumptech.glide.Glide.class.getDeclaredField("engine");
662
+ engineField.setAccessible(true);
663
+
664
+ // Get Engine.activeResources field
665
+ Class<?> engineClass = Class.forName("com.bumptech.glide.load.engine.Engine");
666
+ activeResourcesField = engineClass.getDeclaredField("activeResources");
667
+ activeResourcesField.setAccessible(true);
668
+
669
+ Class<?> activeResourcesClass = Class.forName("com.bumptech.glide.load.engine.ActiveResources");
670
+
671
+ // Get ActiveResources.activeEngineResources field (the actual map)
672
+ try {
673
+ activeEngineResourcesField = activeResourcesClass.getDeclaredField("activeEngineResources");
674
+ activeEngineResourcesField.setAccessible(true);
675
+ } catch (NoSuchFieldException e) {
676
+ // Try alternative field names in different Glide versions
675
677
  try {
676
- Key resourceKey = new RecreatedResourceKey(sourceKey, signature, width, height, transformationKeyBytes,
677
- decodedResourceClass, optionsKeyBytes);
678
- dc.delete(resourceKey);
679
- } catch (Exception e) {
680
- failed.set(true);
681
- firstException.compareAndSet(null, e);
678
+ activeEngineResourcesField = activeResourcesClass.getDeclaredField("activeResources");
679
+ activeEngineResourcesField.setAccessible(true);
680
+ } catch (NoSuchFieldException e2) {
681
+ Log.w(TAG, "Could not find activeEngineResources map field");
682
682
  }
683
683
  }
684
- if (remaining.decrementAndGet() == 0) {
685
- mainHandler.post(() -> {
686
- if (engineKey != null) {
687
- MemoryCache mc;
688
- synchronized (EvictionManager.this) {
689
- mc = memoryCache;
690
- }
691
- if (mc != null) {
692
- try {
693
- mc.remove(engineKey);
694
- } catch (Exception ignored) {
695
- }
696
- }
697
- }
698
- if (callback != null)
699
- callback.onComplete(!failed.get(), firstException.get());
700
- });
701
- }
702
- });
703
- }
704
- }
705
-
706
- @Nullable
707
- private CacheKeyStore.StoredKeys readStoredKeysPreferPersistent(String id) {
708
- if (persistentStore != null) {
709
- CacheKeyStore.StoredKeys s = persistentStore.get(id);
710
- if (s != null) {
711
- // keep mirrored in-memory copy for engineKey augmentation
712
- inMemoryKeyStore.put(id, s);
713
- return s;
684
+
685
+ reflectionInitialized = true;
686
+ // Log.i(TAG, "ActiveResources reflection initialized successfully");
687
+ } catch (Throwable t) {
688
+ reflectionFailed = true;
689
+ Log.w(TAG, "Failed to initialize ActiveResources reflection (may not exist in this Glide version)", t);
714
690
  }
715
691
  }
716
- return inMemoryKeyStore.get(id);
717
692
  }
718
693
 
719
- @Nullable
720
- private static byte[] getTransformationBytesFromStoredKeys(CacheKeyStore.StoredKeys s) {
721
- if (s == null)
722
- return null;
723
- if (s.transformationKeyBytes != null)
724
- return s.transformationKeyBytes;
725
- if (s.transformation != null) {
726
- try {
727
- RecordingDigest rd = new RecordingDigest();
728
- s.transformation.updateDiskCacheKey(rd);
729
- return rd.digest();
730
- } catch (Exception ignored) {
694
+ /**
695
+ * Clear all entries from Glide's ActiveResources cache.
696
+ * Must be called on main thread.
697
+ *
698
+ * @return true if successfully cleared, false if reflection failed
699
+ */
700
+ @MainThread
701
+ public boolean clearAllActiveResources() {
702
+ try {
703
+ initActiveResourcesReflection();
704
+
705
+ if (reflectionFailed) {
706
+ return false;
707
+ }
708
+
709
+ Object engine = engineField.get(glideInstance);
710
+
711
+ if (engine != null) {
712
+ Object activeResources = activeResourcesField.get(engine);
713
+
714
+ if (activeResources != null && activeEngineResourcesField != null) {
715
+ // Get the map and clear it
716
+ Object mapObject = activeEngineResourcesField.get(activeResources);
717
+
718
+ if (mapObject instanceof java.util.Map) {
719
+ java.util.Map<?, ?> map = (java.util.Map<?, ?>) mapObject;
720
+ int size = map.size();
721
+ map.clear();
722
+ // Log.i(TAG, "Cleared " + size + " entries from ActiveResources map");
723
+ return true;
724
+ }
725
+ }
731
726
  }
727
+ } catch (Throwable t) {
728
+ Log.w(TAG, "Failed to clear ActiveResources map", t);
732
729
  }
733
- return null;
730
+
731
+ return false;
734
732
  }
735
733
  }