+ *
+ * @param maxSize The max size of cache, in bytes.
+ * @param maxCount The max count of cache.
+ * @return the single {@link CacheDiskUtils} instance
+ */
+ public static CacheDiskUtils getInstance(final long maxSize, final int maxCount) {
+ return getInstance("", maxSize, maxCount);
+ }
+
+ /**
+ * Return the single {@link CacheDiskUtils} instance.
+ *
+ *
+ * @param cacheName The name of cache.
+ * @param maxSize The max size of cache, in bytes.
+ * @param maxCount The max count of cache.
+ * @return the single {@link CacheDiskUtils} instance
+ */
+ public static CacheDiskUtils getInstance(String cacheName, final long maxSize, final int maxCount) {
+ if (isSpace(cacheName)) cacheName = "cacheUtils";
+ File file = new File(getApp().getCacheDir(), cacheName);
+ return getInstance(file, maxSize, maxCount);
+ }
+
+ /**
+ * Return the single {@link CacheDiskUtils} instance.
+ *
cache size: unlimited
+ *
cache count: unlimited
+ *
+ * @param cacheDir The directory of cache.
+ * @return the single {@link CacheDiskUtils} instance
+ */
+ public static CacheDiskUtils getInstance(@NonNull final File cacheDir) {
+ return getInstance(cacheDir, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT);
+ }
+
+ /**
+ * Return the single {@link CacheDiskUtils} instance.
+ *
+ * @param cacheDir The directory of cache.
+ * @param maxSize The max size of cache, in bytes.
+ * @param maxCount The max count of cache.
+ * @return the single {@link CacheDiskUtils} instance
+ */
+ public static CacheDiskUtils getInstance(@NonNull final File cacheDir,
+ final long maxSize,
+ final int maxCount) {
+ final String cacheKey = cacheDir.getAbsoluteFile() + "_" + maxSize + "_" + maxCount;
+ CacheDiskUtils cache = CACHE_MAP.get(cacheKey);
+ if (cache == null) {
+ synchronized (CacheDiskUtils.class) {
+ cache = CACHE_MAP.get(cacheKey);
+ if (cache == null) {
+ cache = new CacheDiskUtils(cacheKey, cacheDir, maxSize, maxCount);
+ CACHE_MAP.put(cacheKey, cache);
+ }
+ }
+ }
+ return cache;
+ }
+
+ private CacheDiskUtils(final String cacheKey,
+ final File cacheDir,
+ final long maxSize,
+ final int maxCount) {
+ mCacheKey = cacheKey;
+ mCacheDir = cacheDir;
+ mMaxSize = maxSize;
+ mMaxCount = maxCount;
+ }
+
+ private DiskCacheManager getDiskCacheManager() {
+ if (mCacheDir.exists()) {
+ if (mDiskCacheManager == null) {
+ mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount);
+ }
+ } else {
+ if (mCacheDir.mkdirs()) {
+ mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount);
+ } else {
+ Log.e("CacheDiskUtils", "can't make dirs in " + mCacheDir.getAbsolutePath());
+ }
+ }
+ return mDiskCacheManager;
+ }
+
+ @Override
+ public String toString() {
+ return mCacheKey + "@" + Integer.toHexString(hashCode());
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // about bytes
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Put bytes in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ */
+ public void put(@NonNull final String key, final byte[] value) {
+ put(key, value, -1);
+ }
+
+ /**
+ * Put bytes in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ * @param saveTime The save time of cache, in seconds.
+ */
+ public void put(@NonNull final String key, final byte[] value, final int saveTime) {
+ realPutBytes(TYPE_BYTE + key, value, saveTime);
+ }
+
+ private void realPutBytes(final String key, byte[] value, int saveTime) {
+ if (value == null) return;
+ DiskCacheManager diskCacheManager = getDiskCacheManager();
+ if (diskCacheManager == null) return;
+ if (saveTime >= 0) value = DiskCacheHelper.newByteArrayWithTime(saveTime, value);
+ File file = diskCacheManager.getFileBeforePut(key);
+ writeFileFromBytes(file, value);
+ diskCacheManager.updateModify(file);
+ diskCacheManager.put(file);
+ }
+
+
+ /**
+ * Return the bytes in cache.
+ *
+ * @param key The key of cache.
+ * @return the bytes if cache exists or null otherwise
+ */
+ public byte[] getBytes(@NonNull final String key) {
+ return getBytes(key, null);
+ }
+
+ /**
+ * Return the bytes in cache.
+ *
+ * @param key The key of cache.
+ * @param defaultValue The default value if the cache doesn't exist.
+ * @return the bytes if cache exists or defaultValue otherwise
+ */
+ public byte[] getBytes(@NonNull final String key, final byte[] defaultValue) {
+ return realGetBytes(TYPE_BYTE + key, defaultValue);
+ }
+
+ private byte[] realGetBytes(@NonNull final String key) {
+ return realGetBytes(key, null);
+ }
+
+ private byte[] realGetBytes(@NonNull final String key, final byte[] defaultValue) {
+ DiskCacheManager diskCacheManager = getDiskCacheManager();
+ if (diskCacheManager == null) return defaultValue;
+ final File file = diskCacheManager.getFileIfExists(key);
+ if (file == null) return defaultValue;
+ byte[] data = readFile2Bytes(file);
+ if (DiskCacheHelper.isDue(data)) {
+ diskCacheManager.removeByKey(key);
+ return defaultValue;
+ }
+ diskCacheManager.updateModify(file);
+ return DiskCacheHelper.getDataWithoutDueTime(data);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // about String
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Put string value in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ */
+ public void put(@NonNull final String key, final String value) {
+ put(key, value, -1);
+ }
+
+ /**
+ * Put string value in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ * @param saveTime The save time of cache, in seconds.
+ */
+ public void put(@NonNull final String key, final String value, final int saveTime) {
+ realPutBytes(TYPE_STRING + key, string2Bytes(value), saveTime);
+ }
+
+ /**
+ * Return the string value in cache.
+ *
+ * @param key The key of cache.
+ * @return the string value if cache exists or null otherwise
+ */
+ public String getString(@NonNull final String key) {
+ return getString(key, null);
+ }
+
+ /**
+ * Return the string value in cache.
+ *
+ * @param key The key of cache.
+ * @param defaultValue The default value if the cache doesn't exist.
+ * @return the string value if cache exists or defaultValue otherwise
+ */
+ public String getString(@NonNull final String key, final String defaultValue) {
+ byte[] bytes = realGetBytes(TYPE_STRING + key);
+ if (bytes == null) return defaultValue;
+ return bytes2String(bytes);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // about JSONObject
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Put JSONObject in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ */
+ public void put(@NonNull final String key, final JSONObject value) {
+ put(key, value, -1);
+ }
+
+ /**
+ * Put JSONObject in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ * @param saveTime The save time of cache, in seconds.
+ */
+ public void put(@NonNull final String key,
+ final JSONObject value,
+ final int saveTime) {
+ realPutBytes(TYPE_JSON_OBJECT + key, jsonObject2Bytes(value), saveTime);
+ }
+
+ /**
+ * Return the JSONObject in cache.
+ *
+ * @param key The key of cache.
+ * @return the JSONObject if cache exists or null otherwise
+ */
+ public JSONObject getJSONObject(@NonNull final String key) {
+ return getJSONObject(key, null);
+ }
+
+ /**
+ * Return the JSONObject in cache.
+ *
+ * @param key The key of cache.
+ * @param defaultValue The default value if the cache doesn't exist.
+ * @return the JSONObject if cache exists or defaultValue otherwise
+ */
+ public JSONObject getJSONObject(@NonNull final String key, final JSONObject defaultValue) {
+ byte[] bytes = realGetBytes(TYPE_JSON_OBJECT + key);
+ if (bytes == null) return defaultValue;
+ return bytes2JSONObject(bytes);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // about JSONArray
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Put JSONArray in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ */
+ public void put(@NonNull final String key, final JSONArray value) {
+ put(key, value, -1);
+ }
+
+ /**
+ * Put JSONArray in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ * @param saveTime The save time of cache, in seconds.
+ */
+ public void put(@NonNull final String key, final JSONArray value, final int saveTime) {
+ realPutBytes(TYPE_JSON_ARRAY + key, jsonArray2Bytes(value), saveTime);
+ }
+
+ /**
+ * Return the JSONArray in cache.
+ *
+ * @param key The key of cache.
+ * @return the JSONArray if cache exists or null otherwise
+ */
+ public JSONArray getJSONArray(@NonNull final String key) {
+ return getJSONArray(key, null);
+ }
+
+ /**
+ * Return the JSONArray in cache.
+ *
+ * @param key The key of cache.
+ * @param defaultValue The default value if the cache doesn't exist.
+ * @return the JSONArray if cache exists or defaultValue otherwise
+ */
+ public JSONArray getJSONArray(@NonNull final String key, final JSONArray defaultValue) {
+ byte[] bytes = realGetBytes(TYPE_JSON_ARRAY + key);
+ if (bytes == null) return defaultValue;
+ return bytes2JSONArray(bytes);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // about Bitmap
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Put bitmap in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ */
+ public void put(@NonNull final String key, final Bitmap value) {
+ put(key, value, -1);
+ }
+
+ /**
+ * Put bitmap in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ * @param saveTime The save time of cache, in seconds.
+ */
+ public void put(@NonNull final String key, final Bitmap value, final int saveTime) {
+ realPutBytes(TYPE_BITMAP + key, bitmap2Bytes(value), saveTime);
+ }
+
+ /**
+ * Return the bitmap in cache.
+ *
+ * @param key The key of cache.
+ * @return the bitmap if cache exists or null otherwise
+ */
+ public Bitmap getBitmap(@NonNull final String key) {
+ return getBitmap(key, null);
+ }
+
+ /**
+ * Return the bitmap in cache.
+ *
+ * @param key The key of cache.
+ * @param defaultValue The default value if the cache doesn't exist.
+ * @return the bitmap if cache exists or defaultValue otherwise
+ */
+ public Bitmap getBitmap(@NonNull final String key, final Bitmap defaultValue) {
+ byte[] bytes = realGetBytes(TYPE_BITMAP + key);
+ if (bytes == null) return defaultValue;
+ return bytes2Bitmap(bytes);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // about Drawable
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Put drawable in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ */
+ public void put(@NonNull final String key, final Drawable value) {
+ put(key, value, -1);
+ }
+
+ /**
+ * Put drawable in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ * @param saveTime The save time of cache, in seconds.
+ */
+ public void put(@NonNull final String key, final Drawable value, final int saveTime) {
+ realPutBytes(TYPE_DRAWABLE + key, drawable2Bytes(value), saveTime);
+ }
+
+ /**
+ * Return the drawable in cache.
+ *
+ * @param key The key of cache.
+ * @return the drawable if cache exists or null otherwise
+ */
+ public Drawable getDrawable(@NonNull final String key) {
+ return getDrawable(key, null);
+ }
+
+ /**
+ * Return the drawable in cache.
+ *
+ * @param key The key of cache.
+ * @param defaultValue The default value if the cache doesn't exist.
+ * @return the drawable if cache exists or defaultValue otherwise
+ */
+ public Drawable getDrawable(@NonNull final String key, final Drawable defaultValue) {
+ byte[] bytes = realGetBytes(TYPE_DRAWABLE + key);
+ if (bytes == null) return defaultValue;
+ return bytes2Drawable(bytes);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // about Parcelable
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Put parcelable in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ */
+ public void put(@NonNull final String key, final Parcelable value) {
+ put(key, value, -1);
+ }
+
+ /**
+ * Put parcelable in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ * @param saveTime The save time of cache, in seconds.
+ */
+ public void put(@NonNull final String key, final Parcelable value, final int saveTime) {
+ realPutBytes(TYPE_PARCELABLE + key, parcelable2Bytes(value), saveTime);
+ }
+
+ /**
+ * Return the parcelable in cache.
+ *
+ * @param key The key of cache.
+ * @param creator The creator.
+ * @param The value type.
+ * @return the parcelable if cache exists or null otherwise
+ */
+ public T getParcelable(@NonNull final String key,
+ @NonNull final Parcelable.Creator creator) {
+ return getParcelable(key, creator, null);
+ }
+
+ /**
+ * Return the parcelable in cache.
+ *
+ * @param key The key of cache.
+ * @param creator The creator.
+ * @param defaultValue The default value if the cache doesn't exist.
+ * @param The value type.
+ * @return the parcelable if cache exists or defaultValue otherwise
+ */
+ public T getParcelable(@NonNull final String key,
+ @NonNull final Parcelable.Creator creator,
+ final T defaultValue) {
+ byte[] bytes = realGetBytes(TYPE_PARCELABLE + key);
+ if (bytes == null) return defaultValue;
+ return bytes2Parcelable(bytes, creator);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // about Serializable
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Put serializable in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ */
+ public void put(@NonNull final String key, final Serializable value) {
+ put(key, value, -1);
+ }
+
+ /**
+ * Put serializable in cache.
+ *
+ * @param key The key of cache.
+ * @param value The value of cache.
+ * @param saveTime The save time of cache, in seconds.
+ */
+ public void put(@NonNull final String key, final Serializable value, final int saveTime) {
+ realPutBytes(TYPE_SERIALIZABLE + key, serializable2Bytes(value), saveTime);
+ }
+
+ /**
+ * Return the serializable in cache.
+ *
+ * @param key The key of cache.
+ * @return the bitmap if cache exists or null otherwise
+ */
+ public Object getSerializable(@NonNull final String key) {
+ return getSerializable(key, null);
+ }
+
+ /**
+ * Return the serializable in cache.
+ *
+ * @param key The key of cache.
+ * @param defaultValue The default value if the cache doesn't exist.
+ * @return the bitmap if cache exists or defaultValue otherwise
+ */
+ public Object getSerializable(@NonNull final String key, final Object defaultValue) {
+ byte[] bytes = realGetBytes(TYPE_SERIALIZABLE + key);
+ if (bytes == null) return defaultValue;
+ return bytes2Object(bytes);
+ }
+
+ /**
+ * Return the size of cache, in bytes.
+ *
+ * @return the size of cache, in bytes
+ */
+ public long getCacheSize() {
+ DiskCacheManager diskCacheManager = getDiskCacheManager();
+ if (diskCacheManager == null) return 0;
+ return diskCacheManager.getCacheSize();
+ }
+
+ /**
+ * Return the count of cache.
+ *
+ * @return the count of cache
+ */
+ public int getCacheCount() {
+ DiskCacheManager diskCacheManager = getDiskCacheManager();
+ if (diskCacheManager == null) return 0;
+ return diskCacheManager.getCacheCount();
+ }
+
+ /**
+ * Remove the cache by key.
+ *
+ * @param key The key of cache.
+ * @return {@code true}: success {@code false}: fail
+ */
+ public boolean remove(@NonNull final String key) {
+ DiskCacheManager diskCacheManager = getDiskCacheManager();
+ if (diskCacheManager == null) return true;
+ return diskCacheManager.removeByKey(TYPE_BYTE + key)
+ && diskCacheManager.removeByKey(TYPE_STRING + key)
+ && diskCacheManager.removeByKey(TYPE_JSON_OBJECT + key)
+ && diskCacheManager.removeByKey(TYPE_JSON_ARRAY + key)
+ && diskCacheManager.removeByKey(TYPE_BITMAP + key)
+ && diskCacheManager.removeByKey(TYPE_DRAWABLE + key)
+ && diskCacheManager.removeByKey(TYPE_PARCELABLE + key)
+ && diskCacheManager.removeByKey(TYPE_SERIALIZABLE + key);
+ }
+
+ /**
+ * Clear all of the cache.
+ *
+ * @return {@code true}: success {@code false}: fail
+ */
+ public boolean clear() {
+ DiskCacheManager diskCacheManager = getDiskCacheManager();
+ if (diskCacheManager == null) return true;
+ return diskCacheManager.clear();
+ }
+
+ private static final class DiskCacheManager {
+ private final AtomicLong cacheSize;
+ private final AtomicInteger cacheCount;
+ private final long sizeLimit;
+ private final int countLimit;
+ private final Map lastUsageDates
+ = Collections.synchronizedMap(new HashMap());
+ private final File cacheDir;
+ private final Thread mThread;
+
+ private DiskCacheManager(final File cacheDir, final long sizeLimit, final int countLimit) {
+ this.cacheDir = cacheDir;
+ this.sizeLimit = sizeLimit;
+ this.countLimit = countLimit;
+ cacheSize = new AtomicLong();
+ cacheCount = new AtomicInteger();
+ mThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int size = 0;
+ int count = 0;
+ final File[] cachedFiles = cacheDir.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.startsWith(CACHE_PREFIX);
+ }
+ });
+ if (cachedFiles != null) {
+ for (File cachedFile : cachedFiles) {
+ size += cachedFile.length();
+ count += 1;
+ lastUsageDates.put(cachedFile, cachedFile.lastModified());
+ }
+ cacheSize.getAndAdd(size);
+ cacheCount.getAndAdd(count);
+ }
+ }
+ });
+ mThread.start();
+ }
+
+ private long getCacheSize() {
+ wait2InitOk();
+ return cacheSize.get();
+ }
+
+ private int getCacheCount() {
+ wait2InitOk();
+ return cacheCount.get();
+ }
+
+ private File getFileBeforePut(final String key) {
+ wait2InitOk();
+ File file = new File(cacheDir, getCacheNameByKey(key));
+ if (file.exists()) {
+ cacheCount.addAndGet(-1);
+ cacheSize.addAndGet(-file.length());
+ }
+ return file;
+ }
+
+ private void wait2InitOk() {
+ try {
+ mThread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private File getFileIfExists(final String key) {
+ File file = new File(cacheDir, getCacheNameByKey(key));
+ if (!file.exists()) return null;
+ return file;
+ }
+
+ private String getCacheNameByKey(final String key) {
+ return CACHE_PREFIX + key.substring(0, 3) + key.substring(3).hashCode();
+ }
+
+ private void put(final File file) {
+ cacheCount.addAndGet(1);
+ cacheSize.addAndGet(file.length());
+ while (cacheCount.get() > countLimit || cacheSize.get() > sizeLimit) {
+ cacheSize.addAndGet(-removeOldest());
+ cacheCount.addAndGet(-1);
+ }
+ }
+
+ private void updateModify(final File file) {
+ Long millis = System.currentTimeMillis();
+ file.setLastModified(millis);
+ lastUsageDates.put(file, millis);
+ }
+
+ private boolean removeByKey(final String key) {
+ File file = getFileIfExists(key);
+ if (file == null) return true;
+ if (!file.delete()) return false;
+ cacheSize.addAndGet(-file.length());
+ cacheCount.addAndGet(-1);
+ lastUsageDates.remove(file);
+ return true;
+ }
+
+ private boolean clear() {
+ File[] files = cacheDir.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.startsWith(CACHE_PREFIX);
+ }
+ });
+ if (files == null || files.length <= 0) return true;
+ boolean flag = true;
+ for (File file : files) {
+ if (!file.delete()) {
+ flag = false;
+ continue;
+ }
+ cacheSize.addAndGet(-file.length());
+ cacheCount.addAndGet(-1);
+ lastUsageDates.remove(file);
+ }
+ if (flag) {
+ lastUsageDates.clear();
+ cacheSize.set(0);
+ cacheCount.set(0);
+ }
+ return flag;
+ }
+
+ /**
+ * Remove the oldest files.
+ *
+ * @return the size of oldest files, in bytes
+ */
+ private long removeOldest() {
+ if (lastUsageDates.isEmpty()) return 0;
+ Long oldestUsage = Long.MAX_VALUE;
+ File oldestFile = null;
+ Set> entries = lastUsageDates.entrySet();
+ synchronized (lastUsageDates) {
+ for (Map.Entry entry : entries) {
+ Long lastValueUsage = entry.getValue();
+ if (lastValueUsage < oldestUsage) {
+ oldestUsage = lastValueUsage;
+ oldestFile = entry.getKey();
+ }
+ }
+ }
+ if (oldestFile == null) return 0;
+ long fileSize = oldestFile.length();
+ if (oldestFile.delete()) {
+ lastUsageDates.remove(oldestFile);
+ return fileSize;
+ }
+ return 0;
+ }
+ }
+
+ private static final class DiskCacheHelper {
+
+ static final int TIME_INFO_LEN = 14;
+
+ private static byte[] newByteArrayWithTime(final int second, final byte[] data) {
+ byte[] time = createDueTime(second).getBytes();
+ byte[] content = new byte[time.length + data.length];
+ System.arraycopy(time, 0, content, 0, time.length);
+ System.arraycopy(data, 0, content, time.length, data.length);
+ return content;
+ }
+
+ /**
+ * Return the string of due time.
+ *
+ * @param seconds The seconds.
+ * @return the string of due time
+ */
+ private static String createDueTime(final int seconds) {
+ return String.format(
+ Locale.getDefault(), "_$%010d$_",
+ System.currentTimeMillis() / 1000 + seconds
+ );
+ }
+
+ private static boolean isDue(final byte[] data) {
+ long millis = getDueTime(data);
+ return millis != -1 && System.currentTimeMillis() > millis;
+ }
+
+ private static long getDueTime(final byte[] data) {
+ if (hasTimeInfo(data)) {
+ String millis = new String(copyOfRange(data, 2, 12));
+ try {
+ return Long.parseLong(millis) * 1000;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+ return -1;
+ }
+
+ private static byte[] getDataWithoutDueTime(final byte[] data) {
+ if (hasTimeInfo(data)) {
+ return copyOfRange(data, TIME_INFO_LEN, data.length);
+ }
+ return data;
+ }
+
+ private static byte[] copyOfRange(final byte[] original, final int from, final int to) {
+ int newLength = to - from;
+ if (newLength < 0) throw new IllegalArgumentException(from + " > " + to);
+ byte[] copy = new byte[newLength];
+ System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+ return copy;
+ }
+
+ private static boolean hasTimeInfo(final byte[] data) {
+ return data != null
+ && data.length >= TIME_INFO_LEN
+ && data[0] == '_'
+ && data[1] == '$'
+ && data[12] == '$'
+ && data[13] == '_';
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // other utils methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static byte[] string2Bytes(final String string) {
+ if (string == null) return null;
+ return string.getBytes();
+ }
+
+ private static String bytes2String(final byte[] bytes) {
+ if (bytes == null) return null;
+ return new String(bytes);
+ }
+
+ private static byte[] jsonObject2Bytes(final JSONObject jsonObject) {
+ if (jsonObject == null) return null;
+ return jsonObject.toString().getBytes();
+ }
+
+ private static JSONObject bytes2JSONObject(final byte[] bytes) {
+ if (bytes == null) return null;
+ try {
+ return new JSONObject(new String(bytes));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static byte[] jsonArray2Bytes(final JSONArray jsonArray) {
+ if (jsonArray == null) return null;
+ return jsonArray.toString().getBytes();
+ }
+
+ private static JSONArray bytes2JSONArray(final byte[] bytes) {
+ if (bytes == null) return null;
+ try {
+ return new JSONArray(new String(bytes));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static byte[] parcelable2Bytes(final Parcelable parcelable) {
+ if (parcelable == null) return null;
+ Parcel parcel = Parcel.obtain();
+ parcelable.writeToParcel(parcel, 0);
+ byte[] bytes = parcel.marshall();
+ parcel.recycle();
+ return bytes;
+ }
+
+ private static T bytes2Parcelable(final byte[] bytes,
+ final Parcelable.Creator creator) {
+ if (bytes == null) return null;
+ Parcel parcel = Parcel.obtain();
+ parcel.unmarshall(bytes, 0, bytes.length);
+ parcel.setDataPosition(0);
+ T result = creator.createFromParcel(parcel);
+ parcel.recycle();
+ return result;
+ }
+
+ private static byte[] serializable2Bytes(final Serializable serializable) {
+ if (serializable == null) return null;
+ ByteArrayOutputStream baos;
+ ObjectOutputStream oos = null;
+ try {
+ oos = new ObjectOutputStream(baos = new ByteArrayOutputStream());
+ oos.writeObject(serializable);
+ return baos.toByteArray();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ try {
+ if (oos != null) {
+ oos.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static Object bytes2Object(final byte[] bytes) {
+ if (bytes == null) return null;
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ return ois.readObject();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ try {
+ if (ois != null) {
+ ois.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static byte[] bitmap2Bytes(final Bitmap bitmap) {
+ if (bitmap == null) return null;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+ return baos.toByteArray();
+ }
+
+ private static Bitmap bytes2Bitmap(final byte[] bytes) {
+ return (bytes == null || bytes.length <= 0)
+ ? null
+ : BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+ }
+
+ private static byte[] drawable2Bytes(final Drawable drawable) {
+ return drawable == null ? null : bitmap2Bytes(drawable2Bitmap(drawable));
+ }
+
+ private static Drawable bytes2Drawable(final byte[] bytes) {
+ return bytes == null ? null : bitmap2Drawable(bytes2Bitmap(bytes));
+ }
+
+ private static Bitmap drawable2Bitmap(final Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ if (bitmapDrawable.getBitmap() != null) {
+ return bitmapDrawable.getBitmap();
+ }
+ }
+ Bitmap bitmap;
+ if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ bitmap = Bitmap.createBitmap(
+ 1,
+ 1,
+ drawable.getOpacity() != PixelFormat.OPAQUE
+ ? Bitmap.Config.ARGB_8888
+ : Bitmap.Config.RGB_565
+ );
+ } else {
+ bitmap = Bitmap.createBitmap(
+ drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ drawable.getOpacity() != PixelFormat.OPAQUE
+ ? Bitmap.Config.ARGB_8888
+ : Bitmap.Config.RGB_565
+ );
+ }
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
+
+ private static Drawable bitmap2Drawable(final Bitmap bitmap) {
+ return bitmap == null
+ ? null
+ : new BitmapDrawable(getApp().getResources(), bitmap);
+ }
+
+
+ private static void writeFileFromBytes(final File file, final byte[] bytes) {
+ FileChannel fc = null;
+ try {
+ fc = new FileOutputStream(file, false).getChannel();
+ fc.write(ByteBuffer.wrap(bytes));
+ fc.force(true);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (fc != null) {
+ fc.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static byte[] readFile2Bytes(final File file) {
+ FileChannel fc = null;
+ try {
+ fc = new RandomAccessFile(file, "r").getChannel();
+ int size = (int) fc.size();
+ MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load();
+ byte[] data = new byte[size];
+ mbb.get(data, 0, size);
+ return data;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ try {
+ if (fc != null) {
+ fc.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static boolean isSpace(final String s) {
+ if (s == null) return true;
+ for (int i = 0, len = s.length(); i < len; ++i) {
+ if (!Character.isWhitespace(s.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/app/src/main/java/com/maning/gankmm/utils/CacheManager.java b/app/src/main/java/com/maning/gankmm/utils/CacheManager.java
new file mode 100644
index 0000000..3e78c1e
--- /dev/null
+++ b/app/src/main/java/com/maning/gankmm/utils/CacheManager.java
@@ -0,0 +1,44 @@
+package com.maning.gankmm.utils;
+
+import android.text.TextUtils;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author : maning
+ * @date : 2020-10-16
+ * @desc : 缓存管理
+ */
+public class CacheManager {
+
+ private static final String KEY_SCAN_RESULT = "KEY_SCAN_RESULT";
+ private static Gson gson = new Gson();
+
+ public static void saveScanResult(String content) {
+ List caches = new ArrayList<>();
+ String cache = MMKVUtils.getInstance().getString(KEY_SCAN_RESULT, "");
+ if (TextUtils.isEmpty(cache)) {
+ caches.add(content);
+ } else {
+ caches = gson.fromJson(cache, new TypeToken>() {
+ }.getType());
+ caches.add(0,content);
+ }
+ MMKVUtils.getInstance().putString(KEY_SCAN_RESULT, gson.toJson(caches));
+ }
+
+ public static List getScanResult() {
+ String cache = MMKVUtils.getInstance().getString(KEY_SCAN_RESULT, "");
+ if (TextUtils.isEmpty(cache)) {
+ return new ArrayList<>();
+ } else {
+ return gson.fromJson(cache, new TypeToken>() {
+ }.getType());
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/maning/gankmm/utils/ClipUtils.java b/app/src/main/java/com/maning/gankmm/utils/ClipUtils.java
new file mode 100644
index 0000000..1809350
--- /dev/null
+++ b/app/src/main/java/com/maning/gankmm/utils/ClipUtils.java
@@ -0,0 +1,22 @@
+package com.maning.gankmm.utils;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.text.TextUtils;
+
+public class ClipUtils {
+
+ public static void copy(Context context, String content){
+ if(TextUtils.isEmpty(content)){
+ return;
+ }
+ //获取剪贴板管理器:
+ ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+ // 创建普通字符型ClipData
+ ClipData mClipData = ClipData.newPlainText("Label", content);
+ // 将ClipData内容放到系统剪贴板里。
+ cm.setPrimaryClip(mClipData);
+ }
+
+}
diff --git a/app/src/main/java/com/maning/gankmm/utils/MMKVUtils.java b/app/src/main/java/com/maning/gankmm/utils/MMKVUtils.java
new file mode 100644
index 0000000..7268d52
--- /dev/null
+++ b/app/src/main/java/com/maning/gankmm/utils/MMKVUtils.java
@@ -0,0 +1,77 @@
+package com.maning.gankmm.utils;
+
+import android.text.TextUtils;
+
+import com.google.gson.Gson;
+import com.tencent.mmkv.MMKV;
+
+/**
+ * @author : maning
+ * @date : 2020-10-16
+ * @desc :
+ */
+public class MMKVUtils {
+
+ private static final String TIME_FLAG = "--&T&--";
+ private static MMKV defaultMMKV;
+
+ public static MMKV getInstance() {
+ if (defaultMMKV == null) {
+ defaultMMKV = MMKV.defaultMMKV();
+ }
+ return defaultMMKV;
+ }
+
+ public static T getObject(String key, Class classOfT) {
+ String result = getInstance().decodeString(key);
+ if (TextUtils.isEmpty(result)) {
+ return null;
+ }
+ return new Gson().fromJson(result, classOfT);
+ }
+
+ public static boolean saveObject(String key, String value) {
+ return getInstance().encode(key, value);
+ }
+
+ /**
+ * 保存数据
+ *
+ * @param key key
+ * @param value value
+ * @param saveTime 保存时间长度,秒
+ */
+ public static void saveWithTime(String key, String value, int saveTime) {
+ //创建带有时间标记的value+保存到的时间
+ value = value + TIME_FLAG + (System.currentTimeMillis() / 1000 + saveTime);
+ getInstance().encode(key, value);
+ }
+
+ public static String getWithTime(String key) {
+ String result = getInstance().decodeString(key);
+ if (TextUtils.isEmpty(result)) {
+ return "";
+ }
+ if (result.contains(TIME_FLAG)) {
+ String[] split = result.split(TIME_FLAG);
+ if (split.length > 1) {
+ String time = split[1];
+ String value = split[0];
+ if (!TextUtils.isEmpty(time)) {
+ long current = System.currentTimeMillis() / 1000;
+ long saveTime = Long.parseLong(time);
+ if (current > saveTime) {
+ //过期
+ getInstance().removeValueForKey(key);
+ result = "";
+ } else {
+ result = value;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+
+}
diff --git a/app/src/main/java/com/maning/gankmm/utils/ThreadPoolUtils.java b/app/src/main/java/com/maning/gankmm/utils/ThreadPoolUtils.java
new file mode 100644
index 0000000..8028701
--- /dev/null
+++ b/app/src/main/java/com/maning/gankmm/utils/ThreadPoolUtils.java
@@ -0,0 +1,70 @@
+package com.maning.gankmm.utils;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author : maning
+ * @desc : 线程池工具类
+ */
+public class ThreadPoolUtils {
+
+ /**
+ * 线程池核心线程数
+ */
+ private static int CORE_POOL_SIZE = 0;
+ /**
+ * 线程池最大线程数
+ */
+ private static int MAX_POOL_SIZE = 128;
+ /**
+ * 额外线程空状态生存时间
+ */
+ private static int KEEP_ALIVE_TIME = 60;
+ /**
+ * 阻塞队列。当核心线程都被占用,且阻塞队列已满的情况下,才会开启额外线程。
+ */
+ private static BlockingQueue workQueue = new ArrayBlockingQueue(100);
+ /**
+ * 线程池
+ */
+ private static ThreadPoolExecutor threadPool;
+
+ private ThreadPoolUtils() {
+ }
+
+ /**
+ * 线程工厂
+ */
+ private static ThreadFactory threadFactory = new ThreadFactory() {
+ private final AtomicInteger integer = new AtomicInteger();
+
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "ThreadPoolUtils thread:" + integer.getAndIncrement());
+ }
+ };
+
+ static {
+ threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
+ TimeUnit.SECONDS, workQueue, threadFactory);
+ }
+
+ public static void execute(Runnable runnable) {
+ threadPool.execute(runnable);
+ }
+
+ public static void execute(FutureTask futureTask) {
+ threadPool.execute(futureTask);
+ }
+
+ public static void cancel(FutureTask futureTask) {
+ futureTask.cancel(true);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maning/gankmm/utils/UserUtils.java b/app/src/main/java/com/maning/gankmm/utils/UserUtils.java
index eba1c97..93ba790 100644
--- a/app/src/main/java/com/maning/gankmm/utils/UserUtils.java
+++ b/app/src/main/java/com/maning/gankmm/utils/UserUtils.java
@@ -1,48 +1,25 @@
package com.maning.gankmm.utils;
-import com.maning.gankmm.app.MyApplication;
-import com.maning.gankmm.bean.mob.CitysEntity;
import com.maning.gankmm.bean.mob.MobUserInfo;
/**
- * Created by maning on 2017/4/1.
+ * @author : maning
+ * @date : 2020-10-16
+ * @desc :
*/
-
public class UserUtils {
- private static final String cache_citys = "Cache_Citys";
- private static final String cache_UserLogin = "cache_UserLogin";
-
- public static void saveCitysCache(CitysEntity citysEntity) {
- if (citysEntity != null) {
- MyApplication.getACache().put(cache_citys, citysEntity);
- }
- }
-
- public static CitysEntity getCitysCache() {
- CitysEntity citysEntity = (CitysEntity) MyApplication.getACache().getAsObject(cache_citys);
- return citysEntity;
- }
-
//退出登录
public static void quitLogin() {
- MyApplication.getACache().put(cache_UserLogin, new MobUserInfo());
}
//用户登录信息
public static void saveUserCache(MobUserInfo userInfo) {
- if (userInfo != null) {
- MyApplication.getACache().put(cache_UserLogin, userInfo);
- }
}
//获取用户信息
public static MobUserInfo getUserCache() {
- MobUserInfo userInfo = (MobUserInfo) MyApplication.getACache().getAsObject(cache_UserLogin);
- if(userInfo == null){
- userInfo = new MobUserInfo();
- }
- return userInfo;
+ return null;
}
}
diff --git a/app/src/main/java/com/maning/gankmm/utils/ZxingScanUtils.java b/app/src/main/java/com/maning/gankmm/utils/ZxingScanUtils.java
new file mode 100644
index 0000000..30a1968
--- /dev/null
+++ b/app/src/main/java/com/maning/gankmm/utils/ZxingScanUtils.java
@@ -0,0 +1,66 @@
+package com.maning.gankmm.utils;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import com.google.zxing.client.android.MNScanManager;
+import com.google.zxing.client.android.model.MNScanConfig;
+import com.google.zxing.client.android.other.MNScanCallback;
+import com.maning.gankmm.ui.activity.tools.ScanResultActivity;
+
+/**
+ * @author : maning
+ * @date : 2020-10-16
+ * @desc :
+ */
+public class ZxingScanUtils {
+
+ private static final String COLOR_MAIN = "#62e203";
+
+ public static void open(final Activity activity) {
+ //自定义扫描
+ MNScanConfig scanConfig = new MNScanConfig.Builder()
+ //设置完成震动
+ .isShowVibrate(false)
+ //扫描完成声音
+ .isShowBeep(true)
+ //显示相册功能
+ .isShowPhotoAlbum(true)
+ //显示闪光灯
+ .isShowLightController(true)
+ //自定义文案
+ .setScanHintText("请对准二维码/条形码识别")
+ //扫描线的颜色
+ .setScanColor(COLOR_MAIN)
+ //是否显示缩放控制器
+ .isShowZoomController(false)
+ //扫描线样式
+ .setLaserStyle(MNScanConfig.LaserStyle.Line)
+ //是否全屏范围扫描
+ .setFullScreenScan(true)
+ //二维码标记点
+ .isShowResultPoint(true)
+ .setResultPointConfigs(40, 30, 10, "#70FFFFFF", "#7000A81F")
+ .builder();
+ MNScanManager.startScan(activity, scanConfig, new MNScanCallback() {
+ @Override
+ public void onActivityResult(int resultCode, Intent data) {
+ switch (resultCode) {
+ case MNScanManager.RESULT_SUCCESS:
+ String resultSuccess = data.getStringExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS);
+ //扫描成功
+ //跳转到扫码结果页面
+ ScanResultActivity.open(activity, resultSuccess);
+ break;
+ case MNScanManager.RESULT_FAIL:
+ String resultError = data.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR);
+ MyToast.showShortToast(resultError);
+ break;
+ case MNScanManager.RESULT_CANCLE:
+ break;
+ }
+ }
+ });
+ }
+
+}
diff --git a/app/src/main/res/drawable/gank_icon_tools_scan.xml b/app/src/main/res/drawable/gank_icon_tools_scan.xml
new file mode 100644
index 0000000..da4aaf1
--- /dev/null
+++ b/app/src/main/res/drawable/gank_icon_tools_scan.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/gank_icon_tools_scancode.xml b/app/src/main/res/drawable/gank_icon_tools_scancode.xml
new file mode 100644
index 0000000..a663891
--- /dev/null
+++ b/app/src/main/res/drawable/gank_icon_tools_scancode.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_scan_result.xml b/app/src/main/res/layout/activity_scan_result.xml
new file mode 100644
index 0000000..cad2d55
--- /dev/null
+++ b/app/src/main/res/layout/activity_scan_result.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_health.xml b/app/src/main/res/layout/item_rubbish.xml
similarity index 100%
rename from app/src/main/res/layout/item_health.xml
rename to app/src/main/res/layout/item_rubbish.xml
diff --git a/app/src/main/res/layout/item_scan_history.xml b/app/src/main/res/layout/item_scan_history.xml
new file mode 100644
index 0000000..1619717
--- /dev/null
+++ b/app/src/main/res/layout/item_scan_history.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_main_toolbar.xml b/app/src/main/res/menu/menu_main_toolbar.xml
index 4a6aee4..1955c78 100644
--- a/app/src/main/res/menu/menu_main_toolbar.xml
+++ b/app/src/main/res/menu/menu_main_toolbar.xml
@@ -11,6 +11,7 @@
app:showAsAction="ifRoom"/>
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 1e2a384..28b4ea7 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -13,6 +13,12 @@
true
+
+
+