Skip to content

Instantly share code, notes, and snippets.

@lvsecoto
Last active February 12, 2019 08:19
Show Gist options
  • Save lvsecoto/be5aea0d89beb10b8946dbdacac0ee4e to your computer and use it in GitHub Desktop.
Save lvsecoto/be5aea0d89beb10b8946dbdacac0ee4e to your computer and use it in GitHub Desktop.
把SharedPreference做成LiveData,并提供类似数据库一样增删查改的功能
import android.annotation.SuppressLint;
import android.arch.lifecycle.LiveData;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* 对SharedPreference提供类似数据库CRUD操作,并且可以生成LiveData进行观察
*/
public class SharedPreferenceDao {
@SuppressWarnings("SpellCheckingInspection")
private static Gson mGSON = new Gson();
/**
* 创建LiveData用于观察对象
*/
@NonNull
public static <T> LiveData<T> objectLiveData(
SharedPreferences pref, String key, Class<T> tClass) {
return new PrefLiveData<T>(pref, key, null) {
@Override
protected T getPrefValue(SharedPreferences pref, String key, T defValue) {
return getObject(pref, key, tClass);
}
};
}
/**
* 创建LiveData用于观察对象列表
*/
@NonNull
public static <T> LiveData<List<T>> objectListLiveData(
SharedPreferences pref, String key, Class<T> type) {
return new PrefLiveData<List<T>>(pref, key, null) {
@Override
protected List<T> getPrefValue(SharedPreferences pref, String key, List<T> defValue) {
return getObjectList(pref, key, type);
}
};
}
/**
* 直接获取Pref中的对象
*/
@Nullable
public static <T> T getObject(SharedPreferences pref, String key, Class<T> tClass) {
String json = pref.getString(key, null);
if (json == null) {
return null;
}
try {
return mGSON.fromJson(json, tClass);
} catch (JsonSyntaxException ignored) {
return null;
}
}
/**
* 直接获取Pref中的对象列表
*/
@SuppressWarnings("unchecked")
@Nullable
public static <T> List<T> getObjectList(SharedPreferences pref, String key, Class<T> type) {
String json = pref.getString(key, null);
if (json == null) {
return null;
}
try {
return mGSON.fromJson(json, new GenericOf(type));
} catch (JsonSyntaxException ignored) {
return null;
}
}
/**
* 尝试获取列表,当列表不存在,则创建列表。
* <p>如果可以取得一个对象,则用这个对象来创建列表
*/
@SuppressWarnings("unchecked")
@NonNull
public static <T> List<T> getOrCreateObjectList(
SharedPreferences pref, String key, Class<T> type) {
List<T> objectList = getObjectList(pref, key, type);
if (objectList == null) {
objectList = new ArrayList<>();
T object = getObject(pref, key, type);
if (object != null) {
objectList.add(object);
}
}
return objectList;
}
/**
* 将一个对象写入pref, 替换原来那个
*/
@SuppressLint("ApplySharedPref")
@WorkerThread
public static <T> void putObjectOrObjectList(SharedPreferences preferences, String key, T value) {
preferences.edit().putString(key, mGSON.toJson(value)).commit();
}
/**
* 添加{@code object}, 到列表位置{@code index},如果位置为负数,则表示倒数第几位,-1代表倒数第一位,-2代表倒数第二位
*
* @return 如果索引不在范围,或者{@code key}对应的位置没有列表存在,返回false,否则为true
*/
@WorkerThread
public static <T> boolean insert(
SharedPreferences pref, String key, Class<T> type, T object, int index) {
List<T> objectList = getOrCreateObjectList(pref, key, type);
int actualIndex = getActualIndex(index, objectList.size());
if (actualIndex < 0 || actualIndex > objectList.size()) {
return false;
}
objectList.add(actualIndex, object);
putObjectOrObjectList(pref, key, objectList);
return true;
}
/**
* 添加对象到列表最前面
*
* @see #insert(SharedPreferences, String, Class, Object, int)
*/
@WorkerThread
public static <T> boolean insertFirst(
SharedPreferences pref, String key, Class<T> type, T object) {
return insert(pref, key, type, object, 0);
}
/**
* 添加对象到列表最后面
*
* @see #insert(SharedPreferences, String, Class, Object, int)
*/
@WorkerThread
public static <T> boolean insertLast(
SharedPreferences pref, String key, Class<T> type, T object) {
return insert(pref, key, type, object, -1);
}
/**
* 添加{@code object}, 到列表位置{@code index},如果位置为负数,则表示倒数第几位,-1代表倒数第一位,-2代表倒数第二位
*
* @return 如果索引不在范围,或者{@code key}对应的位置没有列表存在,添加失败, 返回false,否则为true
*/
@WorkerThread
public static <T> boolean insertAll(
SharedPreferences pref, String key, Class<T> type, List<T> objects, int index) {
List<T> objectList = getOrCreateObjectList(pref, key, type);
int actualIndex = getActualIndex(index, objectList.size());
if (actualIndex < 0 || actualIndex > objectList.size()) {
return false;
}
objectList.addAll(actualIndex, objects);
putObjectOrObjectList(pref, key, objectList);
return true;
}
/**
* 当条件符合{@code where}, 则执行{@code update}
*
* @param count 指定要更新的数目
* @return 多少行更新了
*/
@WorkerThread
public static <T> int update(
SharedPreferences pref,
String key,
Class<T> type,
Update<T> update,
Where<T> where,
int count) {
List<T> objectList = getObjectList(pref, key, type);
if (objectList == null || objectList.isEmpty()) {
return 0;
}
int updateCount = 0;
for (int index = 0; index < objectList.size(); index++) {
T object = objectList.get(index);
if (where.where(object)) {
objectList.set(index, update.update(object));
updateCount++;
}
if (updateCount >= count) {
break;
}
}
if (updateCount > 0) {
putObjectOrObjectList(pref, key, objectList);
}
return updateCount;
}
/**
* 仅更新一次,优化性能
*
* @see #update(SharedPreferences, String, Class, Update, Where, int) 仅更新一次, 性能优化
*/
@WorkerThread
public static <T> int updateOnce(
SharedPreferences pref, String key, Class<T> type, Update<T> update, Where<T> where) {
return update(pref, key, type, update, where, 1);
}
/**
* 更新任何一个符合条件
*
* @see #update(SharedPreferences, String, Class, Update, Where, int) 仅更新一次, 性能优化
*/
@WorkerThread
public static <T> int updateAny(
SharedPreferences pref, String key, Class<T> type, Update<T> update, Where<T> where) {
return update(pref, key, type, update, where, Integer.MAX_VALUE);
}
/**
* 删除符合条件{@code where}的条目
*
* @return 删除的数量
*/
@WorkerThread
public static <T> int delete(
SharedPreferences pref, String key, Class<T> type, Where<T> where, int count) {
List<T> objectList = getObjectList(pref, key, type);
if (objectList == null || objectList.isEmpty()) {
return 0;
}
int deleteCount = 0;
for (Iterator<T> iterator = objectList.iterator(); iterator.hasNext(); ) {
T object = iterator.next();
if (where.where(object)) {
deleteCount++;
iterator.remove();
}
if (deleteCount >= count) {
break;
}
}
if (deleteCount > 0) {
putObjectOrObjectList(pref, key, objectList);
}
return deleteCount;
}
/**
* 仅删除符合条件的条目一次,优化性能
*
* @return 删除的数量
*/
@WorkerThread
public static <T> int deleteOnce(
SharedPreferences pref, String key, Class<T> type, Where<T> where) {
return delete(pref, key, type, where, 1);
}
/**
* 删除所有符合条件的条目
*
* @return 删除的数量
*/
@WorkerThread
public static <T> int deleteAny(
SharedPreferences pref, String key, Class<T> type, Where<T> where) {
return delete(pref, key, type, where, Integer.MAX_VALUE);
}
/**
* 删除所有条目
*
* @return 删除的数量
*/
@WorkerThread
public static <T> int deleteAll(SharedPreferences pref, String key, Class<T> type) {
List<T> objectList = getObjectList(pref, key, type);
putObjectOrObjectList(pref, key, Collections.emptyList());
if (objectList == null || objectList.isEmpty()) {
return 0;
} else {
return objectList.size();
}
}
/**
* 转换可正可负的索引,当索引为负几,代表倒数第几个,-1是倒数第一个
*/
private static int getActualIndex(int index, int size) {
int actualIndex;
if (index < 0) {
actualIndex = size - (-index) + 1;
} else {
actualIndex = index;
}
return actualIndex;
}
/**
* 创建一个可以观察SharedPreference Key 对应的数据的变化的LiveData
*
* @param <T>
*/
abstract static class PrefLiveData<T> extends LiveData<T> {
private SharedPreferences mPref;
private String mKey;
private T mDefValue;
private SharedPreferences.OnSharedPreferenceChangeListener mListener =
(pref, key) -> {
if (Objects.equals(key, mKey)) {
setValue(getPrefValue(mPref, mKey, mDefValue));
}
};
/**
* @param sharedPreferences SharedPreference对象
* @param key 对应的SharedPreference Key
* @param defValue 默认指
*/
PrefLiveData(SharedPreferences sharedPreferences, String key, T defValue) {
mPref = sharedPreferences;
mKey = key;
mDefValue = defValue;
}
@Override
protected void onActive() {
super.onActive();
setValue(getPrefValue(mPref, mKey, mDefValue));
mPref.registerOnSharedPreferenceChangeListener(mListener);
}
@Override
protected void onInactive() {
super.onInactive();
mPref.unregisterOnSharedPreferenceChangeListener(mListener);
}
/**
* 如何从SharedPreference获取数据
*/
protected abstract T getPrefValue(SharedPreferences pref, String key, T defValue);
}
/**
* 帮助GSON解析列表范型数据
*/
static class GenericOf<T> implements ParameterizedType {
private final Class<T> type;
GenericOf(Class<T> type) {
this.type = type;
}
@Override
@NonNull
public Type[] getActualTypeArguments() {
return new Type[]{type};
}
@Override
@NonNull
public Type getRawType() {
return List.class;
}
@Override
public Type getOwnerType() {
return null;
}
}
/**
* where条件测试
*/
public interface Where<T> {
boolean where(T object);
}
/**
* 更新操作
*/
public interface Update<T> {
T update(T object);
}
}
import android.arch.core.executor.testing.InstantTaskExecutorRule;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class)
public class SharedPreferenceDaoTest {
private static final String KEY_TEST = "KEY_TEST";
private SharedPreferences mPref;
@Rule
public TestRule rule = new InstantTaskExecutorRule();
private static LifecycleOwner mockLifecycleOwner() {
LifecycleOwner owner = Mockito.mock(LifecycleOwner.class);
LifecycleRegistry lifecycle = new LifecycleRegistry(owner);
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
when(owner.getLifecycle()).thenReturn(lifecycle);
return owner;
}
@SuppressWarnings("unchecked")
private static <T> Observer<T> mockObserver() {
return (Observer<T>) Mockito.mock(Observer.class);
}
@Before
public void setUp() {
Context appContext = InstrumentationRegistry.getTargetContext();
mPref = appContext.getSharedPreferences("test_shared_preference_dao", Context.MODE_PRIVATE);
mPref.edit().clear().commit();
}
@Test
public void objectLiveData() {
// 更新发生在主线程上,所以在主线程上进行测试验证
Handler mainHandler = new Handler(Looper.getMainLooper());
LifecycleOwner owner = mockLifecycleOwner();
Observer<String> observer = mockObserver();
LiveData<String> liveData = SharedPreferenceDao.objectLiveData(mPref, KEY_TEST, String.class);
liveData.observe(owner, observer);
verify(observer).onChanged(null);
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, "one");
mainHandler.post(() -> {
verify(observer).onChanged("one");
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, "two");
mainHandler.post(() ->
verify(observer).onChanged("two"));
});
}
@Test
public void objectListLiveData() {
// 更新发生在主线程上,所以在主线程上进行测试验证
Handler mainHandler = new Handler(Looper.getMainLooper());
LifecycleOwner owner = mockLifecycleOwner();
Observer<List<String>> observer = mockObserver();
LiveData<List<String>> liveData = SharedPreferenceDao.objectListLiveData(mPref, KEY_TEST, String.class);
liveData.observe(owner, observer);
verify(observer).onChanged(null);
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Collections.singletonList("one"));
mainHandler.post(() ->
verify(observer).onChanged(Collections.singletonList("one")));
}
@Test
public void getObject() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, "one");
assertThat(
SharedPreferenceDao.getObject(mPref, KEY_TEST, String.class)
).isEqualTo("one");
}
@Test
public void getObjectList() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(
SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class)
).containsExactly("one", "two", "three");
}
@Test
public void getOrCreateObjectList() {
assertThat(SharedPreferenceDao.getOrCreateObjectList(mPref, KEY_TEST, String.class))
.isEmpty();
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, "one");
assertThat(SharedPreferenceDao.getOrCreateObjectList(mPref, KEY_TEST, String.class))
.containsExactly("one");
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Collections.singletonList("one"));
assertThat(SharedPreferenceDao.getOrCreateObjectList(mPref, KEY_TEST, String.class))
.containsExactly("one");
}
@Test
public void putObjectOrObjectList() {
objectListLiveData();
}
@Test
public void insert() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
SharedPreferenceDao.insert(mPref, KEY_TEST, String.class, "before", 0);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("before", "one", "two", "three");
SharedPreferenceDao.insert(mPref, KEY_TEST, String.class, "after", -1);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("before", "one", "two", "three", "after");
}
@Test
public void insertFirst() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
SharedPreferenceDao.insertFirst(mPref, KEY_TEST, String.class, "first");
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("first", "one", "two", "three");
}
@Test
public void insertLast() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
SharedPreferenceDao.insertLast(mPref, KEY_TEST, String.class, "last");
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("one", "two", "three", "last");
}
@Test
public void insertAll() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
SharedPreferenceDao.insertAll(mPref, KEY_TEST, String.class, Arrays.asList("inner1", "inner2"), 1);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("one", "inner1", "inner2", "two", "three");
}
@Test
public void update() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(SharedPreferenceDao.update(mPref, KEY_TEST, String.class, object -> object + "M", object -> object.contains("o"), 1))
.isEqualTo(1);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("oneM", "two", "three");
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(SharedPreferenceDao.update(mPref, KEY_TEST, String.class, object -> object + "M", object -> object.contains("o"), 2))
.isEqualTo(2);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("oneM", "twoM", "three");
}
@Test
public void updateOnce() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(SharedPreferenceDao.updateOnce(mPref, KEY_TEST, String.class, object -> object + "M", object -> object.contains("o")))
.isEqualTo(1);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("oneM", "two", "three");
}
@Test
public void updateAny() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(SharedPreferenceDao.updateAny(mPref, KEY_TEST, String.class, object -> object + "M", object -> object.contains("o")))
.isEqualTo(2);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("oneM", "twoM", "three");
}
@Test
public void delete() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(SharedPreferenceDao.delete(mPref, KEY_TEST, String.class, object -> object.contains("o"), 1))
.isEqualTo(1);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("two", "three");
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(SharedPreferenceDao.delete(mPref, KEY_TEST, String.class, object -> object.contains("o"), 2))
.isEqualTo(2);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("three");
}
@Test
public void deleteOnce() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(SharedPreferenceDao.deleteOnce(mPref, KEY_TEST, String.class, object -> object.contains("o")))
.isEqualTo(1);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("two", "three");
}
@Test
public void deleteAny() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
assertThat(SharedPreferenceDao.deleteAny(mPref, KEY_TEST, String.class, object -> object.contains("o")))
.isEqualTo(2);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.containsExactly("three");
}
@Test
public void deleteAll() {
SharedPreferenceDao.putObjectOrObjectList(mPref, KEY_TEST, Arrays.asList("one", "two", "three"));
SharedPreferenceDao.deleteAll(mPref, KEY_TEST, String.class);
assertThat(SharedPreferenceDao.getObjectList(mPref, KEY_TEST, String.class))
.isEmpty();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment