├── .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 | --------------------------------------------------------------------------------