Instantly share code, notes, and snippets.
Last active
February 12, 2019 08:19
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save lvsecoto/be5aea0d89beb10b8946dbdacac0ee4e to your computer and use it in GitHub Desktop.
把SharedPreference做成LiveData,并提供类似数据库一样增删查改的功能
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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