This repository has been archived by the owner on May 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
ImageCache.cs
142 lines (118 loc) · 3.32 KB
/
ImageCache.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Android.Runtime;
namespace Xamarin.Forms.Platform.Android
{
/// <summary>
/// I setup the access to all the cache elements to be async because
/// if I didn't then it was locking up the GC and freezing the entire app
/// </summary>
class ImageCache
{
readonly FormsLruCache _lruCache;
readonly ConcurrentDictionary<string, SemaphoreSlim> _waiting;
public ImageCache() : base()
{
_waiting = new ConcurrentDictionary<string, SemaphoreSlim>();
_lruCache = new FormsLruCache();
}
void Put(string key, TimeSpan cacheValidity, global::Android.Graphics.Bitmap cacheObject)
{
_lruCache.Put(key, new CacheEntry() { TimeToLive = DateTimeOffset.UtcNow.Add(cacheValidity), Data = cacheObject });
}
public Task<Java.Lang.Object> GetAsync(string cacheKey, TimeSpan cacheValidity, Func<Task<Java.Lang.Object>> createMethod)
{
return Task.Run(async () =>
{
SemaphoreSlim semaphoreSlim = null;
Java.Lang.Object innerCacheObject = null;
try
{
semaphoreSlim = _waiting.GetOrAdd(cacheKey, (key) => new SemaphoreSlim(1, 1));
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
var cacheEntry = _lruCache.Get(cacheKey) as CacheEntry;
if (cacheEntry?.TimeToLive < DateTimeOffset.UtcNow || cacheEntry?.IsDisposed == true)
cacheEntry = null;
if (cacheEntry == null && createMethod != null)
{
innerCacheObject = await createMethod().ConfigureAwait(false);
if (innerCacheObject is global::Android.Graphics.Bitmap bm)
Put(cacheKey, cacheValidity, bm);
else if (innerCacheObject is global::Android.Graphics.Drawables.BitmapDrawable bitmap)
Put(cacheKey, cacheValidity, bitmap.Bitmap);
}
else
{
innerCacheObject = cacheEntry.Data;
}
}
catch
{
//just in case
}
finally
{
semaphoreSlim?.Release();
}
return innerCacheObject;
});
}
internal class CacheEntry : Java.Lang.Object
{
bool _isDisposed;
public CacheEntry(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
{
}
public CacheEntry()
{
}
public bool IsDisposed
{
get
{
if (Data == null)
return true;
if (this.IsDisposed() || Data.IsDisposed())
return true;
return false;
}
}
public DateTimeOffset TimeToLive { get; set; }
public global::Android.Graphics.Bitmap Data { get; set; }
protected override void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
Data = null;
}
base.Dispose(disposing);
}
}
public class FormsLruCache : global::Android.Util.LruCache
{
static int GetCacheSize()
{
// https://developer.android.com/topic/performance/graphics/cache-bitmap
int cacheSize = 4 * 1024 * 1024;
var maxMemory = Java.Lang.Runtime.GetRuntime()?.MaxMemory();
if (maxMemory != null)
{
cacheSize = (int)(maxMemory.Value / 8);
}
return cacheSize;
}
public FormsLruCache() : base(GetCacheSize())
{
}
protected override int SizeOf(Java.Lang.Object key, Java.Lang.Object value)
{
if (value != null && value is global::Android.Graphics.Bitmap bitmap)
return bitmap.ByteCount / 1024;
return base.SizeOf(key, value);
}
}
}
}