SharedPreferences简称SP(后面都会称SP),是一种轻量级的数据存储方式,采用Key-value的方式存储到本地的/data/data/package_name/shared_prefs/目录 。
SP通常保存一下参数配置信息,而且使用简单、方便,但是SP不建议存储大量的数据信息,因为SP会将数据进行内存缓存,这样如果你的SP存储数据太多,不仅影响性能,甚至会出现ANR。
所以我在这里尝试使用数据库来代替SP,这里要说明SP提交数据主要是有SharedPreferences.Editor的commit()和apply(),区别在于commit()会立即刷新的内存和本地文件数据,而apply()先更新内存的数据,在合适的时间再刷新本地文件数据。
现在开始定义一个PreferencesDBHelper单例类,这里说一下在创建表代码:
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ( _id INTEGER PRIMARY KEY,"
+ KEY + " TEXT UNIQUE, " + VALUE + " BLOB)");
}
这里key做一下唯一约束,value的字段类型的二进制类型,基本数据类型的封装类型和String类型都是序列化的,这里为了不针对每个数据类型都写put和get方法所以就定义value的存储类型是BLOB,这样我们的读写代码就简单多了:
/**
* 直接存储到数据库
* @param key
* @param value
*/
public void commit(String key, Serializable value) {
byte[] bytes = toBytes(value);
SQLiteDatabase database = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY, key);
if (bytes == null) {
values.putNull(VALUE);
} else {
values.put(VALUE, bytes);
}
database.insertWithOnConflict(TABLE_NAME, null, values, CONFLICT_REPLACE);
database.close();
}
public T getValue(String key) {
Serializable value = get(key);
if (value == null)
return null;
return (T) value;
}
private Serializable get(String key) {
SQLiteDatabase database = getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, new String[]{VALUE}, KEY + " = ? ", new String[]{key}, null, null, null);
if (cursor.moveToFirst()) {
byte[] data = cursor.getBlob(0);
Serializable value = toObject(data);
cursor.close();
return value;
}
cursor.close();
return null;
}
这里简单的读写就已经写好了,现在加上SP的apply()功能,这里我们在定义一个HasMap对象mMemoryMap缓存数据,定时写入功能使用WorkManager库,在大概60s间隔进行一次内存数据写入数据库操作,具体的PreferencesDBHelper类代码如下:
/**
* Created by flk on 2018/12/18.
*/
public class PreferencesDBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "Preferences";
private static final int DB_VERSION = 1;
private static final String TABLE_NAME = "prefer";
private static final String KEY = "pKey";
private static final String VALUE = "pValue";
private static PreferencesDBHelper mHelper;
/**
* 内存中的key/value
*/
private HashMap mMemoryMap;
/**
* 上次内存数据写入数据库时间
*/
private long mLastFlushTime;
public static PreferencesDBHelper getInstance(Context context) {
if (mHelper == null) {
synchronized (PreferencesDBHelper.class) {
if (mHelper == null) {
mHelper = new PreferencesDBHelper(context.getApplicationContext());
}
}
}
return mHelper;
}
private PreferencesDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ( _id INTEGER PRIMARY KEY,"
+ KEY + " TEXT UNIQUE, " + VALUE + " BLOB)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
/**
* 先存储到内存
* @param key
* @param value
*/
public void apply(String key, Serializable value) {
createMemoryMapIfNull();
mMemoryMap.put(key, value);
}
/**
* 直接存储到数据库
* @param key
* @param value
*/
public void commit(String key, Serializable value) {
byte[] bytes = toBytes(value);
SQLiteDatabase database = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY, key);
if (bytes == null) {
values.putNull(VALUE);
} else {
values.put(VALUE, bytes);
}
database.insertWithOnConflict(TABLE_NAME, null, values, CONFLICT_REPLACE);
database.close();
}
/**
* 内存数据立即写入数据库
* 在APP进入后台或者退出时调用
*/
public void flushMemoryCache(){
flushMemoryCache(true);
}
public T getValue(String key) {
if (mMemoryMap != null && mMemoryMap.containsKey(key)) {
return (T) mMemoryMap.get(key);
}
Serializable value = get(key);
if (value == null)
return null;
return (T) value;
}
private Serializable get(String key) {
SQLiteDatabase database = getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, new String[]{VALUE}, KEY + " = ? ", new String[]{key}, null, null, null);
if (cursor.moveToFirst()) {
byte[] data = cursor.getBlob(0);
Serializable value = toObject(data);
cursor.close();
return value;
}
cursor.close();
return null;
}
private void createMemoryMapIfNull() {
if (mMemoryMap == null) {
mMemoryMap = new HashMap<>();
startWorker();
}
}
private void startWorker() {
//PeriodicWorkRequest最小周期是15分钟,所以这里使用不断重试的一次性任务
OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(FlushMemoryWorker.class);
WorkManager.getInstance().enqueue(builder.build());
}
/**
* 内存数据写入数据库
* @param must 是否忽略时间间隔
*/
private void flushMemoryCache(boolean must) {
if (mLastFlushTime == 0 && !must) {
mLastFlushTime = System.currentTimeMillis();
return;
}
long time = System.currentTimeMillis() - mLastFlushTime;
if (time < 60 && !must) {
return;
}
if (mMemoryMap != null && mMemoryMap.size() != 0) {
SQLiteDatabase db = getWritableDatabase();
if (!db.isOpen()) {
return;
}
db.beginTransaction();
String sql = "INSERT OR REPLACE INTO " + TABLE_NAME + "(" + KEY + "," + VALUE + ") values(?, ?)";
SQLiteStatement statement = db.compileStatement(sql);
for (Map.Entry entry : mMemoryMap.entrySet()) {
statement.bindString(1, entry.getKey());
Serializable value = entry.getValue();
if (value == null) {
statement.bindNull(2);
} else {
statement.bindBlob(2, toBytes(entry.getValue()));
}
statement.executeInsert();
}
db.setTransactionSuccessful();
db.endTransaction();
db.close();
mMemoryMap.clear();
}
mLastFlushTime = System.currentTimeMillis();
}
private static byte[] toBytes(Serializable v) {
if (v == null)
return null;
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(arrayOutputStream);
objectOutputStream.writeObject(v);
objectOutputStream.flush();
return arrayOutputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static Serializable toObject(byte[] bytes) {
if (bytes == null)
return null;
ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(bytes);
try {
ObjectInputStream inputStream = new ObjectInputStream(arrayInputStream);
Serializable value = (Serializable) inputStream.readObject();
inputStream.close();
arrayInputStream.close();
return value;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 定时清空内存中keyMap
*/
public static class FlushMemoryWorker extends Worker {
public FlushMemoryWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
if (mHelper != null) {
mHelper.flushMemoryCache(false);
}
return Result.retry();
}
}
}
测试代码:
view.findViewById(R.id.message).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PreferencesDBHelper.getInstance(v.getContext()).commit("1", "45678");
PreferencesDBHelper.getInstance(v.getContext()).commit("2", 123);
PreferencesDBHelper.getInstance(v.getContext()).commit("3", true);
PreferencesDBHelper.getInstance(v.getContext()).commit("3", 1.23f);
PreferencesDBHelper.getInstance(v.getContext()).commit("4", null);
PreferencesDBHelper.getInstance(v.getContext()).apply("5", null);
PreferencesDBHelper.getInstance(v.getContext()).apply("6", new A("a"));
}
});
view.findViewById(R.id.message1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("1======>"+PreferencesDBHelper.getInstance(v.getContext()).getValue("1"));
System.out.println("2======>"+PreferencesDBHelper.getInstance(v.getContext()).getValue("2"));
System.out.println("3======>"+PreferencesDBHelper.getInstance(v.getContext()).getValue("3"));
System.out.println("4======>"+PreferencesDBHelper.getInstance(v.getContext()).getValue("4"));
System.out.println("5======>"+PreferencesDBHelper.getInstance(v.getContext()).getValue("5"));
System.out.println("6======>"+PreferencesDBHelper.getInstance(v.getContext()).getValue("6"));
}
});
当前的PreferencesDBHelper只是为这个思路做个简单开始的参考,是否可以代替SP,在细节上的问题还要考虑一下,还有SQLite是不支持多线程的,所以我们对多线程的读写进行处理,主要是多线程调用时会多次调用打开SQLiteDatabase导致闪退,这里可以进行加锁读写,或者控制SQLiteDatabase开闭次数。
不知道这样使用数据库代替SP是否可行呢