https://github.com/jovezhougang/mpsp
原理
使用ContentProvider 来保证进程访问数据安全,使用读写锁来保证线程访问安全
测试
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MPSharedPreferences mpSharedPreferences = new MPSharedPreferences(getApplicationContext(),
getPackageName());
mpSharedPreferences.registerOnSharedPreferenceChangeListener(new MPSharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(MPSharedPreferences sharedPreferences,
String key) {
System.out.println(key);
}
});
mpSharedPreferences.putBoolean("boolean",true);
mpSharedPreferences.putFloat("float",9.9f);
mpSharedPreferences.putInt("int",10);
mpSharedPreferences.putLong("long",System.currentTimeMillis());
mpSharedPreferences.putString("string","hello world");
Set<String> sets = new HashSet<>();
sets.add("8888");
sets.add("9999");
sets.add("0000");
mpSharedPreferences.putStringSet("stringSet",sets);
System.out.println(mpSharedPreferences.getString("string",""));
System.out.println(mpSharedPreferences.getBoolean("boolean",false));
System.out.println(mpSharedPreferences.getFloat("float",0));
System.out.println(mpSharedPreferences.getInt("int",0));
System.out.println(mpSharedPreferences.getLong("long",0));
System.out.println(mpSharedPreferences.getStringSet("stringSet",null));
for (int i = 0;i < 10000;i++) {
System.out.println("i == " + i);
System.out.println(mpSharedPreferences.getAll());
}
}
实现
package com.jove.mpsp;
import android.content.ContentValues;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class MPSharedPreferences {
private HashSet<OnSharedPreferenceChangeListener> listeners = new HashSet<>();
public interface OnSharedPreferenceChangeListener {
void onSharedPreferenceChanged(final MPSharedPreferences sharedPreferences,
final String key);
}
private String name;
private Context context;
private ContentObserver mContentObserver =
new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
synchronized (MPSharedPreferences.this) {
for (final OnSharedPreferenceChangeListener listener : listeners) {
listener.onSharedPreferenceChanged(MPSharedPreferences.this
, uri.getPathSegments().get(1));
}
}
}
};
public MPSharedPreferences(@NonNull Context context, @NonNull final String name) {
this.name = name;
this.context = context;
this.context.getContentResolver()
.registerContentObserver(Uri.parse(String.format("content://com.jove.mpsp" +
".provider/%s"
, name)), true, mContentObserver);
}
public Map<String, ?> getAll() {
Cursor cursor = null;
try {
cursor = context.getContentResolver()
.query(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/all/0"
, name)), null, null, null, null);
if (null != cursor && cursor.moveToNext()) {
final Map<String, Object> map = new HashMap<>(cursor.getColumnCount());
for (int i = 0; i < cursor.getColumnCount(); i++) {
switch (cursor.getType(i)) {
case Cursor.FIELD_TYPE_BLOB:
map.put(cursor.getColumnName(i), cursor.getBlob(i));
break;
case Cursor.FIELD_TYPE_STRING:
case Cursor.FIELD_TYPE_NULL:
map.put(cursor.getColumnName(i), cursor.getString(i));
break;
case Cursor.FIELD_TYPE_INTEGER:
map.put(cursor.getColumnName(i), cursor.getInt(i));
if (Long.parseLong(cursor.getString(i)) != cursor.getInt(i)) {
map.put(cursor.getColumnName(i),
Long.parseLong(cursor.getString(i)));
}
break;
case Cursor.FIELD_TYPE_FLOAT:
map.put(cursor.getColumnName(i), cursor.getFloat(i));
break;
}
}
return map;
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return null;
}
@Nullable
public String getString(String key, @Nullable String defValue) {
Cursor cursor = null;
try {
cursor = context.getContentResolver()
.query(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/1"
, name, key)), null, null, null, null);
if (null != cursor && cursor.moveToNext()) {
return cursor.getString(0);
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return defValue;
}
@Nullable
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
Cursor cursor = null;
try {
cursor = context.getContentResolver()
.query(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/6"
, name, key)), null, null, null, null);
if (null != cursor && cursor.moveToNext()) {
final Set<String> sets = new HashSet<>();
for (int i = 0; i < cursor.getColumnCount(); i++) {
sets.add(cursor.getString(i));
}
return sets;
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return defValues;
}
public int getInt(String key, int defValue) {
Cursor cursor = null;
try {
cursor = context.getContentResolver()
.query(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/4"
, name, key)), null, null, null, null);
if (null != cursor && cursor.moveToNext()) {
return cursor.getInt(0);
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return defValue;
}
public long getLong(String key, long defValue) {
Cursor cursor = null;
try {
cursor = context.getContentResolver()
.query(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/5"
, name, key)), null, null, null, null);
if (null != cursor && cursor.moveToNext()) {
return Long.parseLong(cursor.getString(0));
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return defValue;
}
public float getFloat(final String key, final float defValue) {
Cursor cursor = null;
try {
cursor = context.getContentResolver()
.query(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/3"
, name, key)), null, null, null, null);
if (null != cursor && cursor.moveToNext()) {
return cursor.getFloat(0);
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return defValue;
}
public boolean getBoolean(final String key, final boolean defValue) {
Cursor cursor = null;
try {
cursor = context.getContentResolver()
.query(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/2"
, name, key)), null, null, null, null);
if (null != cursor && cursor.moveToNext()) {
return 0 == cursor.getInt(0) ? false : true;
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return defValue;
}
public boolean contains(final String key) {
Cursor cursor = null;
try {
cursor = context.getContentResolver()
.query(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/7"
, name, key)), null, null, null, null);
if (null != cursor && cursor.moveToNext()) {
return cursor.getInt(0) > 0;
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return false;
}
public synchronized void registerOnSharedPreferenceChangeListener(final @NonNull OnSharedPreferenceChangeListener listener) {
listeners.add(listener);
}
public synchronized void unregisterOnSharedPreferenceChangeListener(final @NonNull OnSharedPreferenceChangeListener listener) {
listeners.remove(listener);
}
public boolean putString(String key, @Nullable String value) {
final ContentValues values = new ContentValues();
values.put(key, value);
final Uri uri = context.getContentResolver()
.insert(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/1"
, name, key)), values);
return null != uri;
}
public boolean putStringSet(final String key, final @Nullable Set<String> values) {
final ContentValues contentValues = new ContentValues();
int index = 0;
for (final String value : values) {
contentValues.put("k" + index, value);
index++;
}
final Uri uri = context.getContentResolver()
.insert(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/6"
, name, key)), contentValues);
return null != uri;
}
public boolean putInt(String key, int value) {
final ContentValues values = new ContentValues();
values.put(key, value);
final Uri uri = context.getContentResolver()
.insert(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/4"
, name, key)), values);
return null != uri;
}
public boolean putLong(String key, long value) {
final ContentValues values = new ContentValues();
values.put(key, value);
final Uri uri = context.getContentResolver()
.insert(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/5"
, name, key)), values);
return null != uri;
}
public boolean putFloat(String key, float value) {
final ContentValues values = new ContentValues();
values.put(key, value);
final Uri uri = context.getContentResolver()
.insert(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/3"
, name, key)), values);
return null != uri;
}
public boolean putBoolean(String key, boolean value) {
final ContentValues values = new ContentValues();
values.put(key, value);
final Uri uri = context.getContentResolver()
.insert(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s/2"
, name, key)), values);
return null != uri;
}
public boolean remove(final String key) {
final int nums = context.getContentResolver()
.delete(Uri.parse(String.format("content://com.jove.mpsp.provider/remove/%s/%s"
, name, key)), null, null);
return nums > 0;
}
public boolean clear() {
final int nums = context.getContentResolver()
.delete(Uri.parse(String.format("content://com.jove.mpsp.provider/clear/%s"
, name)), null, null);
return nums > 0;
}
}
package com.jove.mpsp;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import java.io.File;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class MPContentProvider extends ContentProvider implements SharedPreferences.OnSharedPreferenceChangeListener {
private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
static {
uriMatcher.addURI("com.jove.mpsp.provider", "remove/*/*", 0x666);
uriMatcher.addURI("com.jove.mpsp.provider", "clear/*", 0x999);
uriMatcher.addURI("com.jove.mpsp.provider", "*/*/#", 0x888);
}
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder) {
try {
lock.readLock().lock();
switch (uriMatcher.match(uri)) {
case 0x888:
final List<String> paths = uri.getPathSegments();
final SharedPreferences sp = getContext()
.getSharedPreferences(paths.get(0), Context.MODE_PRIVATE);
final int action = Integer.parseInt(paths.get(2));
if (action == 0) {
final Map<String, ?> map = sp.getAll();
if (null != map && 0 != map.size()) {
final Object[] columnValues = new Object[map.size()];
final String[] columnNames = new String[map.size()];
int index = 0;
for (final String key : map.keySet()) {
columnNames[index] = key;
columnValues[index] = map.get(key);
index++;
}
final MatrixCursor cursor = new MatrixCursor(columnNames
, 1);
cursor.addRow(columnValues);
return cursor;
}
return null;
} else if (action == 1) {
final String key = paths.get(1);
if (sp.contains(key)) {
final MatrixCursor cursor = new MatrixCursor(new String[]{"v"}
, 1);
cursor.addRow(new Object[]{sp.getString(paths.get(1), "")});
return cursor;
}
return null;
} else if (action == 2) {
final String key = paths.get(1);
if (sp.contains(key)) {
final MatrixCursor cursor = new MatrixCursor(new String[]{"v"}
, 1);
cursor.addRow(new Object[]{sp.getBoolean(paths.get(1), false) ? 1 : 0});
return cursor;
}
return null;
} else if (action == 3) {
final String key = paths.get(1);
if (sp.contains(key)) {
final MatrixCursor cursor = new MatrixCursor(new String[]{"v"}
, 1);
cursor.addRow(new Object[]{sp.getFloat(paths.get(1), 0)});
return cursor;
}
return null;
} else if (action == 4) {
final String key = paths.get(1);
if (sp.contains(key)) {
final MatrixCursor cursor = new MatrixCursor(new String[]{"v"}
, 1);
cursor.addRow(new Object[]{sp.getInt(paths.get(1), 0)});
return cursor;
}
return null;
} else if (action == 5) {
final String key = paths.get(1);
if (sp.contains(key)) {
final MatrixCursor cursor = new MatrixCursor(new String[]{"v"}
, 1);
cursor.addRow(new Object[]{sp.getLong(paths.get(1), 0)});
return cursor;
}
return null;
} else if (action == 6) {
final String key = paths.get(1);
if (sp.contains(key)) {
final Set<String> sets = sp.getStringSet(key, null);
if (null != sets && 0 != sets.size()) {
final String[] columnNames = new String[sets.size()];
final Object[] columnValues = new Object[sets.size()];
int index = 0;
for (final String value : sets) {
columnNames[index] = "v" + index;
columnValues[index] = value;
index++;
}
final MatrixCursor cursor = new MatrixCursor(columnNames
, 1);
cursor.addRow(columnValues);
return cursor;
}
}
return null;
} else if (action == 7) {
final String key = paths.get(1);
if (sp.contains(key)) {
final MatrixCursor cursor = new MatrixCursor(new String[]{"v"}
, 1);
cursor.addRow(new Object[]{1});
return cursor;
}
return null;
}
break;
}
return null;
} finally {
lock.readLock().unlock();
}
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
try {
lock.writeLock().lock();
switch (uriMatcher.match(uri)) {
case 0x888:
final List<String> paths = uri.getPathSegments();
final SharedPreferences sp = getContext()
.getSharedPreferences(paths.get(0), Context.MODE_PRIVATE);
sp.unregisterOnSharedPreferenceChangeListener(this);
sp.registerOnSharedPreferenceChangeListener(this);
final int action = Integer.parseInt(paths.get(2));
if (action == 1) {
final String key = paths.get(1);
if (sp.edit().putString(key, values.getAsString(key)).commit()) {
return uri;
}
return null;
} else if (action == 2) {
final String key = paths.get(1);
if (sp.edit().putBoolean(key, values.getAsBoolean(key)).commit()) {
return uri;
}
return null;
} else if (action == 3) {
final String key = paths.get(1);
if (sp.edit().putFloat(key, values.getAsFloat(key)).commit()) {
return uri;
}
return null;
} else if (action == 4) {
final String key = paths.get(1);
if (sp.edit().putInt(key, values.getAsInteger(key)).commit()) {
return uri;
}
return null;
} else if (action == 5) {
final String key = paths.get(1);
if (sp.edit().putLong(key, values.getAsLong(key)).commit()) {
return uri;
}
return null;
} else if (action == 6) {
final Set<String> sets = new HashSet<>();
for (final String key : values.keySet()) {
sets.add(values.getAsString(key));
}
final String key = paths.get(1);
if (sp.edit().putStringSet(key, sets).commit()) {
return uri;
}
return null;
}
break;
}
return null;
} finally {
lock.writeLock().unlock();
}
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) {
try {
lock.writeLock().lock();
final List<String> paths = uri.getPathSegments();
final SharedPreferences sp = getContext()
.getSharedPreferences(paths.get(1), Context.MODE_PRIVATE);
sp.unregisterOnSharedPreferenceChangeListener(this);
sp.registerOnSharedPreferenceChangeListener(this);
switch (uriMatcher.match(uri)) {
case 0x666:
return sp.edit().remove(paths.get(2)).commit() ? 1 : 0;
case 0x999:
return sp.edit().clear().commit() ? 1 : 0;
}
} finally {
lock.writeLock().unlock();
}
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values
, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
try {
final Field field = sharedPreferences.getClass().getDeclaredField("mFile");
field.setAccessible(true);
final File file = (File) field.get(sharedPreferences);
getContext().getContentResolver()
.notifyChange(Uri.parse(String.format("content://com.jove.mpsp.provider/%s/%s"
, file.getName().replaceAll(".xml", ""), key)), null);
} catch (Exception e) {
e.printStackTrace();
}
}
}