B4A Library [Lib] Cache

This library adds a LRU cache to your application. It is based upon the memory cache introduced in API 12 (Honeycomb) and the disk cache introduced in API 14 (ICS). The library is thread-safe and works with Android versions > 1. The memory cache accepts only bitmaps. The disk cache accepts serializable objects (strings, numbers, lists, arrays...) and bitmaps.

If you're wondering what's a cache and if you need it, here's the Wikipedia definition:
[...] A cache is a component that transparently stores data so that future requests for that data can be served faster. The data that is stored within a cache might be values that have been computed earlier or duplicates of original values that are stored elsewhere. If requested data is contained in the cache (cache hit), this request can be served by simply reading the cache, which is comparatively faster. Otherwise (cache miss), the data has to be recomputed or fetched from its original storage location, which is comparatively slower. Hence, the greater the number of requests that can be served from the cache, the faster the overall system performance becomes. [...]

This cache uses the LRU (Least Recently Used) algorithm. Each time a value is stored or accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

A cache may be useful in various situations. Its common use is for downloaded resources or image galleries, but you can also use it in a state manager, or in a game to improve performance.
The demo should prove you the benefit of using a cache.

List of properties and methods

If you want to put the disk cache on an external storage media, don't forget to add the following permission with the Manifest editor:
B4X:
AddPermission("android.permission.WRITE_EXTERNAL_STORAGE")

v1.1:
I added statistics for the memory cache (miss count, hit count, etc.)
I added the IsClosed function for the disk cache.

v1.11:
Fixed a minor bug.

v1.34:
I improved some functions and fixed minor bugs.
I added two properties: FreeMemory and MaxMemory.

v1.35:
I improved the synchronization of asynchronous operations.

v1.36:
I fixed a rare bug with very large caches.
I made an internal change for UltimateListView.
I updated the deprecated links of the demo.

v1.37:
I fixed a bug with truncated lines.
I ignore any error resulting from a call to StatFs because this OS library does not work properly on some devices. I use a default value when an error occurs.
The key for the disk cache accepts longer names.

v1.38:
I fixed a bug with the default cache folder (this concerns mainly the ULV users).
I also changed the GetBitmap function so the message about the source is sent to the unfiltered log now, instead of the filtered log.
 

Attachments

  • Cache v1.38.zip
    74.3 KB · Views: 925
  • SourceJava_Cache_1_38.zip
    51.1 KB · Views: 470
Last edited:

MaxApps

Active Member
Licensed User
Longtime User
Can I use this cache to avoid using the built in cache?
I have an app, that handles a lot of small bitmaps and on some devices, it keeps on running out of cache.

Kind regards
Jakob
 

Informatix

Expert
Licensed User
Longtime User
Can I use this cache to avoid using the built in cache?
I have an app, that handles a lot of small bitmaps and on some devices, it keeps on running out of cache.

Kind regards
Jakob

Could you tell me more? I don't know what's this built-in cache you're talking about. The only thing I know that uses a cache in B4A is the WebView. I'm not sure you can override its caching functions and I'm not sure that doing this would be a good idea.
 

Informatix

Expert
Licensed User
Longtime User
List of properties and methods (v1.11):
  • Cache
    Events:
    • CopyDone(Key As String, Error As Boolean)
    • PutDone(Key As String, Error As Boolean)
    • GetDone(Key As String, Obj As Object)
    Properties:
    • BufferSize As Int
      Gets/sets the buffer size of the disk cache. By default, it is set to the block size of the filesystem.
    Methods:
    • BitmapSize (bitmap As BitmapWrapper) As Long
      Returns the size in bytes of the given bitmap.
    • ClearDiskCache
      Deletes all files in the cache directory including files that weren't created by the cache.
    • ClearMemoryCache ()
      Clears the memory cache holding bitmaps.
    • CloseAndClearDiskCache
      Closes the disk cache and deletes all files in the cache directory including files that weren't created by the cache.
    • CloseDiskCache
      Closes the disk cache. Stored values will remain on the filesystem.
    • CopyFileToDisk (key As String, dir As String, filename As String)
      Copies a file in the disk cache.
    • CopyFileToDisk_Async (key As String, dir As String, filename As String, eventPrefix As String)
      Copies a file in the disk cache (asynchronously).
      eventPrefix = prefix of the "CopyDone" event.
      An event is triggered when the job ends. You can get it with:
      Sub eventPrefix_CopyDone(key As String, Error As Boolean)
      Error is true if there was an error.
    • DiskFree As Long
      Returns the available space in bytes of the disk cache.
    • DiskJournal As List
      Returns a copy of the journal of the disk cache.
      Example:
      B4X:
      Dim l As List
      l = myCache.DiskJournal
      For i = 0 To l.Size - 1
         Log(l.Get(i))
      Next
    • DiskList As Map
      Returns a list of the current contents of the disk cache, unordered.
      Map structure: key=filename, value=object array (0=time of the last modification, 1=size in bytes)
      Example:
      B4X:
      Dim m As Map
      m = myCache.DiskList
      Dim Info(2) As Object
      For i = 0 To m.Size - 1
         Log(m.GetKeyAt(i))
         Info = m.GetValueAt(i)
         Log("   " & DateTime.Date(Info(0)) & " " & DateTime.Time(Info(0)))
         Log("   " & Info(1) & " bytes")
      Next
    • DiskUsed As Long
      Returns the sum of the sizes in bytes of the files in the disk cache.
    • GetBitmap (key As String, updateMemory As Boolean) As Bitmap
      Returns the bitmap for key from one of the caches (the memory cache is looked first).
      Returns null if the key was not found.
      The bitmap source (memory or disk cache) is specified in the log.
      updateMemory = if true, the memory cache is updated if the bitmap was only on disk.
    • GetBitmapFromDisk (key As String) As Bitmap
      Returns the bitmap for key from the disk cache.
      Returns null if the key was not found.
    • GetBitmapFromDisk_Async (key As String, eventPrefix As String)
      Returns the bitmap for key from the disk cache (asynchronously).
      eventPrefix = prefix of the "GetDone" event.
      An event is triggered when the job ends. You can get it with:
      Sub eventPrefix_GetDone(key As String, bmp As Bitmap)
      bmp is null if the key was not found or there was an error.
    • GetBitmapFromMemory (key As String) As Bitmap
      Returns the Bitmap for key if it exists in the cache.
      Returns null if not found.
    • GetBitmap_Async (key As String, updateMemory As Boolean, eventPrefix As String)
      Returns the given bitmap for key from one of the caches.
      If the bitmap is in memory, it is sent back immediately. Otherwise, it is loaded from disk asynchronously.
      updateMemory = if true, the memory cache is updated if the bitmap was only found on disk.
      eventPrefix = prefix of the "GetDone" event.
      An event is triggered when the job ends. You can get it with:
      Sub eventPrefix_GetDone(key As String, bmp As Bitmap)
      bmp is null if the key was not found or there was an error.
    • GetDirectory As String
      Returns the directory where the disk cache stores its data.
    • GetObjectFromDisk (key As String) As Object
      Returns the object for key from the disk cache.
      Returns null if the key was not found.
    • GetObjectFromDisk_Async (key As String, eventPrefix As String)
      Returns the object for key from the disk cache (asynchronously).
      eventPrefix = prefix of the "GetDone" event.
      An event is triggered when the job ends. You can get it with:
      Sub eventPrefix_GetDone(key As String, obj As Object)
      obj is null if the key was not found or there was an error.
    • GetStringFromDisk (key As String) As String
      Gets the string for key from the disk cache.
      Returns an empty string if the key was not found.
    • Initialize (memoryCachePct As Byte, diskCacheSize As Long, diskCacheDir As String)
      Initializes the caches.
      memoryCachePct: percentage of the available memory, used for the memory cache (this cache is only used for bitmaps, the other objects are stored on disk). Cannot exceed 75.
      diskCacheSize: size in bytes of the cache on disk
      diskCacheDir: folder where cache files are stored
      If empty, the default cache folder is used. This folder is specific to the application.
      Example:
      B4X:
      '25% for the memory cache and 20MB for the disk cache
      myCache.Initialize(25, 20 * 1024 * 1024, "/mnt/sdcard/myAppCache")
    • IsClosed As Boolean
      Returns true if the disk cache has been closed.
    • IsInMemory (key As String) As Boolean
      Returns true if the Bitmap for key is still in the memory cache.
    • IsOnDisk (key As String) As Boolean
      Returns true if the file for key is still in the disk cache.
    • MemoryEvictionCount As Int
      Returns the number of bitmaps that have been evicted from the memory cache.
    • MemoryFree As Int
      Returns the available space in bytes of the memory cache.
    • MemoryHitCount As Int
      Returns the number of times GetBitmapFromMemory returned a bitmap that was present in the cache.
    • MemoryList As Map
      Returns a copy of the current contents of the memory cache, ordered from least recently accessed to most recently accessed.
    • MemoryMissCount As Int
      Returns the number of times GetBitmapFromMemory returned null (bitmap not found).
    • MemoryPutCount As Int
      Returns the number of times PutBitmapInMemory was called.
    • MemoryUsed As Int
      Returns the sum of the sizes in bytes of the entries in the memory cache.
    • PutBitmapInMemory (key As String, bitmap As BitmapWrapper) As Boolean
      Caches the given bitmap in memory.
      Returns false if the bitmap is too big for the cache.
    • PutBitmapOnDisk (key As String, bitmap As BitmapWrapper, format As String, quality As Int)
      Stores the given bitmap in the disk cache.
      Format: "JPEG" or "PNG"
      Quality: quality of JPEG compression (0..100). This setting is ignored for the PNG format.
    • PutBitmapOnDisk_Async (key As String, bitmap As BitmapWrapper, format As String, quality As Int, eventPrefix As String)
      Stores the given bitmap in the disk cache (asynchronously).
      Format: "JPEG" or "PNG"
      Quality: quality of JPEG compression (0..100). This setting is ignored for the PNG format.
      eventPrefix = prefix of the "PutDone" event.
      An event is triggered when the job ends. You can get it with:
      Sub eventPrefix_PutDone(key As String, Error As Boolean)
      Error is true if there was an error.
    • PutObjectOnDisk (key As String, obj As Object)
      Stores the given object in the disk cache.
      This works only with serializable objects (numbers, booleans, lists, arrays...).
    • PutObjectOnDisk_Async (key As String, obj As Object, eventPrefix As String)
      Stores the given object in the disk cache (asynchronously).
      This works only with serializable objects (numbers, booleans, lists, arrays...).
      eventPrefix = prefix of the "PutDone" event.
      An event is triggered when the job ends. You can get it with:
      Sub eventPrefix_PutDone(key As String, Error As Boolean)
      Error is true if there was an error.
    • PutStringOnDisk (key As String, value As String)
      Stores the given string in the disk cache.
    • RemoveBitmapFromMemory (key As String)
      Removes the Bitmap for key if it exists.
    • RemoveFromDisk (key As String) As Boolean
      Removes the file for key if it exists in the disk cache.
      Returns true if the file was removed.
 
Last edited:

Shark812

Member
Licensed User
Longtime User
Thank you for this and awesome demo code. Very clear.
Was able to integrate it very quickly with the app I'm developing.

I will be donating a couple $ later today ;)
And more once my app is ready for release, since your tutorials have been helping as well. :sign0098:
 

Shark812

Member
Licensed User
Longtime User
Need Assistance...

Okay, I've got an issue that's driving me insane and I keep thinking it's something simple that I'm overlooking but can't figure it out!

The imageview is being shrunken(?) when using GetBitmap(I suppose?), only after closing/resuming activity. (rotate or userclose/reopen)
It does not have this problem using Android 2.2, but does on 4.0.3 and 4.1.2, using emulators and devices.

Also, I would think this problem would be seen in your demo code, but it doesn't occur! And I can't figure out what I'm doing different...

Screenshots: Before and After

I've uploaded a very small sample project that will demonstrate my problem.
Any help is greatly appreciated.

Edit: Also, here is the code, if it can be figured out by just looking at it...
B4X:
'Activity module
Sub Process_Globals
   'These global variables will be declared once when the application starts.
   'These variables can be accessed from all modules.
   Dim Cache As Cache
   Dim MemorySize As Int: MemorySize = 50
   Dim DiskSize As Int: DiskSize = 2 * 1024 * 1024
End Sub

Sub Globals
   'These global variables will be redeclared each time the activity is created.
   'These variables can only be accessed from this module.
   Dim ivTest As ImageView
   Dim ivThumbnailColor As Int: ivThumbnailColor = Colors.DarkGray
End Sub

Sub Activity_Create(FirstTime As Boolean)
   'Do not forget to load the layout file created with the visual designer. For example:
   'Activity.LoadLayout("Layout1")
   
   Dim btnLoad As Button
   btnLoad.Initialize("btnLoad")
   btnLoad.Text = "Load Image"
   Activity.AddView(btnLoad, 30dip, 60dip, 165dip, 45dip)
   
   Dim btnClear As Button
   btnClear.Initialize("btnClear")
   btnClear.Text = "Clear Cache"
   Activity.AddView(btnClear, 30dip, 10dip, 175dip, 45dip)
   
   ivTest.Initialize("")
   ivTest.Gravity = Gravity.FILL
   ivTest.Color = ivThumbnailColor
   Activity.AddView(ivTest, 100%x - 95dip, 20dip, 65dip, 65dip)
   
   Cache.Initialize(MemorySize, DiskSize, "")
   
   WantImage2("Testing", "http://api.jamendo.com/get2/image/album/redirect/?id=116&imagesize=65", False)
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)
   Cache.CloseDiskCache
End Sub

Sub JobDone (Job As HttpJob)
    Log("JobName = " & Job.JobName & ", Success = " & Job.Success)
    If Job.Success = True Then
        Select Job.JobName
         Case "Testing"
            ivTest.Bitmap = Job.GetBitmap
            
            Cache.PutBitmapInMemory(Job.JobName, ivTest.Bitmap)
            Cache.PutBitmapOnDisk(Job.JobName, ivTest.Bitmap, "JPEG", 100)
        End Select
    Else
        Log("Error: " & Job.ErrorMessage)
        ToastMessageShow("Error: " & Job.ErrorMessage, True)
    End If
    Job.Release
End Sub

Sub WantImage2(ImageKey As String, URL As String, Refresh As Boolean) As Bitmap
   ' The memory cache is looked first. If nothing is found, the disk cache is used.
   Dim bmp As Bitmap, iv As ImageView
   bmp = Cache.GetBitmap(ImageKey, True)
   iv = ivTest
   
   If bmp.IsInitialized AND Refresh <> True Then
      ' The bitmap is in cache -> the image is displayed
      ' Also, refresh must be false.
      iv.Bitmap = bmp
   Else
      ' The bitmap is not in cache -> lets download the image
      ' An hourglass is displayed in the meanwhile
      iv.Bitmap = LoadBitmap(File.DirAssets, "hourglass.png") 'Source: http://sweetclipart.com/hourglass-silhouette-874
      Dim job As HttpJob
      job.Initialize(ImageKey, Me)
      job.Download(URL)
   End If
End Sub

Sub btnLoad_Click
   WantImage2("Testing", "http://api.jamendo.com/get2/image/album/redirect/?id=116&imagesize=65", False)
End Sub

Sub btnClear_Click
   Cache.ClearMemoryCache
   Cache.ClearDiskCache
End Sub
 

Attachments

  • CacheTest.zip
    38.1 KB · Views: 536
Last edited:

bloxa69

Active Member
Licensed User
Longtime User
Hey Informatix,
I've played with your lib, it works great, no problems.
I've only have a hard time checking the images in cache versus the remote originals, may be I am missing something. For my app, I need to compare the modification dates on the remote images versus the ones in cache and then refresh only those images in cache that have been updated on the remote server.

I was looking for some properties or methods to get the bitmap info from cache by using the bitmaps's key (because that's the only way to identify them), something like myCache.DateModified(key as String) or myCache.GetBitmapProperties(key as as String) but couldn't find any easy way of doing it.
Anything I could find so far was IsOnDisk (key As String) property that is using this approach. May be I am missing something?

Thanks for the lib.
 

Informatix

Expert
Licensed User
Longtime User
I've only have a hard time checking the images in cache versus the remote originals, may be I am missing something. For my app, I need to compare the modification dates on the remote images versus the ones in cache and then refresh only those images in cache that have been updated on the remote server.

Bitmaps are stored as files in the disk cache, so to know their timestamp, read the file timestamp. These files are named with their key. The extension is 0 (example: myimage125.0).
 

bloxa69

Active Member
Licensed User
Longtime User
Thanks for the advice Informatix. I figured that though. So for now I am downloading the bitmaps to the app's cache folder using httputils and later on when needed I check the file's properties using the regular file operations. But I don't need the cache lib for that, I guess.

Thank you for sharing your work.
 

Informatix

Expert
Licensed User
Longtime User
Thanks for the advice Informatix. I figured that though. So for now I am downloading the bitmaps to the app's cache folder using httputils and later on when needed I check the file's properties using the regular file operations. But I don't need the cache lib for that, I guess.

There's a common confusion between a cache folder and a temporary folder. A cache folder uses a caching algorithm to handle its content (LRU for my library). A temporary folder is just a folder. If you download directly the images in the cache folder, then my library is of no use to you.
 

guidoarfini

Member
Licensed User
Longtime User
Hello Guys!
With this is library can i get chace only my application or can i get any cache list stored in my phone?

I would like to create a mini application to save and transport the advances of my games in another phone without using root permissions.
 

Informatix

Expert
Licensed User
Longtime User
Hello Guys!
With this is library can i get chace only my application or can i get any cache list stored in my phone?

I would like to create a mini application to save and transport the advances of my games in another phone without using root permissions.
What you call "cache" is just the private folder of an application, so it has nothing to do with this library.
And your project cannot work without being root because of the rights on this private folder.
 
Top