LruCache、DiskLruCache实现自定义SharedPreferences

8月份新上了一个项目,涉及到多用户下的数据共享,Android手机的默认用户id是0,也就是我们平时使用的用户,当进入到其他用户时,虽然是同一app,但是处于不同的用户下,通过SharedPreferences共享数据肯定是做不到的,而且SharedPreferences不能存储Bitmap,而在项目中,需要用到视频的第一帧图片,综合考虑之后,决定在公共目录下通过写文件的形式存储Bitmap等数据。本着代码复用的原则,决定通过LruCache和DiskLruCache自定义一个具备二级缓存的加强版的SharedPreferences。
ps:在这里就不介绍LruCache和DiskLruCache的具体用法了,网上可以找到很多详细的解析。
首先定义一个操作类公共的接口:

public interface ICache {

    void put(String key, Object value);

    void remove(String key) throws IOException;

    void clear() throws IOException;

    Object getObject(String key);

    int getInt(String key);

    long getLong(String key);

    double getDouble(String key);

    float getFloat(String key);

    boolean getBoolean(String key);

    Bitmap getBitmap(String key);

    String getString(String key);

    byte[] getBytes(String key);

}

然后定义分别定义MemoryCache和DiskCache去实现ICache接口,采用LruCache、DiskLruCache来实现一二级缓存。

/**
 * This class uses {@link LruCache} to cache data and does not allow null
 * to be used as a key or value. The more information can be found on {@link LruCache}.
 *
 * Created by Feng Bangquan on 17-11-11
 */
public class MemoryCache implements ICache {
    private LruCache<Object, Object> mLruCache;

    /**
     * @param maxMemorySize this is the maximum sum of the sizes of the entries
     * in this cache.
     */
    public MemoryCache(int maxMemorySize){
        mLruCache = new LruCache<>(maxMemorySize);
    }

    /**
     * Caches {@code value} for {@code key} calling {@link LruCache#put(Object, Object)}
     */
    @Override
    public void put(String key, Object value) {
        mLruCache.put(key, value);
    }

    /**
     * Removes the entry for {@code key} if it exists.
     */
    @Override
    public void remove(String key) {
        mLruCache.remove(key);
    }

    /**
     * Clear the MemoryCache, calling {@link LruCache#evictAll()}
     */
    @Override
    public void clear() {
        mLruCache.evictAll();
    }

    /**
     * Returns the value for {@code key} if it exists in the cache .
     * The following methods call {@link #getObject(String)}
     * to get the specific types' value:
     * @see #getInt(String)
     * @see #getBitmap(String)
     * @see #getBoolean(String)
     * @see #getBytes(String)
     * @see #getDouble(String)
     * @see #getFloat(String)
     * @see #getLong(String)
     * @see #getString(String)
     */
    @Override
    public Object getObject(String key) {
        return mLruCache.get(key);
    }

    @Override
    public int getInt(String key) {
        Object object = mLruCache.get(key);
        return object == null ? null : (int) object;
    }

    @Override
    public long getLong(String key) {
        Object object = mLruCache.get(key);
        return object == null ? null : (long) object;
    }

    @Override
    public double getDouble(String key) {
        Object object = mLruCache.get(key);
        return object == null ? null : (double) object;
    }

    @Override
    public float getFloat(String key) {
        Object object = mLruCache.get(key);
        return object == null ? null : (float) object;
    }

    @Override
    public boolean getBoolean(String key) {
        Object object = mLruCache.get(key);
        return object == null ? null : (boolean) object;
    }

    @Override
    public Bitmap getBitmap(String key) {
        Object object = mLruCache.get(key);
        return object == null ? null : (Bitmap) object;
    }

    @Override
    public String getString(String key) {
        Object object = mLruCache.get(key);
        return object == null ? null : String.valueOf(object);
    }

    @Override
    public byte[] getBytes(String key) {
        Object object = mLruCache.get(key);
        return object == null ? null : (byte[]) object;
    }

}
/**
 * This class uses {@link DiskLruCache} to cache data and does not allow null
 * to be used as a key or value. The more information can be found on {@link DiskLruCache}.
 *
 * Created by Feng Bangquan on 17-11-11
 */
public class DiskCache implements ICache {

    private DiskLruCache mDiskLruCache;

    /**
     * Opens the cache in {@code directory}, creating a cache if none exists
     * there.
     *
     * @param directory a writable directory
     * @param appVersion
     * @param valueCount the number of values per cache entry. Must be positive.
     * @param maxSize the maximum number of bytes this cache should use to store
     * @throws IOException if reading or writing the cache directory fails
     */
    public DiskCache(File directory, int appVersion, int valueCount, long maxSize) throws IOException {
            mDiskLruCache = DiskLruCache.open(directory, appVersion, valueCount, maxSize);
    }

    /**
     * Caches {@code value} for {@code key} in {@code directory}
     */
    @Override
    public void put(String key, Object value) {
        try {
            DiskLruCache.Editor editor  = mDiskLruCache.edit(key);
            OutputStream outputStream = editor.newOutputStream(0);
            ObjectOutputStream objectOutputStream;
            if (value instanceof Bitmap) {
                outputStream.write(bitmapToBytes((Bitmap)value));
            } else if (value instanceof byte[]) {
                outputStream.write((byte[]) value);
            } else {
                objectOutputStream = new ObjectOutputStream(outputStream);
                objectOutputStream.writeObject(value);
                objectOutputStream.close();
            }
            outputStream.close();
            editor.commit();
            mDiskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Calls {@link DiskLruCache#remove(String)} to drop the entry for {@code key}
     * if it exists and can be removed.
     */
    @Override
    public void remove(String key) throws IOException {
        mDiskLruCache.remove(key);
    }

    /**
     * Calls {@link DiskLruCache#delete()} to close the cache and deletes all of its stored values.
     * This will delete all files in the cache directory including files that weren't created by
     * the cache.
     */
    @Override
    public void clear() throws IOException {
        mDiskLruCache.delete();
    }

    /**
     * Returns an object named {@code key}, or null if it doesn't exist is not currently readable.
     * The following methods call {@link #getObject(String)} to get the specific types' value:
     * @see #getInt(String)
     * @see #getLong(String)
     * @see #getDouble(String)
     * @see #getFloat(String)
     * @see #getBoolean(String)
     * @return Object
     */
    @Override
    public Object getObject(String key) {
        try {
            DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
            if (snapshot != null) {
                ObjectInputStream objectInputStream = new ObjectInputStream(snapshot.getInputStream(0));
                return objectInputStream.readObject();
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public int getInt(String key) {
        Object object = getObject(key);
        return object == null ? null : (Integer) object;
    }

    @Override
    public long getLong(String key) {
        Object object = getObject(key);
        return object == null ? null : (Long) object;
    }

    @Override
    public double getDouble(String key) {
        Object object = getObject(key);
        return object == null ? null : (Double) object;
    }

    @Override
    public float getFloat(String key) {
        Object object = getObject(key);
        return object == null ? null : (Float) object;
    }

    @Override
    public boolean getBoolean(String key) {
        Object object = getObject(key);
        return object == null ? null : (Boolean) object;
    }

    /**
     *  Returns a Bitmap named {@code key}, or null if it doesn't exist is not currently readable.
     *  @return Bitmap
     */
    @Override
    public Bitmap getBitmap(String key) {
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
            InputStream inputStream = snapShot.getInputStream(0);
            Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
            inputStream.close();
            return bitmap == null ? null : bitmap;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } catch (NullPointerException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String getString(String key) {
        Object object = getObject(key);
        return object == null ? null : (String)object;
    }

    /**
     * Returns bytes[] named {@code key}, or null if it doesn't exist is not currently readable.
     * @return
     */
    @Override
    public byte[] getBytes(String key) {
        try {
            DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
            return snapshot == null ? null : streamToBytes(snapshot.getInputStream(0));
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Transforms Bitmap to bytes[]
     * @param bitmap the Bitmap to be transformed
     * @return a byte[] transforms from Bitmap
     */
    private byte[] bitmapToBytes(Bitmap bitmap) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
        try {
            byteArrayOutputStream.flush();
            byteArrayOutputStream.close();
            if (!bitmap.isRecycled()){
                bitmap.recycle();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return byteArrayOutputStream.toByteArray();
    }

    /**
     * Transforms InputStream to byte[]
     * @param in the InputStream to be transformed
     * @return a byte[] transforms from InputStream
     */
    private byte[] streamToBytes(InputStream in) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int length;
        byte[] bytes = new byte[1024];
        try {
            while ((length = in.read(bytes)) != -1) {
                out.write(bytes, 0, length);
            }
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return out.toByteArray();
    }

}

最后在Cache类中分别实例化MemoryCache和DiskCache,Cache.open()传入DiskCache的存储路径以及其它初始化参数,再通过putXXX(key, value)和getXXX(key, value)即可实现对数据的存储和读取。

public class Cache {
    private static MemoryCache mMemoryCache;
    private static DiskCache mDiskCache;

    /**
     * Opens the cache in {@code directory}, creating a cache if none exists there.
     *
     * @param directory a writable directory
     * @param appVersion the value must be positive
     * @param valueCount the number of values per cache entry. Must be positive.
     * @param maxDiskSize the maximum number of bytes this diskCache should use to store
     * @param maxMemorySize the maximum number of kilobytes this memoryCache should be to store
     * @throws IOException if reading or writing the cache directory fails
     */
    public static void open(File directory, int appVersion, int valueCount, long maxDiskSize, int maxMemorySize) throws IOException {
        mMemoryCache = new MemoryCache(maxMemorySize);
        mDiskCache = new DiskCache(directory, appVersion, valueCount, maxDiskSize);
    }

    /**
     * Removes the entry for {@code key} if it exists.
     * @throws IOException
     */
    public static void remove(String key) throws IOException {
        mMemoryCache.remove(key);
        mDiskCache.remove(key);
    }

    /**
     * Closes the cache and deletes all of its stored values. This will delete
     * all files in the cache directory including files that weren't created by
     * the cache.
     * @throws IOException
     */
    public static void clear() throws IOException {
        mMemoryCache.clear();
        mDiskCache.clear();
    }

    /**
     * Caches {@code value} for {@code key} calling {@link LruCache#put(Object, Object)}
     * and {@link DiskCache#put(String, Object)}. The following methods putXXX(key, value)
     * cache the specific types' data.
     * @param key the value of key can not contain (" ") , ("\n"), ("\r")
     * @param value it does not support generic types
     */
    public static void put(String key, Object value) {
        mMemoryCache.put(key, value);
        mDiskCache.put(key, value);
    }

    public static void putString(String key, String value){
        put(key, value);
    }

    public static void putInt(String key, int value) {
        put(key, value);
    }

    public static void putFloat(String key, float value) {
        put(key, value);
    }

    public static void putDouble(String key, double value){
        put(key, value);
    }

    public static void putBytes(String key, byte[] value) {
        put(key, value);
    }

    public static void putBoolean(String key, boolean value) {
        put(key, value);
    }

    public static void putBitmap(String key, Bitmap value) {
        put(key, value);
    }

    /**
     * Returns the value for {@code key} if it exists in the MemoryCache or return
     * the value if it exists in the DiskCache, otherwise return null. The following
     * method getXXX(key, value) return specific types' value.
     * @param key
     * @return
     */
    public static Object getObject(String key) {
        if (mMemoryCache.getObject(key) != null) {
            return mMemoryCache.getObject(key);
        } else {
            return mDiskCache.getObject(key);
        }
    }

    public static int getInt(String key) {
        if (mMemoryCache.getObject(key) != null) {
            return mMemoryCache.getInt(key);
        } else {
            return mDiskCache.getInt(key);
        }
    }

    public static long getLong(String key) {
        if (mMemoryCache.getObject(key) != null) {
            return mMemoryCache.getLong(key);
        } else {
            return mDiskCache.getLong(key);
        }
    }

    public static double getDouble(String key) {
        if (mMemoryCache.getObject(key) != null) {
            return mMemoryCache.getDouble(key);
        } else {
            return mDiskCache.getDouble(key);
        }
    }

    public static float getFloat(String key) {
        if (mMemoryCache.getObject(key) != null) {
            return mMemoryCache.getFloat(key);
        } else {
            return mDiskCache.getFloat(key);
        }
    }


    public static boolean getBoolean(String key) {
        if (mMemoryCache.getObject(key) != null) {
            return mMemoryCache.getBoolean(key);
        } else {
            return mDiskCache.getBoolean(key);
        }
    }

    public static Bitmap getBitmap(String key) {
        Bitmap bitmap = mMemoryCache.getBitmap(key);
        if (bitmap != null && !bitmap.isRecycled()) {
            return bitmap;
        } else {
            return mDiskCache.getBitmap(key);
        }
    }

    public static String getString(String key) {
        if (mMemoryCache.getString(key) != null) {
            return mMemoryCache.getString(key);
        } else {
            return mDiskCache.getString(key);
        }
    }

    public static byte[] getBytes(String key) {
        if (mMemoryCache.getBytes(key) != null) {
            return mMemoryCache.getBytes(key);
        } else {
            return mDiskCache.getBytes(key);
        }
    }
}

下面是一个测试用例:

/**
 * A demo activity to guide how to use Cache to store data
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String KEY_INT = "int";
    private static final String KEY_STRING = "string";
    private static final String KEY_BOOLEAN  = "boolean";
    private static final String KEY_BITMAP = "bitmap";
    private static final String KEY_BYTES = "bytes";
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 10; // 100MB

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final int maxMemorySize = (int) (Runtime.getRuntime().maxMemory() / 1024);
        try {
            File cacheDir = getDiskCacheDir("mySharedPreferences");
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            Cache.open(cacheDir, 1, 1, DISK_CACHE_SIZE, (maxMemorySize / 8));
        } catch (IOException e) {
            e.printStackTrace();
        }

        (findViewById(R.id.put)).setOnClickListener(this);
        (findViewById(R.id.get)).setOnClickListener(this);
        (findViewById(R.id.remove)).setOnClickListener(this);
        (findViewById(R.id.clear)).setOnClickListener(this);
    }

    public File getDiskCacheDir(String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = getExternalCacheDir().getPath();
        } else {
            cachePath = getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    private void putCache() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 8;
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.picture, options);

                Cache.putBitmap(KEY_BITMAP, bitmap);
                Cache.putInt(KEY_INT, 1024);
                Cache.putString(KEY_STRING, "read the source code");
                Cache.putBoolean(KEY_BOOLEAN, true);

            }
        }).start();
    }

    private void getCache() {
        Bitmap bitmap = Cache.getBitmap(KEY_BITMAP);
        ((ImageView) findViewById(R.id.image_view)).setImageBitmap(bitmap);
        System.out.println("getInt: " + Cache.getInt(KEY_INT));
        System.out.println("getString: " + Cache.getString(KEY_STRING));
        System.out.println("getBoolean: " + Cache.getBoolean(KEY_BOOLEAN));
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.put) {
            putCache();
        }
        if (v.getId() == R.id.get) {
            getCache();
        }
        if (v.getId() == R.id.clear) {
            try {
                Cache.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (v.getId() == R.id.remove) {
            try {
                Cache.remove(KEY_INT);
                Cache.remove(KEY_BYTES);
                Cache.remove(KEY_BOOLEAN);
                Cache.remove(KEY_BITMAP);
                Cache.remove(KEY_BYTES);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

ImageView上显示出了照片,说明bitmap能够正确存储和读取:

这里写图片描述

01-04 13:08:30.529 17505-17505/com.fengbangquan.cache I/System.out: getInt: 1024
01-04 13:08:30.534 17505-17505/com.fengbangquan.cache I/System.out: getString: read the source code
01-04 13:08:30.544 17505-17505/com.fengbangquan.cache I/System.out: getBoolean: true

这样,具有二级缓存,能够存储Bitmap的自定义SharedPreferences就完成了,需要注意的是Cache在多线程的操作不是线程安全的。完整代码下载地址:https://github.com/fengbangquan/Cache

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值