├── .gitignore
├── TangoAndCache
├── Sample
│ ├── Resources
│ │ ├── drawable
│ │ │ └── Icon.png
│ │ ├── values
│ │ │ └── Strings.xml
│ │ ├── layout
│ │ │ └── Main.axml
│ │ ├── AboutResources.txt
│ │ └── Resource.designer.cs
│ ├── Properties
│ │ ├── AndroidManifest.xml
│ │ └── AssemblyInfo.cs
│ ├── Assets
│ │ └── AboutAssets.txt
│ ├── Sample.csproj
│ └── MainActivity.cs
├── TangoAndCache
│ ├── Resources
│ │ ├── values
│ │ │ └── Strings.xml
│ │ ├── Resource.designer.cs
│ │ └── AboutResources.txt
│ ├── Android.Util
│ │ └── Utils.cs
│ ├── Android.UI.Drawables
│ │ ├── BitmapDrawableExtensions.cs
│ │ └── SelfDisposingBitmapDrawable.cs
│ ├── Android.Widget
│ │ └── ManagedImageView.cs
│ ├── Collections
│ │ ├── ByteBoundStrongLruCache.cs
│ │ └── StrongLruCache.cs
│ ├── TangoAndCache.csproj
│ └── Android.Collections
│ │ ├── BitmapDrawableCache.cs
│ │ └── ReuseBitmapDrawableCache.cs
├── TangoAndCache.userprefs
└── TangoAndCache.sln
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Resources/drawable/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdio/tangoandcache/HEAD/TangoAndCache/Sample/Resources/drawable/Icon.png
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Resources/values/Strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Tango And Cache
4 |
5 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Resources/values/Strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TangoAndCache
4 |
5 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Properties/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Android.Util/Utils.cs:
--------------------------------------------------------------------------------
1 | //
2 | // BitmapDrawableExtensions.cs
3 | //
4 | // Author:
5 | // Brett Duncavage
6 | //
7 | // Copyright 2013 Rdio, Inc.
8 | //
9 |
10 | using System;
11 | using Android.OS;
12 |
13 | namespace Rdio.TangoAndCache.Android.Util
14 | {
15 | public static class Utils
16 | {
17 | public static bool HasHoneycomb {
18 | get {
19 | return Build.VERSION.SdkInt >= BuildVersionCodes.Honeycomb;
20 | }
21 | }
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Assets/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories) and given a Build Action of "AndroidAsset".
3 |
4 | These files will be deployed with your package and will be accessible using Android's
5 | AssetManager, like this:
6 |
7 | public class ReadAsset : Activity
8 | {
9 | protected override void OnCreate (Bundle bundle)
10 | {
11 | base.OnCreate (bundle);
12 |
13 | InputStream input = Assets.Open ("my_asset.txt");
14 | }
15 | }
16 |
17 | Additionally, some Android functions will automatically load asset files:
18 |
19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
20 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Resources/layout/Main.axml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
17 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Android.UI.Drawables/BitmapDrawableExtensions.cs:
--------------------------------------------------------------------------------
1 | //
2 | // BitmapDrawableExtensions.cs
3 | //
4 | // Author:
5 | // Brett Duncavage
6 | //
7 | // Copyright 2013 Rdio, Inc.
8 | //
9 |
10 | using System;
11 | using Android.Graphics.Drawables;
12 |
13 | namespace Rdio.TangoAndCache.Android.UI.Drawables
14 | {
15 | public static class BitmapDrawableExtensions
16 | {
17 | public static void TypeCheckedSetIsDisplayed(this BitmapDrawable drawable, bool displayed)
18 | {
19 | if (drawable is SelfDisposingBitmapDrawable) {
20 | ((SelfDisposingBitmapDrawable)drawable).SetIsDisplayed(displayed);
21 | }
22 | }
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache.userprefs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using Android.App;
4 |
5 | // Information about this assembly is defined by the following attributes.
6 | // Change them to the values specific to your project.
7 |
8 | [assembly: AssemblyTitle("Sample")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Rdio, Inc.")]
12 | [assembly: AssemblyProduct("")]
13 | [assembly: AssemblyCopyright("Rdio, Inc.")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
18 | // The form "{Major}.{Minor}.*" will automatically update the build and revision,
19 | // and "{Major}.{Minor}.{Build}.*" will update just the revision.
20 |
21 | [assembly: AssemblyVersion("1.0.0")]
22 |
23 | // The following attributes are used to specify the signing key for the assembly,
24 | // if desired. See the Mono documentation for more information about signing.
25 |
26 | //[assembly: AssemblyDelaySign(false)]
27 | //[assembly: AssemblyKeyFile("")]
28 |
29 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TangoAndCache", "TangoAndCache\TangoAndCache.csproj", "{FB43CF84-E9A5-44EB-AF90-3C3544E1A251}"
5 | EndProject
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{F2CF0BC1-9DC9-4AB8-9A87-8487E072D6C8}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {F2CF0BC1-9DC9-4AB8-9A87-8487E072D6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {F2CF0BC1-9DC9-4AB8-9A87-8487E072D6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {F2CF0BC1-9DC9-4AB8-9A87-8487E072D6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {F2CF0BC1-9DC9-4AB8-9A87-8487E072D6C8}.Release|Any CPU.Build.0 = Release|Any CPU
18 | {FB43CF84-E9A5-44EB-AF90-3C3544E1A251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {FB43CF84-E9A5-44EB-AF90-3C3544E1A251}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {FB43CF84-E9A5-44EB-AF90-3C3544E1A251}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {FB43CF84-E9A5-44EB-AF90-3C3544E1A251}.Release|Any CPU.Build.0 = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(MonoDevelopProperties) = preSolution
24 | StartupItem = Sample\Sample.csproj
25 | EndGlobalSection
26 | EndGlobal
27 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Resources/Resource.designer.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable 1591
2 | // ------------------------------------------------------------------------------
3 | //
4 | // This code was generated by a tool.
5 | // Mono Runtime Version: 4.0.30319.17020
6 | //
7 | // Changes to this file may cause incorrect behavior and will be lost if
8 | // the code is regenerated.
9 | //
10 | // ------------------------------------------------------------------------------
11 |
12 | [assembly: Android.Runtime.ResourceDesignerAttribute("TangoAndCache.Resource", IsApplication=false)]
13 |
14 | namespace TangoAndCache
15 | {
16 |
17 |
18 | [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
19 | public partial class Resource
20 | {
21 |
22 | static Resource()
23 | {
24 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
25 | }
26 |
27 | public partial class Attribute
28 | {
29 |
30 | static Attribute()
31 | {
32 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
33 | }
34 |
35 | private Attribute()
36 | {
37 | }
38 | }
39 |
40 | public partial class String
41 | {
42 |
43 | // aapt resource value: 0x7f020000
44 | public static int library_name = 2130837504;
45 |
46 | static String()
47 | {
48 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
49 | }
50 |
51 | private String()
52 | {
53 | }
54 | }
55 | }
56 | }
57 | #pragma warning restore 1591
58 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Resources/AboutResources.txt:
--------------------------------------------------------------------------------
1 | Images, layout descriptions, binary blobs and string dictionaries can be included
2 | in your application as resource files. Various Android APIs are designed to
3 | operate on the resource IDs instead of dealing with images, strings or binary blobs
4 | directly.
5 |
6 | For example, a sample Android app that contains a user interface layout (main.axml),
7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
8 | would keep its resources in the "Resources" directory of the application:
9 |
10 | Resources/
11 | drawable/
12 | icon.png
13 |
14 | layout/
15 | main.axml
16 |
17 | values/
18 | strings.xml
19 |
20 | In order to get the build system to recognize Android resources, set the build action to
21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but
22 | instead operate on resource IDs. When you compile an Android application that uses resources,
23 | the build system will package the resources for distribution and generate a class called "R"
24 | (this is an Android convention) that contains the tokens for each one of the resources
25 | included. For example, for the above Resources layout, this is what the R class would expose:
26 |
27 | public class R {
28 | public class drawable {
29 | public const int icon = 0x123;
30 | }
31 |
32 | public class layout {
33 | public const int main = 0x456;
34 | }
35 |
36 | public class strings {
37 | public const int first_string = 0xabc;
38 | public const int second_string = 0xbcd;
39 | }
40 | }
41 |
42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first
44 | string in the dictionary file values/strings.xml.
45 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Resources/AboutResources.txt:
--------------------------------------------------------------------------------
1 | Images, layout descriptions, binary blobs and string dictionaries can be included
2 | in your application as resource files. Various Android APIs are designed to
3 | operate on the resource IDs instead of dealing with images, strings or binary blobs
4 | directly.
5 |
6 | For example, a sample Android app that contains a user interface layout (main.axml),
7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
8 | would keep its resources in the "Resources" directory of the application:
9 |
10 | Resources/
11 | drawable/
12 | icon.png
13 |
14 | layout/
15 | main.axml
16 |
17 | values/
18 | strings.xml
19 |
20 | In order to get the build system to recognize Android resources, set the build action to
21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but
22 | instead operate on resource IDs. When you compile an Android application that uses resources,
23 | the build system will package the resources for distribution and generate a class called "R"
24 | (this is an Android convention) that contains the tokens for each one of the resources
25 | included. For example, for the above Resources layout, this is what the R class would expose:
26 |
27 | public class R {
28 | public class drawable {
29 | public const int icon = 0x123;
30 | }
31 |
32 | public class layout {
33 | public const int main = 0x456;
34 | }
35 |
36 | public class strings {
37 | public const int first_string = 0xabc;
38 | public const int second_string = 0xbcd;
39 | }
40 | }
41 |
42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first
44 | string in the dictionary file values/strings.xml.
45 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Android.Widget/ManagedImageView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Android.Widget;
3 | using Android.Content;
4 | using Android.Util;
5 | using Rdio.TangoAndCache.Android.UI.Drawables;
6 | using Android.Graphics.Drawables;
7 |
8 | namespace Rdio.TangoAndCache.Android.Widget
9 | {
10 | public class ManagedImageView : ImageView
11 | {
12 | public ManagedImageView(Context context)
13 | : base(context)
14 | {
15 | }
16 |
17 | public ManagedImageView(Context context, IAttributeSet attrs)
18 | : base(context, attrs)
19 | {
20 | }
21 |
22 | public ManagedImageView(Context context, IAttributeSet attrs, int defStyle)
23 | : base(context, attrs, defStyle)
24 | {
25 | }
26 |
27 | public override void SetImageDrawable(Drawable drawable)
28 | {
29 | var previous = Drawable;
30 |
31 | base.SetImageDrawable(drawable);
32 |
33 | UpdateDrawableDisplayedState(drawable, true);
34 | UpdateDrawableDisplayedState(previous, false);
35 | }
36 |
37 | public override void SetImageResource(int resId)
38 | {
39 | var previous = Drawable;
40 | // Ultimately calls SetImageDrawable, where the state will be updated.
41 | base.SetImageResource(resId);
42 | UpdateDrawableDisplayedState(previous, false);
43 | }
44 |
45 | public override void SetImageURI(global::Android.Net.Uri uri)
46 | {
47 | var previous = Drawable;
48 | // Ultimately calls SetImageDrawable, where the state will be updated.
49 | base.SetImageURI(uri);
50 | UpdateDrawableDisplayedState(previous, false);
51 | }
52 |
53 | private void UpdateDrawableDisplayedState(Drawable drawable, bool isDisplayed)
54 | {
55 | if (drawable is SelfDisposingBitmapDrawable) {
56 | ((SelfDisposingBitmapDrawable)drawable).SetIsDisplayed(isDisplayed);
57 | } else if (drawable is LayerDrawable) {
58 | var layerDrawable = (LayerDrawable)drawable;
59 | for (var i = 0; i < layerDrawable.NumberOfLayers; i++) {
60 | UpdateDrawableDisplayedState(layerDrawable.GetDrawable(i), isDisplayed);
61 | }
62 | }
63 | }
64 |
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Resources/Resource.designer.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable 1591
2 | // ------------------------------------------------------------------------------
3 | //
4 | // This code was generated by a tool.
5 | // Mono Runtime Version: 4.0.30319.17020
6 | //
7 | // Changes to this file may cause incorrect behavior and will be lost if
8 | // the code is regenerated.
9 | //
10 | // ------------------------------------------------------------------------------
11 |
12 | [assembly: Android.Runtime.ResourceDesignerAttribute("Sample.Resource", IsApplication=true)]
13 |
14 | namespace Sample
15 | {
16 |
17 |
18 | [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
19 | public partial class Resource
20 | {
21 |
22 | static Resource()
23 | {
24 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
25 | }
26 |
27 | public static void UpdateIdValues()
28 | {
29 | global::TangoAndCache.Resource.String.library_name = global::Sample.Resource.String.library_name;
30 | }
31 |
32 | public partial class Attribute
33 | {
34 |
35 | static Attribute()
36 | {
37 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
38 | }
39 |
40 | private Attribute()
41 | {
42 | }
43 | }
44 |
45 | public partial class Drawable
46 | {
47 |
48 | // aapt resource value: 0x7f020000
49 | public const int Icon = 2130837504;
50 |
51 | static Drawable()
52 | {
53 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
54 | }
55 |
56 | private Drawable()
57 | {
58 | }
59 | }
60 |
61 | public partial class Id
62 | {
63 |
64 | // aapt resource value: 0x7f050001
65 | public const int grid = 2131034113;
66 |
67 | // aapt resource value: 0x7f050000
68 | public const int progress = 2131034112;
69 |
70 | static Id()
71 | {
72 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
73 | }
74 |
75 | private Id()
76 | {
77 | }
78 | }
79 |
80 | public partial class Layout
81 | {
82 |
83 | // aapt resource value: 0x7f030000
84 | public const int Main = 2130903040;
85 |
86 | static Layout()
87 | {
88 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
89 | }
90 |
91 | private Layout()
92 | {
93 | }
94 | }
95 |
96 | public partial class String
97 | {
98 |
99 | // aapt resource value: 0x7f040001
100 | public const int app_name = 2130968577;
101 |
102 | // aapt resource value: 0x7f040000
103 | public const int library_name = 2130968576;
104 |
105 | static String()
106 | {
107 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
108 | }
109 |
110 | private String()
111 | {
112 | }
113 | }
114 | }
115 | }
116 | #pragma warning restore 1591
117 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Collections/ByteBoundStrongLruCache.cs:
--------------------------------------------------------------------------------
1 | //
2 | // ByteBoundStringLruCache.cs
3 | //
4 | // Author:
5 | // Brett Duncavage
6 | //
7 | // Copyright 2014 Rdio, Inc.
8 | //
9 | using System;
10 |
11 | namespace Rdio.TangoAndCache.Collections
12 | {
13 | public interface IByteSizeAware
14 | {
15 | long SizeInBytes { get; }
16 | }
17 |
18 | public class ByteBoundStrongLruCache : StrongLruCache where TValue : IByteSizeAware
19 | {
20 | private const string TAG = "ByteBoundStrongLruCache";
21 |
22 | private readonly object monitor = new object();
23 |
24 | private long high_watermark;
25 | private long low_watermark;
26 | private long current_cache_size;
27 | private bool has_exceeded_high_watermark;
28 |
29 | public long CacheSizeInBytes {
30 | get { lock (monitor) { return current_cache_size; } }
31 | }
32 |
33 | public ByteBoundStrongLruCache(long highWatermark, long lowWatermark)
34 | {
35 | high_watermark = Math.Max(0, highWatermark);
36 | low_watermark = Math.Max(0, lowWatermark);
37 | if (high_watermark == 0) {
38 | throw new ArgumentException("highWatermark must be > 0");
39 | }
40 | if (low_watermark == 0) {
41 | throw new ArgumentException("lowWatermark must be > 0");
42 | }
43 | if (high_watermark < low_watermark) {
44 | high_watermark = low_watermark;
45 | }
46 | }
47 |
48 | protected override void OnEntryAdded(TKey key, TValue value)
49 | {
50 | lock (monitor) {
51 | current_cache_size += value.SizeInBytes;
52 | }
53 | base.OnEntryAdded(key, value);
54 | }
55 |
56 | protected override void OnEntryRemoved(bool evicted, TKey key, TValue oldValue, TValue newValue)
57 | {
58 | base.OnEntryRemoved(evicted, key, oldValue, newValue);
59 | // We handle updating the size due to evictions in OnEntryEvicted.
60 | if (!evicted) {
61 | lock (monitor) {
62 | current_cache_size -= oldValue.SizeInBytes;
63 | }
64 | }
65 | }
66 |
67 | protected override void OnWillEvictEntry(TKey key, TValue value)
68 | {
69 | // This method is called inside of a lock.
70 | current_cache_size -= value.SizeInBytes;
71 | }
72 |
73 | protected override bool CheckEvictionRequired()
74 | {
75 | lock (monitor) {
76 | if (current_cache_size > high_watermark) {
77 | has_exceeded_high_watermark = true;
78 | return true;
79 | } else if (has_exceeded_high_watermark && current_cache_size > low_watermark) {
80 | return true;
81 | }
82 | has_exceeded_high_watermark = false;
83 | }
84 | return false;
85 | }
86 | }
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
7 | {F2CF0BC1-9DC9-4AB8-9A87-8487E072D6C8}
8 | Library
9 | Sample
10 | Assets
11 | Resources
12 | Resource
13 | Resources\Resource.designer.cs
14 | True
15 | True
16 | Sample
17 | v4.4
18 | Properties\AndroidManifest.xml
19 |
20 |
21 | true
22 | full
23 | false
24 | bin\Debug
25 | DEBUG;
26 | prompt
27 | 4
28 | None
29 | false
30 |
31 |
32 | full
33 | true
34 | bin\Release
35 | prompt
36 | 4
37 | false
38 | false
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 | {FB43CF84-E9A5-44EB-AF90-3C3544E1A251}
65 | TangoAndCache
66 |
67 |
68 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/TangoAndCache.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
7 | {FB43CF84-E9A5-44EB-AF90-3C3544E1A251}
8 | Library
9 | TangoAndCache
10 | Assets
11 | Resources
12 | Resource
13 | Resources\Resource.designer.cs
14 | True
15 | TangoAndCache
16 | v4.4
17 |
18 |
19 | true
20 | full
21 | false
22 | bin\Debug
23 | DEBUG;
24 | prompt
25 | 4
26 | None
27 | false
28 |
29 |
30 | full
31 | true
32 | bin\Release
33 | prompt
34 | 4
35 | false
36 | false
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Tango And Cache
2 | =======
3 |
4 | C# in-memory Bitmap cache for Android
5 |
6 | Details
7 | =======
8 |
9 | TangoAndCache is an in-memory cache for bitmaps. At a basic level it is a byte-bound LRU cache, evicting the oldest entries as new ones
10 | push the cache over the high watermark. It facilitates Bitmap reuse to reduce the number of allocations made and monitors the count of
11 | bytes evicted from the cache in order to force GC cycles to prevent OOMs on the native side.
12 |
13 | TangoAndCache is not an image downloader or disk cache (maybe in the future, that will be the Tango part).
14 |
15 | Usage
16 | =====
17 |
18 | Usage of TangoAndCache is very straightforward. You simply create an instance of the cache, then add entries and retrieve them.
19 |
20 | Example:
21 | ```csharp
22 | // Create an instance.
23 | // The high, low and gc threshold values are up to you to provide. Here is a rundown of the values:
24 | // The GC threshold is the amount of bytes that have been evicted from the cache
25 | // that will trigger a GC.Collect. For example if set to 2mb, a GC will be performed
26 | // each time the cache has evicted a total of 2mb.
27 | // This means that we can have highWatermark + gcThreshold amount of memory in use
28 | // before a GC is forced, so we should ensure that the threshold value + hightWatermark
29 | // is less than our total memory.
30 | // In our case, the highWatermark is 1/3 of max memory, so using the same value for the
31 | // GC threshold means we can have up to 2/3 of max memory in use before kicking the GC.
32 | image_cache = new ReuseBitmapDrawableCache(highWatermark, lowWatermark, gcThreshold);
33 |
34 | // OR
35 |
36 | // If you need to support < API 11 (Honeycomb) create an instance of the BitmapDrawableCache.
37 | if (Utils.HasHoneycomb) {
38 | image_cache = new ReuseBitmapDrawableCache(highWatermark, lowWatermark, gcThreshold);
39 | } else {
40 | image_cache = new BitmapDrawableCache(highWatermark, lowWatermark, gcThreshold);
41 | }
42 |
43 | // Add images to the cache (in your downloader or wherever you're getting images)
44 | image_cache.Add(new Uri(uri), new SelfDisposingBitmapDrawable(Resources, bitmap));
45 |
46 | // Get images out of the cache
47 | var drawable = image_cache[key];
48 | ```
49 |
50 | You can create the cache instance with dumpDebug = true which causes the cache to periodically log stats about the cache. Stats include:
51 | current size in bytes, current count, number of cache hits, number of cache misses, etc.
52 |
53 | Choosing high and low watermarks is up to you as the values may vary depending on your application domain and how much room you want to
54 | give the cache. You can also experiment with the gc threshold value to control how often forced GCs are performed.
55 |
56 | Sample
57 | ======
58 |
59 | The TangoAndCache solution contains a sample Android application that demonstrates trivial usage of the cache.
60 |
61 | Future Improvements
62 | ==================
63 |
64 | Making TangoAndCache an image downloader and disk LRU cache. Adding that functionality would make TangoAndCache a fully featured image providing
65 | solution for C# Android apps.
66 |
67 | Better debug stats and logging may be useful and desireable.
68 |
69 | License
70 | =======
71 |
72 | The MIT License (MIT)
73 |
74 | Copyright (c)
75 |
76 | Permission is hereby granted, free of charge, to any person obtaining a copy
77 | of this software and associated documentation files (the "Software"), to deal
78 | in the Software without restriction, including without limitation the rights
79 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
80 | copies of the Software, and to permit persons to whom the Software is
81 | furnished to do so, subject to the following conditions:
82 |
83 | The above copyright notice and this permission notice shall be included in
84 | all copies or substantial portions of the Software.
85 |
86 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
88 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
89 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
90 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
91 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
92 | THE SOFTWARE.
93 |
--------------------------------------------------------------------------------
/TangoAndCache/Sample/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using Android.App;
4 | using Android.Content;
5 | using Android.Runtime;
6 | using Android.Views;
7 | using Android.Widget;
8 | using Android.OS;
9 | using System.Collections.Generic;
10 | using Rdio.TangoAndCache.Android.Collections;
11 | using Android.Graphics;
12 | using Rdio.TangoAndCache.Android.UI.Drawables;
13 | using Rdio.TangoAndCache.Android.Widget;
14 | using System.Threading;
15 | using System.Net;
16 |
17 | namespace Sample
18 | {
19 | [Activity(Label = "@string/app_name", MainLauncher = true, Icon = "@drawable/icon")]
20 | public class MainActivity : Activity
21 | {
22 | GridView grid_view;
23 | ReuseBitmapDrawableCache image_cache;
24 | readonly Handler main_thread_handler = new Handler();
25 |
26 | readonly List images_to_fetch = new List{
27 | "http://rdio3img-a.akamaihd.net/album/8/c/7/00000000004987c8/1/square-400.jpg",
28 | "http://img00.cdn2-rdio.com/album/4/5/9/000000000021f954/4/square-400.jpg",
29 | "http://img02.cdn2-rdio.com/album/f/2/a/000000000049aa2f/1/square-400.jpg",
30 | "http://img00.cdn2-rdio.com/album/4/f/3/00000000004a33f4/1/square-400.jpg",
31 | "http://rdio1img-a.akamaihd.net/album/0/e/c/0000000000498ce0/3/square-400.jpg",
32 | "http://img02.cdn2-rdio.com/album/2/7/e/00000000004aee72/1/square-400.jpg",
33 | "http://img00.cdn2-rdio.com/album/3/c/0/00000000004b20c3/1/square-400.jpg",
34 | "http://img02.cdn2-rdio.com/album/5/c/8/000000000047a8c5/3/square-400.jpg",
35 | "http://rdio1img-a.akamaihd.net/album/f/7/e/0000000000487e7f/3/square-400.jpg",
36 | "http://rdio3img-a.akamaihd.net/album/2/c/9/000000000037a9c2/1/square-400.jpg",
37 | "http://img00.cdn2-rdio.com/album/f/f/a/0000000000479aff/5/square-400.jpg",
38 | "http://img02.cdn2-rdio.com/album/9/d/e/0000000000493ed9/1/square-400.jpg",
39 | "http://rdio3img-a.akamaihd.net/album/1/4/3/0000000000010341/square-400.jpg",
40 | "http://rdio1img-a.akamaihd.net/album/6/f/1/00000000000911f6/4/square-400.jpg",
41 | "http://img00.cdn2-rdio.com/album/e/6/4/00000000004bb46e/1/square-400.jpg"
42 | };
43 |
44 | protected override void OnCreate(Bundle bundle)
45 | {
46 | base.OnCreate(bundle);
47 |
48 | var highWatermark = Java.Lang.Runtime.GetRuntime().MaxMemory() / 3;
49 | var lowWatermark = highWatermark / 2;
50 |
51 | // The GC threshold is the amount of bytes that have been evicted from the cache
52 | // that will trigger a GC.Collect. For example if set to 2mb, a GC will be performed
53 | // each time the cache has evicted a total of 2mb.
54 | // This means that we can have highWatermark + gcThreshold amount of memory in use
55 | // before a GC is forced, so we should ensure that the threshold value + hightWatermark
56 | // is less than our total memory.
57 | // In our case, the highWatermark is 1/3 of max memory, so using the same value for the
58 | // GC threshold means we can have up to 2/3 of max memory in use before kicking the GC.
59 | var gcThreshold = highWatermark;
60 |
61 | image_cache = new ReuseBitmapDrawableCache(highWatermark, lowWatermark, gcThreshold);
62 |
63 | // Set our view from the "main" layout resource
64 | SetContentView(Resource.Layout.Main);
65 |
66 | ThreadPool.QueueUserWorkItem(DownloadImages);
67 |
68 | }
69 |
70 | private void DownloadImages(object state)
71 | {
72 | var client = new WebClient();
73 | foreach (var uri in images_to_fetch) {
74 | var bytes = client.DownloadData(uri);
75 | var bitmap = BitmapFactory.DecodeByteArray(bytes, 0, bytes.Length);
76 | // ReuseBitmapDrawableCache is threadsafe
77 | image_cache.Add(new Uri(uri), new SelfDisposingBitmapDrawable(Resources, bitmap));
78 | }
79 | main_thread_handler.Post(() => {
80 | FindViewById(Resource.Id.progress).Visibility = ViewStates.Gone;
81 | grid_view = FindViewById(Resource.Id.grid);
82 | grid_view.Adapter = new ImageAdapter(this);
83 | });
84 | }
85 |
86 | private class ImageAdapter : BaseAdapter
87 | {
88 | MainActivity activity;
89 | const int count_multiplier = 10;
90 |
91 | public ImageAdapter(MainActivity activity) : base()
92 | {
93 | this.activity = activity;
94 | }
95 |
96 | #region implemented abstract members of BaseAdapter
97 |
98 | public override Java.Lang.Object GetItem(int position)
99 | {
100 | position = position % count_multiplier;
101 | return activity.images_to_fetch[position];
102 | }
103 |
104 | public override long GetItemId(int position)
105 | {
106 | return position;
107 | }
108 |
109 | public override View GetView(int position, View convertView, ViewGroup parent)
110 | {
111 | position = position % count_multiplier;
112 |
113 | var imageView = (ImageView)convertView ?? new ManagedImageView(activity);
114 | var key = new Uri(activity.images_to_fetch[position]);
115 | // This assumes the image exists in the cache. In the real world you'd want to
116 | // Wrap cache checking to download the image if it is not in the cache.
117 | var drawable = activity.image_cache[key];
118 | imageView.SetImageDrawable(drawable);
119 | return imageView;
120 | }
121 |
122 | public override int Count {
123 | get {
124 | return activity.images_to_fetch.Count * count_multiplier;
125 | }
126 | }
127 |
128 | #endregion
129 |
130 | }
131 | }
132 | }
133 |
134 |
135 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Android.UI.Drawables/SelfDisposingBitmapDrawable.cs:
--------------------------------------------------------------------------------
1 | //
2 | // SelfDisposingBitmapDrawable.cs
3 | //
4 | // Author:
5 | // Brett Duncavage
6 | //
7 | // Copyright 2013 Rdio, Inc.
8 | //
9 |
10 | using Android.Content.Res;
11 | using Android.Graphics;
12 | using System;
13 | using Android.Graphics.Drawables;
14 | using Rdio.TangoAndCache.Collections;
15 | using Android.Util;
16 |
17 | namespace Rdio.TangoAndCache.Android.UI.Drawables
18 | {
19 | ///
20 | /// A BitmapDrawable that uses reference counting to determine when internal resources
21 | /// should be freed (Disposed).
22 | ///
23 | /// On Android versions Honeycomb and higher the internal Bitmap is Dispose()d but not recycled.
24 | /// On all other Android versions the Bitmap is recycled then disposed.
25 | ///
26 | public class SelfDisposingBitmapDrawable : BitmapDrawable, IByteSizeAware
27 | {
28 | private const string TAG = "SelfDisposingBitmapDrawable";
29 |
30 | protected readonly object monitor = new object();
31 |
32 | private int cache_ref_count;
33 | private int display_ref_count;
34 | private int retain_ref_count;
35 |
36 | private bool is_bitmap_disposed;
37 |
38 | public Uri InCacheKey;
39 |
40 | public bool SupportsBitmapReuse {
41 | get { return Util.Utils.HasHoneycomb; }
42 | }
43 |
44 | ///
45 | /// Occurs when internal displayed reference count has reached 0.
46 | /// It is raised before the counts are rechecked, and thus before
47 | /// any resources have potentially been freed.
48 | ///
49 | public event EventHandler NoLongerDisplayed;
50 |
51 | ///
52 | /// Occurs when internal displayed reference count goes from 0 to 1.
53 | /// Once the internal reference count is > 1 this event will not be raised
54 | /// on subsequent calls to SetIsDisplayed(bool).
55 | ///
56 | public event EventHandler Displayed;
57 |
58 | public long SizeInBytes {
59 | get {
60 | if (HasValidBitmap) {
61 | return Bitmap.Height * Bitmap.RowBytes;
62 | }
63 | return 0;
64 | }
65 | }
66 |
67 | public SelfDisposingBitmapDrawable(Resources resources, Bitmap bitmap) : base(resources, bitmap)
68 | {
69 | }
70 |
71 | ///
72 | /// This should only be called by Views that are actually going to draw the drawable.
73 | ///
74 | /// Increments or decrements the internal displayed reference count.
75 | /// If the internal reference count becomes 0, NoLongerDisplayed will be raised.
76 | /// If the internal reference count becomes 1, Displayed will be raised.
77 | /// This method should be called from the main thread.
78 | ///
79 | /// If set to true reference count is
80 | /// incremented, otherwise it is decremented.
81 | public void SetIsDisplayed(bool isDisplayed)
82 | {
83 | EventHandler handler = null;
84 | lock (monitor) {
85 | if (isDisplayed && !HasValidBitmap) {
86 | throw new InvalidOperationException("Cannot redisplay this drawable, its resources have been disposed.");
87 | }
88 |
89 | if (isDisplayed) {
90 | display_ref_count++;
91 | if (display_ref_count == 1) {
92 | handler = Displayed;
93 | }
94 | } else {
95 | display_ref_count--;
96 | }
97 |
98 | if (display_ref_count <= 0) {
99 | handler = NoLongerDisplayed;
100 | }
101 | }
102 | if (handler != null) {
103 | handler(this, EventArgs.Empty);
104 | }
105 | CheckState();
106 | }
107 |
108 | ///
109 | /// This should only be called by caching entities.
110 | ///
111 | /// Increments or decrements the cache reference count.
112 | /// This count represents if the instance is cached by something
113 | /// and should not free its resources when no longer displayed.
114 | ///
115 | /// If set to true is cached.
116 | public void SetIsCached(bool isCached)
117 | {
118 | lock (monitor) {
119 | if (isCached) {
120 | cache_ref_count++;
121 | } else {
122 | cache_ref_count--;
123 | }
124 | }
125 | CheckState();
126 | }
127 |
128 | ///
129 | /// If you wish to use the instance beyond the lifecycle managed by the caching entity
130 | /// call this method with true. But be aware that you must also have the same number
131 | /// of calls with false or the instance and its resources will be leaked.
132 | ///
133 | /// Also be aware that once retained, the caching entity will not allow the internal
134 | /// Bitmap allocation to be reused. Retaining an instance does not guarantee it a place
135 | /// in the cache, it can be evicted at any time.
136 | ///
137 | /// If set to true is retained.
138 | public void SetIsRetained(bool isRetained)
139 | {
140 | lock (monitor) {
141 | if (isRetained) {
142 | retain_ref_count++;
143 | } else {
144 | retain_ref_count--;
145 | }
146 | }
147 | CheckState();
148 | }
149 |
150 | public bool IsRetained {
151 | get {
152 | lock (monitor) {
153 | return retain_ref_count > 0;
154 | }
155 | }
156 | }
157 |
158 | protected virtual void OnFreeResources()
159 | {
160 | Log.Debug(TAG, "OnFreeResources");
161 | lock (monitor) {
162 | if (!SupportsBitmapReuse) {
163 | Log.Debug(TAG, "Detected API level that does not support bitmap reuse, recycling bitmap");
164 | Bitmap.Recycle();
165 | }
166 | Bitmap.Dispose();
167 | is_bitmap_disposed = true;
168 | }
169 | }
170 |
171 | private void CheckState()
172 | {
173 | lock (monitor) {
174 | if (ShouldFreeResources) {
175 | OnFreeResources();
176 | }
177 | }
178 | }
179 |
180 | private bool ShouldFreeResources {
181 | get {
182 | lock (monitor) {
183 | return cache_ref_count <= 0 &&
184 | display_ref_count <= 0 &&
185 | retain_ref_count <= 0 &&
186 | HasValidBitmap;
187 | }
188 | }
189 | }
190 |
191 | protected virtual bool HasValidBitmap {
192 | get {
193 | lock (monitor) {
194 | return Bitmap != null && !is_bitmap_disposed && !Bitmap.IsRecycled;
195 | }
196 | }
197 | }
198 | }
199 | }
200 |
201 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Collections/StrongLruCache.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Collections;
5 |
6 | namespace Rdio.TangoAndCache.Collections
7 | {
8 | public class EntryRemovedEventArgs : EventArgs
9 | {
10 | public bool Evicted;
11 | public TKey Key;
12 | public TValue OldValue;
13 | public TValue NewValue;
14 | }
15 |
16 | public class EntryAddedEventArgs : EventArgs
17 | {
18 | public TKey Key;
19 | public TValue Value;
20 | }
21 |
22 | public class StrongLruCache : IDictionary
23 | {
24 | private readonly object sync = new object();
25 |
26 | private const string TAG = "StrongLruCache";
27 |
28 | private readonly int max_size;
29 |
30 | private readonly IDictionary dict = new Dictionary();
31 | private readonly LinkedList list = new LinkedList();
32 |
33 | public event EventHandler> EntryRemoved;
34 | public event EventHandler> EntryAdded;
35 |
36 | public StrongLruCache()
37 | {
38 | }
39 |
40 | public StrongLruCache(int maxSize)
41 | {
42 | max_size = maxSize;
43 | }
44 |
45 | public TValue this[TKey key]
46 | {
47 | get {
48 | TValue value;
49 | if (TryGetValue (key, out value)) {
50 | return value;
51 | }
52 | else {
53 | throw new KeyNotFoundException(String.Format("Key not found: {0}", key));
54 | }
55 | }
56 |
57 | set {
58 | Add (key, value, true);
59 | }
60 | }
61 |
62 | protected virtual bool CheckEvictionRequired()
63 | {
64 | return Count > max_size;
65 | }
66 |
67 | protected virtual void OnWillEvictEntry(TKey key, TValue value) {}
68 |
69 | protected virtual void OnEntryRemoved(bool evicted, TKey key, TValue oldValue, TValue newValue)
70 | {
71 | var h = EntryRemoved;
72 | if (h != null) {
73 | h(this, new EntryRemovedEventArgs {
74 | Evicted = evicted,
75 | Key = key,
76 | OldValue = oldValue,
77 | NewValue = newValue
78 | });
79 | }
80 | }
81 |
82 | protected virtual void OnEntryAdded(TKey key, TValue value)
83 | {
84 | var h = EntryAdded;
85 | if (h != null) {
86 | h(this, new EntryAddedEventArgs {
87 | Key = key,
88 | Value = value
89 | });
90 | }
91 | }
92 |
93 | ///
94 | /// Returns the value for the key if it exists in the cache.
95 | /// It does not refresh the LRU order of the returned entry.
96 | ///
97 | /// Key.
98 | public TValue Peek(TKey key)
99 | {
100 | var outValue = default(TValue);
101 | dict.TryGetValue(key, out outValue);
102 | return outValue;
103 | }
104 |
105 | ///
106 | /// Add the specified key and value. This will overwrite the value if the key
107 | /// already exists.
108 | ///
109 | /// Key.
110 | /// Value.
111 | public void Add(TKey key, TValue value)
112 | {
113 | Add(key, value, true);
114 | }
115 |
116 | private void Add(TKey key, TValue value, bool overwrite)
117 | {
118 | var raiseEntryRemovedDueToEviction = false;
119 | IDictionary evictions = null;
120 | var valueOverwritten = false;
121 | var overwrittenKey = default(TKey);
122 | var overwrittenValue = default(TValue);
123 |
124 | lock (this.sync) {
125 | if (dict.ContainsKey(key)) {
126 | list.Remove(key);
127 | if (overwrite) {
128 | overwrittenKey = key;
129 | overwrittenValue = dict[key];
130 | dict[key] = value;
131 | valueOverwritten = true;
132 | }
133 | } else {
134 | dict.Add(key, value);
135 | }
136 |
137 | list.AddLast(key);
138 |
139 | while (CheckEvictionRequired()) {
140 | OnWillEvictEntry(list.First.Value, dict[list.First.Value]);
141 |
142 | evictions = evictions ?? new Dictionary();
143 | evictions[list.First.Value] = dict[list.First.Value];
144 |
145 | dict.Remove(list.First.Value);
146 | list.RemoveFirst();
147 |
148 | raiseEntryRemovedDueToEviction = true;
149 | }
150 | }
151 |
152 | OnEntryAdded(key, value);
153 |
154 | if (raiseEntryRemovedDueToEviction) {
155 | foreach (var k in evictions.Keys) {
156 | OnEntryRemoved(true, k, evictions[k], default(TValue));
157 | }
158 | }
159 | if (valueOverwritten) {
160 | OnEntryRemoved(false, overwrittenKey, overwrittenValue, value);
161 | }
162 | }
163 |
164 | public bool TryGetValue(TKey key, out TValue value)
165 | {
166 | lock (this.sync) {
167 | if (dict.TryGetValue(key, out value)) {
168 | list.Remove(key);
169 | list.AddLast(key);
170 |
171 | return true;
172 | }
173 |
174 | return false;
175 | }
176 | }
177 |
178 | public void Remove(string keyPrefix)
179 | {
180 | List keysToRemove = null;
181 | Dictionary removedItems = null;
182 |
183 | lock (this.sync) {
184 | foreach (TKey key in dict.Keys) {
185 | if (key.ToString().StartsWith(keyPrefix)) {
186 | keysToRemove = keysToRemove ?? new List();
187 | removedItems = removedItems ?? new Dictionary();
188 | keysToRemove.Add(key);
189 | }
190 | }
191 |
192 | if (keysToRemove != null) {
193 | foreach (TKey key in keysToRemove) {
194 | removedItems[key] = dict[key];
195 | dict.Remove(key);
196 | list.Remove(key);
197 | }
198 | }
199 | }
200 |
201 | if (removedItems != null) {
202 | foreach (var kvp in removedItems) {
203 | OnEntryRemoved(false, kvp.Key, kvp.Value, default(TValue));
204 | }
205 | }
206 | }
207 |
208 | public void Clear()
209 | {
210 | TKey[] keys = null;
211 | lock (this.sync) {
212 | var keyCount = dict.Keys.Count;
213 | keys = new TKey[keyCount];
214 | dict.Keys.CopyTo(keys, 0);
215 | }
216 | if (keys != null) {
217 | foreach (var k in keys) {
218 | Remove(k);
219 | }
220 | }
221 | }
222 |
223 | public bool ContainsKey(TKey key)
224 | {
225 | lock (this.sync) {
226 | return dict.ContainsKey(key);
227 | }
228 | }
229 |
230 | public bool Remove(TKey key)
231 | {
232 | TValue value = default(TValue);
233 | bool removed = false;
234 | lock (this.sync) {
235 | if (dict.ContainsKey(key)) {
236 | value = dict[key];
237 | }
238 | list.Remove(key);
239 | removed = dict.Remove(key);
240 | }
241 | if (removed) {
242 | OnEntryRemoved(false, key, value, default(TValue));
243 | }
244 | return removed;
245 | }
246 |
247 | public ICollection Keys {
248 | get {
249 | lock (this.sync) {
250 | return list;
251 | }
252 | }
253 | }
254 |
255 | public ICollection Values {
256 | get {
257 | lock (this.sync) {
258 | return dict.Values;
259 | }
260 | }
261 | }
262 |
263 | public void Add(KeyValuePair item)
264 | {
265 | Add(item.Key, item.Value);
266 | }
267 |
268 | public bool Contains(KeyValuePair item)
269 | {
270 | lock (this.sync) {
271 | return ContainsKey(item.Key);
272 | }
273 | }
274 |
275 | public void CopyTo(KeyValuePair[] array, int arrayIndex)
276 | {
277 | lock (this.sync) {
278 | dict.CopyTo(array, arrayIndex);
279 | }
280 | }
281 |
282 | public bool Remove(KeyValuePair item)
283 | {
284 | return Remove(item.Key);
285 | }
286 |
287 | public int Count {
288 | get {
289 | lock (this.sync) {
290 | return dict.Count;
291 | }
292 | }
293 | }
294 |
295 | public bool IsReadOnly {
296 | get {
297 | return false;
298 | }
299 | }
300 |
301 | public IEnumerator> GetEnumerator()
302 | {
303 | lock (this.sync) {
304 | return dict.GetEnumerator();
305 | }
306 | }
307 |
308 | IEnumerator IEnumerable.GetEnumerator()
309 | {
310 | lock (this.sync) {
311 | return dict.GetEnumerator();
312 | }
313 | }
314 | }
315 | }
316 |
317 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Android.Collections/BitmapDrawableCache.cs:
--------------------------------------------------------------------------------
1 | //
2 | // BitmapDrawableCache.cs
3 | //
4 | // Author:
5 | // Brett Duncavage
6 | //
7 | // Copyright 2013 Rdio, Inc.
8 | //
9 |
10 | using System;
11 | using System.Collections.Generic;
12 | using Android.Graphics.Drawables;
13 | using Rdio.TangoAndCache.Collections;
14 | using Rdio.TangoAndCache.Android.UI.Drawables;
15 | using Android.Graphics;
16 | using Android.Util;
17 | using Android.OS;
18 |
19 | namespace Rdio.TangoAndCache.Android.Collections
20 | {
21 | public class BitmapDrawableCache : IDictionary
22 | {
23 | private const string TAG = "BitmapDrawableCache";
24 |
25 | private int total_added;
26 | private int total_removed;
27 | private int total_evictions;
28 | private int total_cache_hits;
29 | private long current_cache_byte_count;
30 |
31 | private readonly object monitor = new object();
32 |
33 | private IDictionary displayed_cache;
34 |
35 | private TimeSpan debug_dump_interval = TimeSpan.FromSeconds(10);
36 | private Handler main_thread_handler;
37 |
38 | ///
39 | /// Initializes a new instance of the class.
40 | ///
41 | /// Maximum size of cache in bytes before evictions start.
42 | /// Size in bytes to drain the cache down to after the high watermark has been exceeded.
43 | /// If set to true dump stats to log every 10 seconds.
44 | public BitmapDrawableCache(long highWatermark, long lowWatermark, bool debugDump = false)
45 | {
46 | var lruCache = new ByteBoundStrongLruCache(highWatermark, lowWatermark);
47 | lruCache.EntryRemoved += OnLruEviction;
48 | displayed_cache = lruCache;
49 |
50 | if (debugDump) {
51 | main_thread_handler = new Handler();
52 | DebugDumpStats();
53 | }
54 | }
55 |
56 | private void UpdateByteUsage(Bitmap bitmap, bool decrement = false, bool causedByEviction = false)
57 | {
58 | lock(monitor) {
59 | var byteCount = bitmap.RowBytes * bitmap.Height;
60 | current_cache_byte_count += byteCount * (decrement ? -1 : 1);
61 | }
62 | }
63 |
64 | private void OnLruEviction(object sender, EntryRemovedEventArgs e)
65 | {
66 | if (e.OldValue is SelfDisposingBitmapDrawable) {
67 | ProcessEviction((SelfDisposingBitmapDrawable)e.OldValue, e.Evicted);
68 | }
69 | }
70 |
71 | private void ProcessEviction(SelfDisposingBitmapDrawable value, bool evicted)
72 | {
73 | if (evicted) {
74 | lock (monitor) {
75 | total_evictions++;
76 | total_removed++;
77 | }
78 | UpdateByteUsage(value.Bitmap, decrement:true, causedByEviction:true);
79 | value.SetIsCached(false);
80 | value.Displayed -= OnEntryDisplayed;
81 | } else {
82 | lock (monitor) {
83 | total_removed++;
84 | }
85 | }
86 | }
87 |
88 | private void OnEntryDisplayed(object sender, EventArgs args)
89 | {
90 | if (!(sender is SelfDisposingBitmapDrawable)) return;
91 |
92 | // see if the sender is in the reuse pool and move it
93 | // into the internal_cache if found.
94 | lock (monitor) {
95 | var sdbd = (SelfDisposingBitmapDrawable)sender;
96 | // Adding a key that already exists refreshes the item's
97 | // position in the LRU list.
98 | displayed_cache.Add(sdbd.InCacheKey, sdbd);
99 | }
100 | }
101 |
102 | private void OnEntryAdded(Uri key, SelfDisposingBitmapDrawable value)
103 | {
104 | total_added++;
105 | Log.Debug(TAG, "OnEntryAdded(key = {0})", key);
106 | if (value is SelfDisposingBitmapDrawable) {
107 | var sdbd = (SelfDisposingBitmapDrawable)value;
108 | sdbd.SetIsCached(true);
109 | sdbd.InCacheKey = key;
110 | sdbd.Displayed += OnEntryDisplayed;
111 | UpdateByteUsage(sdbd.Bitmap);
112 | }
113 | }
114 |
115 | #region IDictionary implementation
116 |
117 | public void Add(Uri key, SelfDisposingBitmapDrawable value)
118 | {
119 | if (value == null) {
120 | Log.Warn(TAG, "Attempt to add null value, refusing to cache");
121 | return;
122 | }
123 |
124 | if (value.Bitmap == null) {
125 | Log.Warn(TAG, "Attempt to add Drawable with null bitmap, refusing to cache");
126 | return;
127 | }
128 |
129 | lock (monitor) {
130 | if (!displayed_cache.ContainsKey(key)) {
131 | displayed_cache.Add(key, value);
132 | OnEntryAdded(key, value);
133 | }
134 | }
135 | }
136 |
137 | public bool ContainsKey(Uri key)
138 | {
139 | lock (monitor) {
140 | return displayed_cache.ContainsKey(key);
141 | }
142 | }
143 |
144 | public bool Remove(Uri key)
145 | {
146 | SelfDisposingBitmapDrawable tmp = null;
147 | var result = false;
148 | lock (monitor) {
149 | if (displayed_cache.TryGetValue(key, out tmp)) {
150 | result = displayed_cache.Remove(key);
151 | }
152 | if (tmp is SelfDisposingBitmapDrawable) {
153 | ProcessEviction((SelfDisposingBitmapDrawable)tmp, evicted: true);
154 | }
155 | return result;
156 | }
157 | }
158 |
159 | public bool TryGetValue(Uri key, out SelfDisposingBitmapDrawable value)
160 | {
161 | lock (monitor) {
162 | var result = displayed_cache.TryGetValue(key, out value);
163 | if (result) {
164 | total_cache_hits++;
165 | Log.Debug(TAG, "Cache hit");
166 | }
167 | return result;
168 | }
169 | }
170 |
171 | public SelfDisposingBitmapDrawable this[Uri index] {
172 | get {
173 | lock (monitor) {
174 | SelfDisposingBitmapDrawable tmp = null;
175 | TryGetValue(index, out tmp);
176 | return tmp;
177 | }
178 | }
179 | set {
180 | Add(index, value);
181 | }
182 | }
183 |
184 | public ICollection Keys {
185 | get {
186 | lock (monitor) {
187 | return displayed_cache.Keys;
188 | }
189 | }
190 | }
191 |
192 | public ICollection Values {
193 | get {
194 | lock (monitor) {
195 | return displayed_cache.Values;
196 | }
197 | }
198 | }
199 |
200 | #endregion
201 |
202 | #region ICollection implementation
203 |
204 | public void Add(KeyValuePair item)
205 | {
206 | Add(item.Key, item.Value);
207 | }
208 |
209 | public void Clear()
210 | {
211 | lock (monitor) {
212 | foreach (var k in displayed_cache.Keys) {
213 | var tmp = displayed_cache[k];
214 | if (tmp is SelfDisposingBitmapDrawable) {
215 | ProcessEviction((SelfDisposingBitmapDrawable)tmp , evicted: true);
216 | }
217 | }
218 | displayed_cache.Clear();
219 | }
220 | }
221 |
222 | public bool Contains(KeyValuePair item)
223 | {
224 | return ContainsKey(item.Key);
225 | }
226 |
227 | public void CopyTo(KeyValuePair[] array, int arrayIndex)
228 | {
229 | throw new NotImplementedException("CopyTo is not supported");
230 | }
231 |
232 | public bool Remove(KeyValuePair item)
233 | {
234 | return Remove(item.Key);
235 | }
236 |
237 | public int Count {
238 | get {
239 | lock (monitor) {
240 | return displayed_cache.Count;
241 | }
242 | }
243 | }
244 |
245 | public bool IsReadOnly {
246 | get {
247 | lock (monitor) {
248 | return displayed_cache.IsReadOnly;
249 | }
250 | }
251 | }
252 |
253 | #endregion
254 |
255 | #region IEnumerable implementation
256 |
257 | public IEnumerator> GetEnumerator()
258 | {
259 | List> values;
260 | lock (monitor) {
261 | values = new List>(Count);
262 | foreach (var k in Keys) {
263 | values.Add(new KeyValuePair(k, this[k]));
264 | }
265 | }
266 | foreach (var kvp in values) {
267 | yield return kvp;
268 | }
269 | }
270 |
271 | #endregion
272 |
273 | #region IEnumerable implementation
274 |
275 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
276 | {
277 | return GetEnumerator();
278 | }
279 |
280 | #endregion
281 |
282 | private void DebugDumpStats()
283 | {
284 | main_thread_handler.PostDelayed(DebugDumpStats, (long)debug_dump_interval.TotalMilliseconds);
285 |
286 | lock (monitor) {
287 | Log.Debug(TAG, "--------------------");
288 | Log.Debug(TAG, "current total count: " + Count);
289 | Log.Debug(TAG, "cumulative additions: " + total_added);
290 | Log.Debug(TAG, "cumulative removals: " + total_removed);
291 | Log.Debug(TAG, "total evictions: " + total_evictions);
292 | Log.Debug(TAG, "total cache hits: " + total_cache_hits);
293 | Log.Debug(TAG, "cache size in bytes: " + current_cache_byte_count);
294 | }
295 | }
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/TangoAndCache/TangoAndCache/Android.Collections/ReuseBitmapDrawableCache.cs:
--------------------------------------------------------------------------------
1 | //
2 | // ReuseBitmapDrawableCache.cs
3 | //
4 | // Author:
5 | // Brett Duncavage
6 | //
7 | // Copyright 2013 Rdio, Inc.
8 | //
9 |
10 | using System.Linq;
11 | using System;
12 | using Rdio.TangoAndCache.Collections;
13 | using Rdio.TangoAndCache.Android.UI.Drawables;
14 | using Android.Graphics.Drawables;
15 | using Android.OS;
16 | using Android.Graphics;
17 | using Android.App;
18 | using System.Collections.Generic;
19 | using Android.Content;
20 | using Rdio.TangoAndCache.Android.Util;
21 | using Android.Util;
22 |
23 | namespace Rdio.TangoAndCache.Android.Collections
24 | {
25 | public class ReuseBitmapDrawableCache : IDictionary
26 | {
27 | private const string TAG = "ReuseBitmapDrawableCache";
28 |
29 | private int total_added;
30 | private int total_removed;
31 | private int total_reuse_hits;
32 | private int total_reuse_misses;
33 | private int total_evictions;
34 | private int total_cache_hits;
35 | private int total_forced_gc_collections;
36 | private long current_cache_byte_count;
37 | private long current_evicted_byte_count;
38 |
39 | private readonly object monitor = new object();
40 |
41 | private readonly long high_watermark;
42 | private readonly long low_watermark;
43 | private readonly long gc_threshold;
44 | private bool reuse_pool_refill_needed = true;
45 |
46 | ///
47 | /// Contains all entries that are currently being displayed. These entries are not eligible for
48 | /// reuse or eviction. Entries will be added to the reuse pool when they are no longer displayed.
49 | ///
50 | private IDictionary displayed_cache;
51 | ///
52 | /// Contains entries that potentially available for reuse and candidates for eviction.
53 | /// This is the default location for newly added entries. This cache
54 | /// is searched along with the displayed cache for cache hits. If a cache hit is found here, its
55 | /// place in the LRU list will be refreshed. Items only move out of reuse and into displayed
56 | /// when the entry has SetIsDisplayed(true) called on it.
57 | ///
58 | private ByteBoundStrongLruCache reuse_pool;
59 |
60 | private TimeSpan debug_dump_interval = TimeSpan.FromSeconds(10);
61 | private Handler main_thread_handler;
62 |
63 | ///
64 | /// Initializes a new instance of the class.
65 | ///
66 | /// Maximum number of bytes the reuse pool will hold before starting evictions.
67 | /// Number of bytes the reuse pool will be drained down to after the high watermark is exceeded.
68 | /// On Honeycomb and higher this value is used for the reuse pool size.
69 | /// Threshold in bytes that triggers a System.GC.Collect (Honeycomb+ only).
70 | /// If set to true dump stats to log every 10 seconds.
71 | public ReuseBitmapDrawableCache(long highWatermark, long lowWatermark, long gcThreshold = 2 * 1024 * 1024, bool debugDump = false)
72 | {
73 | low_watermark = lowWatermark;
74 | high_watermark = highWatermark;
75 |
76 | gc_threshold = gcThreshold;
77 | displayed_cache = new Dictionary();
78 | reuse_pool = new ByteBoundStrongLruCache(highWatermark, lowWatermark);
79 | reuse_pool.EntryRemoved += OnEntryRemovedFromReusePool;
80 |
81 | if (debugDump) {
82 | main_thread_handler = new Handler();
83 | DebugDumpStats();
84 | }
85 | }
86 |
87 | ///
88 | /// Attempts to find a bitmap suitable for reuse based on the given dimensions.
89 | /// Note that any returned instance will have SetIsRetained(true) called on it
90 | /// to ensure that it does not release its resources prematurely as it is leaving
91 | /// cache management. This means you must call SetIsRetained(false) when you no
92 | /// longer need the instance.
93 | ///
94 | /// A SelfDisposingBitmapDrawable that has been retained. You must call SetIsRetained(false)
95 | /// when finished using it.
96 | /// Width of the image to be written to the bitmap allocation.
97 | /// Height of the image to be written to the bitmap allocation.
98 | public SelfDisposingBitmapDrawable GetReusableBitmapDrawable(int width, int height)
99 | {
100 | if (reuse_pool == null) return null;
101 |
102 | // Only attempt to get a bitmap for reuse if the reuse cache is full.
103 | // This prevents us from prematurely depleting the pool and allows
104 | // more cache hits, as the most recently added entries will have a high
105 | // likelihood of being accessed again so we don't want to steal those bytes too soon.
106 | lock (monitor) {
107 | if (reuse_pool.CacheSizeInBytes < low_watermark && reuse_pool_refill_needed) {
108 | Log.Debug(TAG, "Reuse pool is not full, refusing reuse request");
109 | total_reuse_misses++;
110 | return null;
111 | }
112 | reuse_pool_refill_needed = false;
113 |
114 | SelfDisposingBitmapDrawable reuseDrawable = null;
115 |
116 | if (reuse_pool.Count > 0) {
117 | var reuse_keys = reuse_pool.Keys;
118 | foreach (var k in reuse_keys) {
119 | var bd = reuse_pool.Peek(k);
120 | // TODO: Implement check for KitKat and higher since
121 | // bitmaps that are smaller than the requested size can be used.
122 | if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat) {
123 | // We can reuse a bitmap if the allocation to be reused is larger
124 | // than the requested bitmap.
125 | if (bd.Bitmap.Width >= width && bd.Bitmap.Height >= height && !bd.IsRetained) {
126 | reuseDrawable = bd;
127 | Log.Debug(TAG, "Reuse hit! Using larger allocation.");
128 | break;
129 | }
130 | } else if (bd.Bitmap.Width == width &&
131 | bd.Bitmap.Height == height &&
132 | bd.Bitmap.IsMutable &&
133 | !bd.IsRetained) {
134 |
135 | reuseDrawable = bd;
136 |
137 | Log.Debug(TAG, "Reuse hit!");
138 | break;
139 | }
140 | }
141 | if (reuseDrawable != null) {
142 | reuseDrawable.SetIsRetained(true);
143 |
144 | UpdateByteUsage(reuseDrawable.Bitmap, decrement:true, causedByEviction: true);
145 |
146 | // Cleanup the entry
147 | reuseDrawable.Displayed -= OnEntryDisplayed;
148 | reuseDrawable.NoLongerDisplayed -= OnEntryNoLongerDisplayed;
149 | reuseDrawable.SetIsCached(false);
150 |
151 | reuse_pool.Remove(reuseDrawable.InCacheKey);
152 | total_reuse_hits++;
153 | }
154 | }
155 | if (reuseDrawable == null) {
156 | total_reuse_misses++;
157 | // Indicate that the pool may need to be refilled.
158 | // There is little harm in setting this flag since it will be unset
159 | // on the next reuse request if the threshold is reuse_pool.CacheSizeInBytes >= low_watermark.
160 | reuse_pool_refill_needed = true;
161 | }
162 | return reuseDrawable;
163 | }
164 | }
165 |
166 | private void UpdateByteUsage(Bitmap bitmap, bool decrement = false, bool causedByEviction = false)
167 | {
168 | lock(monitor) {
169 | var byteCount = bitmap.RowBytes * bitmap.Height;
170 | current_cache_byte_count += byteCount * (decrement ? -1 : 1);
171 | if (causedByEviction) {
172 | current_evicted_byte_count += byteCount;
173 | // Kick the gc if we've accrued more than our desired threshold.
174 | // TODO: Implement high/low watermarks to prevent thrashing
175 | if (current_evicted_byte_count > gc_threshold) {
176 | total_forced_gc_collections++;
177 | Log.Debug(TAG, "Memory usage exceeds threshold, invoking GC.Collect");
178 | System.GC.Collect();
179 | current_evicted_byte_count = 0;
180 | }
181 | }
182 | }
183 | }
184 |
185 | private void OnEntryRemovedFromReusePool (object sender, EntryRemovedEventArgs e)
186 | {
187 | ProcessRemoval(e.OldValue, e.Evicted);
188 | }
189 |
190 | private void ProcessRemoval(SelfDisposingBitmapDrawable value, bool evicted)
191 | {
192 | lock(monitor) {
193 | total_removed++;
194 | if (evicted) {
195 | Log.Debug(TAG, "Evicted key: {0}", value.InCacheKey);
196 | total_evictions++;
197 | }
198 | }
199 |
200 | // We only really care about evictions because we do direct Remove()als
201 | // all the time when promoting to the displayed_cache. Only when the
202 | // entry has been evicted is it truly not longer being held by us.
203 | if (evicted) {
204 | UpdateByteUsage(value.Bitmap, decrement: true, causedByEviction: true);
205 |
206 | value.SetIsCached(false);
207 | value.Displayed -= OnEntryDisplayed;
208 | value.NoLongerDisplayed -= OnEntryNoLongerDisplayed;
209 | }
210 | }
211 |
212 | private void OnEntryNoLongerDisplayed(object sender, EventArgs args)
213 | {
214 | if (!(sender is SelfDisposingBitmapDrawable)) return;
215 |
216 | var sdbd = (SelfDisposingBitmapDrawable)sender;
217 | lock (monitor) {
218 | if (displayed_cache.ContainsKey(sdbd.InCacheKey)) {
219 | DemoteDisplayedEntryToReusePool(sdbd);
220 | }
221 | }
222 | }
223 |
224 | private void OnEntryDisplayed(object sender, EventArgs args)
225 | {
226 | if (!(sender is SelfDisposingBitmapDrawable)) return;
227 |
228 | // see if the sender is in the reuse pool and move it
229 | // into the displayed_cache if found.
230 | var sdbd = (SelfDisposingBitmapDrawable)sender;
231 | lock (monitor) {
232 | if (reuse_pool.ContainsKey(sdbd.InCacheKey)) {
233 | PromoteReuseEntryToDisplayedCache(sdbd);
234 | }
235 | }
236 | }
237 |
238 | private void OnEntryAdded(Uri key, BitmapDrawable value)
239 | {
240 | total_added++;
241 | Log.Debug(TAG, "OnEntryAdded(key = {0})", key);
242 | if (value is SelfDisposingBitmapDrawable) {
243 | var sdbd = (SelfDisposingBitmapDrawable)value;
244 | sdbd.SetIsCached(true);
245 | sdbd.InCacheKey = key;
246 | sdbd.Displayed += OnEntryDisplayed;
247 | UpdateByteUsage(sdbd.Bitmap);
248 | }
249 | }
250 |
251 | private void PromoteReuseEntryToDisplayedCache(SelfDisposingBitmapDrawable value)
252 | {
253 | value.Displayed -= OnEntryDisplayed;
254 | value.NoLongerDisplayed += OnEntryNoLongerDisplayed;
255 | reuse_pool.Remove(value.InCacheKey);
256 | displayed_cache.Add(value.InCacheKey, value);
257 | }
258 |
259 | private void DemoteDisplayedEntryToReusePool(SelfDisposingBitmapDrawable value)
260 | {
261 | value.NoLongerDisplayed -= OnEntryNoLongerDisplayed;
262 | value.Displayed += OnEntryDisplayed;
263 | displayed_cache.Remove(value.InCacheKey);
264 | reuse_pool.Add(value.InCacheKey, value);
265 | }
266 |
267 | #region IDictionary implementation
268 |
269 | public void Add(Uri key, SelfDisposingBitmapDrawable value)
270 | {
271 | if (value == null) {
272 | Log.Warn(TAG, "Attempt to add null value, refusing to cache");
273 | return;
274 | }
275 |
276 | if (value.Bitmap == null) {
277 | Log.Warn(TAG, "Attempt to add Drawable with null bitmap, refusing to cache");
278 | return;
279 | }
280 |
281 | lock (monitor) {
282 | if (!displayed_cache.ContainsKey(key) && !reuse_pool.ContainsKey(key)) {
283 | reuse_pool.Add(key, (SelfDisposingBitmapDrawable)value);
284 | OnEntryAdded(key, value);
285 | }
286 | }
287 | }
288 |
289 | public bool ContainsKey(Uri key)
290 | {
291 | lock (monitor) {
292 | return displayed_cache.ContainsKey(key) || reuse_pool.ContainsKey(key);
293 | }
294 | }
295 |
296 | public bool Remove(Uri key)
297 | {
298 | SelfDisposingBitmapDrawable tmp = null;
299 | SelfDisposingBitmapDrawable reuseTmp = null;
300 | var result = false;
301 | lock (monitor) {
302 | if (displayed_cache.TryGetValue(key, out tmp)) {
303 | result = displayed_cache.Remove(key);
304 | } else if (reuse_pool.TryGetValue(key, out reuseTmp)) {
305 | result = reuse_pool.Remove(key);
306 | }
307 | if (tmp is SelfDisposingBitmapDrawable) {
308 | ProcessRemoval((SelfDisposingBitmapDrawable)tmp, evicted: true);
309 | }
310 | if (reuseTmp is SelfDisposingBitmapDrawable) {
311 | ProcessRemoval(reuseTmp, evicted: true);
312 | }
313 | return result;
314 | }
315 | }
316 |
317 | public bool TryGetValue(Uri key, out SelfDisposingBitmapDrawable value)
318 | {
319 | lock (monitor) {
320 | var result = displayed_cache.TryGetValue(key, out value);
321 | if (result) {
322 | total_cache_hits++;
323 | Log.Debug(TAG, "Cache hit");
324 | } else {
325 |
326 | SelfDisposingBitmapDrawable tmp = null;
327 | result = reuse_pool.TryGetValue(key, out tmp); // If key is found, its place in the LRU is refreshed
328 | if (result) {
329 | Log.Debug(TAG, "Cache hit from reuse pool");
330 | total_cache_hits++;
331 | }
332 | value = tmp;
333 | }
334 | return result;
335 | }
336 | }
337 |
338 | public SelfDisposingBitmapDrawable this[Uri index] {
339 | get {
340 | lock (monitor) {
341 | SelfDisposingBitmapDrawable tmp = null;
342 | TryGetValue(index, out tmp);
343 | return tmp;
344 | }
345 | }
346 | set {
347 | Add(index, value);
348 | }
349 | }
350 |
351 | public ICollection Keys {
352 | get {
353 | lock (monitor) {
354 | var cacheKeys = displayed_cache.Keys;
355 | var allKeys = new List(cacheKeys);
356 | allKeys.AddRange(reuse_pool.Keys);
357 | return allKeys;
358 | }
359 | }
360 | }
361 |
362 | public ICollection Values {
363 | get {
364 | lock (monitor) {
365 | var cacheValues = displayed_cache.Values;
366 | var allValues = new List(cacheValues);
367 | allValues.AddRange(reuse_pool.Values);
368 | return allValues;
369 | }
370 | }
371 | }
372 |
373 | #endregion
374 |
375 | #region ICollection implementation
376 |
377 | public void Add(KeyValuePair item)
378 | {
379 | Add(item.Key, item.Value);
380 | }
381 |
382 | public void Clear()
383 | {
384 | lock (monitor) {
385 | foreach (var k in displayed_cache.Keys) {
386 | var tmp = displayed_cache[k];
387 | if (tmp is SelfDisposingBitmapDrawable) {
388 | ProcessRemoval((SelfDisposingBitmapDrawable)tmp , evicted: true);
389 | }
390 | }
391 | displayed_cache.Clear();
392 |
393 | foreach (var k in reuse_pool.Keys) {
394 | ProcessRemoval(reuse_pool[k], evicted: true);
395 | }
396 | reuse_pool.Clear();
397 | }
398 | }
399 |
400 | public bool Contains(KeyValuePair item)
401 | {
402 | return ContainsKey(item.Key);
403 | }
404 |
405 | public void CopyTo(KeyValuePair[] array, int arrayIndex)
406 | {
407 | throw new NotImplementedException("CopyTo is not supported");
408 | }
409 |
410 | public bool Remove(KeyValuePair item)
411 | {
412 | return Remove(item.Key);
413 | }
414 |
415 | public int Count {
416 | get {
417 | lock (monitor) {
418 | return displayed_cache.Count + reuse_pool.Count;
419 | }
420 | }
421 | }
422 |
423 | public bool IsReadOnly {
424 | get {
425 | lock (monitor) {
426 | return displayed_cache.IsReadOnly;
427 | }
428 | }
429 | }
430 |
431 | #endregion
432 |
433 | #region IEnumerable implementation
434 |
435 | public IEnumerator> GetEnumerator()
436 | {
437 | List> values;
438 | lock (monitor) {
439 | values = new List>(Count);
440 | foreach (var k in Keys) {
441 | values.Add(new KeyValuePair(k, this[k]));
442 | }
443 | }
444 | foreach (var kvp in values) {
445 | yield return kvp;
446 | }
447 | }
448 |
449 | #endregion
450 |
451 | #region IEnumerable implementation
452 |
453 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
454 | {
455 | return GetEnumerator();
456 | }
457 |
458 | #endregion
459 |
460 | private void DebugDumpStats()
461 | {
462 | main_thread_handler.PostDelayed(DebugDumpStats, (long)debug_dump_interval.TotalMilliseconds);
463 |
464 | lock (monitor) {
465 | Log.Debug(TAG, "--------------------");
466 | Log.Debug(TAG, "current total count: " + Count);
467 | Log.Debug(TAG, "cumulative additions: " + total_added);
468 | Log.Debug(TAG, "cumulative removals: " + total_removed);
469 | Log.Debug(TAG, "total evictions: " + total_evictions);
470 | Log.Debug(TAG, "total cache hits: " + total_cache_hits);
471 | Log.Debug(TAG, "reuse hits: " + total_reuse_hits);
472 | Log.Debug(TAG, "reuse misses: " + total_reuse_misses);
473 | Log.Debug(TAG, "reuse pool count: " + reuse_pool.Count);
474 | Log.Debug(TAG, "gc threshlold: " + gc_threshold);
475 | Log.Debug(TAG, "cache size in bytes: " + current_cache_byte_count);
476 | Log.Debug(TAG, "reuse pool in bytes: " + reuse_pool.CacheSizeInBytes);
477 | Log.Debug(TAG, "current evicted bytes: " + current_evicted_byte_count);
478 | Log.Debug(TAG, "high watermark: " + high_watermark);
479 | Log.Debug(TAG, "low watermark: " + low_watermark);
480 | Log.Debug(TAG, "total force gc collections: " + total_forced_gc_collections);
481 | if (total_reuse_hits > 0 || total_reuse_misses > 0) {
482 | Log.Debug(TAG, "reuse hit %: " + (100f * (total_reuse_hits / (float)(total_reuse_hits + total_reuse_misses))));
483 | }
484 | }
485 | }
486 | }
487 | }
488 |
489 |
--------------------------------------------------------------------------------