Last active
February 29, 2020 19:39
-
-
Save VladSumtsov/c4af1f4b8fe5099ca809 to your computer and use it in GitHub Desktop.
Cache your data with parcelable and disklrucache
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
public interface DiskCache<T> { | |
/** | |
* Sets the value to {@code value}. | |
*/ | |
public void set(String key, T value); | |
/** | |
* Returns a value by {@code key}, or null if it doesn't | |
* exist is not currently readable. If a value is returned, it is moved to | |
* the head of the LRU queue. | |
*/ | |
public T get(String key); | |
/** | |
* Drops the entry for {@code key} if it exists and can be removed. Entries | |
* actively being edited cannot be removed. | |
* | |
* @return true if an entry was removed. | |
*/ | |
public boolean remove(String key); | |
/** | |
* Returns all values from cache directory if all files are same type | |
* | |
* @return | |
*/ | |
public List<T> getAll(); | |
/** | |
* Deletes all file from cache directory | |
*/ | |
public void clear(); | |
/** | |
* Returns true if there is file by {@code key} in cache folder | |
* | |
* @param key | |
* @return | |
*/ | |
public boolean exists(String key); | |
/** | |
* Closes this cache. Stored values will remain on the filesystem. | |
*/ | |
public void close(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.content.pm.PackageInfo; | |
import android.content.pm.PackageManager; | |
import android.os.Build; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
import android.text.TextUtils; | |
import com.jakewharton.disklrucache.DiskLruCache; | |
import java.io.ByteArrayOutputStream; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.concurrent.Executor; | |
import java.util.concurrent.Executors; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
final public class ParcelDiskCache<T extends Parcelable> implements DiskCache<Parcelable> { | |
private static final String LIST = "list"; | |
private static final String PARCELABLE = "parcelable"; | |
private static final String VALIDATE_KEY_REGEX = "[a-z0-9_-]{1,5}"; | |
private static final int MAX_KEY_SYMBOLS = 62; | |
private ClassLoader classLoader; | |
private DiskLruCache cache; | |
private Executor storeExecutor; | |
private boolean saveInUI = true; | |
private ParcelDiskCache(Context context, ClassLoader classLoader, String name, long maxSize) throws IOException { | |
File cacheDir = context.getExternalCacheDir(); | |
if (cacheDir == null) { | |
cacheDir = context.getCacheDir(); | |
} | |
this.classLoader = classLoader; | |
storeExecutor = Executors.newSingleThreadExecutor(); | |
File dir = new File(cacheDir, name); | |
int version = getVersionCode(context) + Build.VERSION.SDK_INT; | |
this.cache = DiskLruCache.open(dir, version, 1, maxSize); | |
} | |
public static ParcelDiskCache open(Context context, ClassLoader classLoader, String name, long maxSize) throws IOException { | |
return new ParcelDiskCache(context, classLoader, name, maxSize); | |
} | |
public void set(String key, Parcelable value) { | |
key = validateKey(key); | |
Parcel parcel = Parcel.obtain(); | |
parcel.writeString(PARCELABLE); | |
parcel.writeParcelable(value, 0); | |
if (saveInUI) { | |
saveValue(cache, parcel, key); | |
} else { | |
storeExecutor.execute(new StoreParcelableValueTask(cache, parcel, key)); | |
} | |
} | |
public void set(String key, List<T> values) { | |
key = validateKey(key); | |
Parcel parcel = Parcel.obtain(); | |
parcel.writeString(LIST); | |
parcel.writeList(values); | |
if (saveInUI) { | |
saveValue(cache, parcel, key); | |
} else { | |
storeExecutor.execute(new StoreParcelableValueTask(cache, parcel, key)); | |
} | |
} | |
public T get(String key) { | |
key = validateKey(key); | |
Parcel parcel = getParcel(key); | |
if (parcel != null) { | |
try { | |
String type = parcel.readString(); | |
if (type.equals(LIST)) { | |
throw new IllegalAccessError("get list data with getList method"); | |
} | |
if (type != null && !type.equals(PARCELABLE)) { | |
throw new IllegalAccessError("Parcel doesn't contain parcelable data"); | |
} | |
return parcel.readParcelable(classLoader); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
parcel.recycle(); | |
} | |
} | |
return null; | |
} | |
private Parcel getParcel(String key) { | |
key = validateKey(key); | |
byte[] value = null; | |
DiskLruCache.Snapshot snapshot = null; | |
try { | |
snapshot = cache.get(key); | |
if (snapshot == null) { | |
return null; | |
} | |
value = getBytesFromStream(snapshot.getInputStream(0)); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
if (snapshot != null) { | |
snapshot.close(); | |
} | |
} | |
Parcel parcel = Parcel.obtain(); | |
parcel.unmarshall(value, 0, value.length); | |
parcel.setDataPosition(0); | |
return parcel; | |
} | |
private String validateKey(String key) { | |
Matcher keyMatcher = getPattern(VALIDATE_KEY_REGEX).matcher(key); | |
StringBuilder newKey = new StringBuilder(); | |
while (keyMatcher.find()) { | |
String group = keyMatcher.group(); | |
if (newKey.length() + group.length() > MAX_KEY_SYMBOLS) { | |
break; | |
} | |
newKey.append(group); | |
} | |
return newKey.toString().toLowerCase(); | |
} | |
public Pattern getPattern(String bodyRegex) { | |
int flags = Pattern.MULTILINE | Pattern.DOTALL | Pattern.CASE_INSENSITIVE; | |
Pattern pattern = Pattern.compile(bodyRegex, flags); | |
return pattern; | |
} | |
public List<T> getList(String key, Class itemClass) { | |
key = validateKey(key); | |
ArrayList<T> res = new ArrayList<T>(); | |
Parcel parcel = getParcel(key); | |
if (parcel != null) { | |
try { | |
String type = parcel.readString(); | |
if (type.equals(PARCELABLE)) { | |
throw new IllegalAccessError("Get not a list data with get method"); | |
} | |
if (type != null && !type.equals(LIST)) { | |
throw new IllegalAccessError("Parcel doesn't contain list data"); | |
} | |
parcel.readList(res, itemClass != null ? itemClass.getClassLoader() : ArrayList.class.getClassLoader()); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
parcel.recycle(); | |
} | |
} | |
return res; | |
} | |
public List<T> getList(String key) { | |
return getList(key, null); | |
} | |
public boolean remove(String key) { | |
key = validateKey(key); | |
try { | |
return cache.remove(key.toLowerCase()); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
return false; | |
} | |
public List getAll() { | |
return getAll(null); | |
} | |
public List getAll(String preffix) { | |
List<T> list = new ArrayList<T>(1); | |
File dir = cache.getDirectory(); | |
File[] files = dir.listFiles(); | |
if (files != null) { | |
list = new ArrayList<T>(files.length); | |
for (File file : files) { | |
String fileName = file.getName(); | |
if ((!TextUtils.isEmpty(preffix) && fileName.startsWith(preffix) && fileName.indexOf(".") > 0) | |
|| (TextUtils.isEmpty(preffix) && fileName.indexOf(".") > 0)) { | |
String key = fileName.substring(0, fileName.indexOf(".")); | |
T value = get(key); | |
list.add(value); | |
} | |
} | |
} | |
return list; | |
} | |
public void clear() { | |
try { | |
cache.delete(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
public boolean exists(String key) { | |
key = validateKey(key); | |
DiskLruCache.Snapshot snapshot = null; | |
try { | |
snapshot = cache.get(key.toLowerCase()); | |
if (snapshot == null) { | |
return false; | |
} | |
return snapshot.getLength(0) > 0; | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
if (snapshot != null) { | |
snapshot.close(); | |
} | |
} | |
return false; | |
} | |
@Override | |
public void close() { | |
try { | |
cache.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
public void shouldSaveInUI() { | |
this.saveInUI = true; | |
} | |
private static class StoreParcelableValueTask implements Runnable { | |
private final DiskLruCache cache; | |
private final Parcel value; | |
private final String key; | |
public StoreParcelableValueTask(DiskLruCache cache, Parcel value, String key) { | |
this.value = value; | |
this.key = key; | |
this.cache = cache; | |
} | |
@Override | |
public void run() { | |
saveValue(cache, value, key); | |
} | |
} | |
private static void saveValue(DiskLruCache cache, Parcel value, String key) { | |
if (cache == null) return; | |
key = key.toLowerCase(); | |
try { | |
final String skey = key.intern(); | |
synchronized (skey) { | |
DiskLruCache.Editor editor = cache.edit(key); | |
OutputStream outputStream = editor.newOutputStream(0); | |
writeBytesToStream(outputStream, value.marshall()); | |
editor.commit(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
value.recycle(); | |
} | |
} | |
public static byte[] getBytesFromStream(InputStream is) throws IOException { | |
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | |
try { | |
byte[] data = new byte[1024]; | |
int count; | |
while ((count = is.read(data, 0, data.length)) != -1) { | |
buffer.write(data, 0, count); | |
} | |
buffer.flush(); | |
return buffer.toByteArray(); | |
} finally { | |
is.close(); | |
buffer.close(); | |
} | |
} | |
public static void writeBytesToStream(OutputStream outputStream, byte[] bytes) throws IOException { | |
outputStream.write(bytes); | |
outputStream.flush(); | |
outputStream.close(); | |
} | |
public static int getVersionCode(Context context) { | |
int result = 0; | |
try { | |
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); | |
result = pInfo.versionCode; | |
} catch (PackageManager.NameNotFoundException e) { | |
e.printStackTrace(); | |
} | |
return result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment