Faster, ThreadSafe, TTL supported Cache

  1. LinkedHashMap
package com.example.myapplication.cache;

public class CacheManager {

private static CacheManager mInstance;
private MemoryCache memoryCache;
public static int prefetchThreshold = 100;
public static int expiryThreshold = 120;

private CacheManager() {
memoryCache = new MemoryCache();
DBCache dbCache = new DBCache();
DiskCache diskCache = new DiskCache();
NetworkData networkData = new NetworkData();
memoryCache.setNextChain(dbCache);
memoryCache.setNetworkFetcher(networkData);
dbCache.setNextChain(diskCache);
diskCache.setNextChain(networkData);
}

public static CacheManager getInstance(){
if(mInstance == null){
synchronized (CacheManager.class){
if(mInstance == null){
mInstance = new CacheManager();
}
}
}
return mInstance;
}

public CacheValue getEntry(String key){
return memoryCache.getEntry(key);
}

public void putEntry(String key, CacheValue val){
memoryCache.putEntry(key, val);
}

}
package com.example.myapplication.cache;

import android.graphics.Bitmap;

public class CacheValue {

public Bitmap bm;
public long timeStamp;

CacheValue(Bitmap bm, long timeStamp) {

}

public int getSize() {
return 1;
}
}
package com.example.myapplication.cache;

public interface CacheDataFetcher{
void setNextChain(CacheDataFetcher dataFetcher);
CacheValue getEntry(String key);
void deleteEntry(String key);
void updateEntry(String key, CacheValue cv);
void AddEntryIfNotPresent(String key, CacheValue cv);
}
package com.example.myapplication.cache;

import com.example.myapplication.AppExecutors;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MemoryCache extends LinkedHashMap<String, CacheValue> implements CacheDataFetcher {

private CacheDataFetcher nextChain;
private CacheDataFetcher networkFetcher;
private int maxSize = 2000;
private int currentSize = 0;
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public MemoryCache() {
super(10, 0.75f, true);
}

@Override
public void setNextChain(CacheDataFetcher dataFetcher) {
this.nextChain = dataFetcher;
}

public void setNetworkFetcher(CacheDataFetcher networkFetcher) {
this.networkFetcher = networkFetcher;
}

@Override
protected boolean removeEldestEntry(Map.Entry<String, CacheValue> eldest) {
return currentSize > maxSize;
}

public void putEntry(String key, CacheValue val) {
try {
lock.writeLock().lock();
if (this.containsKey(key)) {
CacheValue value = this.get(key);
if (value != null)
currentSize -= value.getSize();
}
currentSize += val.getSize();
this.put(key, val); //here Linkedhashmap will call removeEldestEntry and removed eldest entry if size exceeds
} catch (Exception ex) {
lock.writeLock().unlock();
} finally {
lock.writeLock().unlock();
}
}

@Override
public CacheValue getEntry(String key) {
try {
lock.readLock().lock();
CacheValue val = this.get(key);
if (val != null && val.bm != null) {
if(System.currentTimeMillis() - val.timeStamp < CacheManager.expiryThreshold) {
checkPrefetchData(val, key);
lock.readLock().unlock();
return val;
}else{
lock.writeLock().lock();
this.remove(key);
nextChain.deleteEntry(key);
lock.writeLock().unlock();
}
}
CacheValue cv2 = nextChain.getEntry(key);
putEntry(key, cv2);
checkPrefetchData(val, key);
lock.readLock().unlock();
return cv2 ;
} catch (Exception ex) {
lock.readLock().unlock();
lock.writeLock().unlock();
} finally {
lock.readLock().unlock();
}
return null;
}

public void checkPrefetchData(CacheValue val, String key){
if (System.currentTimeMillis() - val.timeStamp > CacheManager.prefetchThreshold) {
AppExecutors.getInstance().getIoDispatcherDispatcher().execute(() -> {
CacheValue cv1 = networkFetcher.getEntry(key);
nextChain.updateEntry(key, cv1);
putEntry(key,cv1);
});
}else{
nextChain.AddEntryIfNotPresent(key, cv1);
} }
}
package com.example.myapplication.cache;

import com.example.myapplication.AppExecutors;

public class DBCache implements CacheDataFetcher{

private CacheDataFetcher nextChain;

@Override
public void setNextChain(CacheDataFetcher dataFetcher) {
this.nextChain = dataFetcher;
}

@Override
public CacheValue getEntry(String key) {
CacheValue val = null;
//val = do some db query if val is null then call next chain
if (val != null && val.bm != null) {
if (System.currentTimeMillis() - val.timeStamp < CacheManager.expiryThreshold) {
return val;
} else {
nextchain.deleteEntry(key); AppExecutors.getInstance().getIoDispatcherDispatcher().execute(()->{
deleteFile(key);
});
}
}
return nextChain.getEntry(key);
}

public void deleteFile(String key){
//do file deletion here from DB
}
public void deleteEntry(String key){
deleteFile(key);
nextChain.deleteEntry(key);
}

public void updateEntry(String key, CacheValue cv){
// update DB with cache value of key
nextChain.updateEntry(key, cv)
}
public void AddEntryIfNotPresent(String key, CacheValue cv){
// add entry in DB file here
nextChain.AddEntryIfNotPresent(key, cv); }}
package com.example.myapplication.cache;

import com.example.myapplication.AppExecutors;

import java.io.File;

public class DiskCache implements CacheDataFetcher{

private CacheDataFetcher nextChain;

@Override
public void setNextChain(CacheDataFetcher dataFetcher) {
this.nextChain = dataFetcher;
}

@Override
public CacheValue getEntry(String key) {
File f = new File(key);
if (f.exists()) {
CacheValue val = null;
//val = Do file operation to get the data
if (val != null && val.bm != null) {
if (System.currentTimeMillis() - val.timeStamp < CacheManager.expiryThreshold) {
return val;
} else {
nextChain.deleteEntry(key); AppExecutors.getInstance().getIoDispatcherDispatcher().execute(()->{
deleteFile(key, f);
});

}
}
}
return nextChain.getEntry(key);
}

public synchronized void deleteFile(String key, File f){
if(f.exists()) {
//do file deletion here
}
}
public void deleteEntry(String key){
deleteFile(key);
nextChain.deleteEntry(key);
}

public void updateEntry(String key, CacheValue cv){
// update Diskcache with cache value of key
nextChain.updateEntry(key, cv);
}
public void AddEntryIfNotPresent(String key, CacheValue cv){
// add entry in Disk file here
nextChain.AddEntryIfNotPresent(key, cv); }}
package com.example.myapplication.cache;

import android.graphics.Bitmap;

public class NetworkData implements CacheDataFetcher{

private CacheDataFetcher nextChain;
@Override
public void setNextChain(CacheDataFetcher dataFetcher) {
this.nextChain = dataFetcher;
}

@Override
public CacheValue getEntry(String key) {
Bitmap bm = null;
// bm = do some network operation and replace null value
return new CacheValue(bm, System.currentTimeMillis());
}
public void deleteEntry(String key){
// No need to do anything on network
}

public void updateEntry(String key, CacheValue cv){
// no need to do anything on network
}
public void AddEntryIfNotPresent(String key, CacheValue cv){
// nothing required here
}}
package com.example.myapplication;

import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class AppExecutors {

private static AppExecutors mInstance;
private final Executor ioDispatcher;//Network,Disk,DB access
private final Executor cpuDispatcher;//CPU consumtion(Sorting)
private final Executor mainDispatcher;

private AppExecutors(){
cpuDispatcher = Executors.newSingleThreadExecutor();
ioDispatcher = Executors.newFixedThreadPool(4);
mainDispatcher = new MainDispatcher();

}

public static AppExecutors getInstance(){
if(mInstance == null){
synchronized (AppExecutors.class){
if(mInstance == null){
mInstance = new AppExecutors();
}
}
}
return mInstance;
}
// Most Thread time goes into waiting , don't consume much CPU
//no of threads may increase optimally since more thread take memory
public Executor getIoDispatcherDispatcher(){
return ioDispatcher;
}
// CPU consumption is more , so take 1-2 threads for this part
public Executor getCPUDispatcher(){
return cpuDispatcher;
}

public Executor getMainDispatcher(){
return mainDispatcher;
}

private static class MainDispatcher implements Executor{
private Handler mainThreadHandler = HandlerCompat.createAsync(Looper.getMainLooper());
@Override
public void execute(Runnable command) {
mainThreadHandler.post(command);
}
}
}

--

--

--

Principal Software Engineer , Mobile Engineering

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Continuously check Internet Connection and connection type

How to Create Custom Toast in Android

Building a Live-tracking App using Google Maps in Android

Checking Network Connectivity In Flutter

GetX: Flutter State Managment

Clarity

SIMEXTRACK Version 2 !!!

Matt’s Tidbits #79 — RxLifecycle woes

Boost your Firebase Performance reports with custom traces

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Amit Gupta

Amit Gupta

Principal Software Engineer , Mobile Engineering

More from Medium

Benefits of Modernizing .NET Workloads on AWS

XS ADMIN/ COCKPIT_ADMIN User locked?

Troubleshooting 101: Client Email Issues

A magnifying glass is held over the keyboard of a MacBook

Event Planning Tactics for 2022