使用数据库代替SharedPreferences的

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是否可行呢

你可能感兴趣的:(使用数据库代替SharedPreferences的)