本封装是基于1.0.0
版本。
出现DataStore是因为SharePeference存在一些问题。
SharePeference可能会导致以下问题:
1. SharePeference第一次加载数据时需要全量加载,当数据量大时可能会阻塞UI线程造成卡顿。
2. SharePeference读写文件不是类型安全的,且没有发出错误信号的机制,缺少事务性API。
3. commit()是同步提交,会在UI主线程中直接执行IO操作,当写入操作耗时比较长时就会导致UI线程被阻塞,进而产生ANR;apply()虽然是异步提交,但异步写入磁盘时,如果执行了Activity / Service中的onStop()方法,那么一样会同步等待SP写入完毕,等待时间过长时也会引起ANR问题。
其实这个导致问题的概率是非常低的!“数据量大时可能会阻塞UI线程”,这对于中小型app不会有什么问题。从SharePeference到DataStore可能是Google内部的KPI原因,因为他们后台还是可以检测到的。
但是DataStore的功能确实比SharePeference更好。因为基于协程和Flow, 所以可以是异步的,而且可以监听值的变化。比如,用户购买了去广告内容,设置值后,前端就能接到通知了。这是SharePeference不具有的功能。
然而直接使用DataStore并不方便。 封装一下,就和SharePeference一样便捷了。
下面开始封装:
1. 创建DataStore
需求上,默认只有一个DataStore, 但是也可以创建多个。
所以做一下缓存。这一块代码借鉴了WanAndroid的源码。
object DataStoreFactory {
private const val USER_PREFERENCES = "default_user_preferences"
private lateinit var defaultDataStore: DataStore<Preferences>
private val dataStoreMaps = ConcurrentHashMap<String, DataStore<Preferences>>()
private val applicationScope =
CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineExceptionHandler { _, throwable ->
Log.e("",
"applicationScope:\n${throwable.message.toString()}", throwable
)
})
fun init(appContext: Context) {
getDefaultPreferencesDataStore(appContext)
}
private fun getDefaultPreferencesDataStore(appContext: Context): DataStore<Preferences> {
if (this::defaultDataStore.isInitialized.not()) {
defaultDataStore = createPreferencesDataStore(appContext, USER_PREFERENCES)
}
return defaultDataStore
}
fun getDefaultPreferencesDataStore() = defaultDataStore
fun getPreferencesDataStore(appContext: Context, name: String): DataStore<Preferences> =
dataStoreMaps.getOrPut(name) {
createPreferencesDataStore(appContext, name)
}
private fun createPreferencesDataStore(
appContext: Context,
name: String
): DataStore<Preferences> {
return PreferenceDataStoreFactory.create(
corruptionHandler = ReplaceFileCorruptionHandler(
produceNewData = { emptyPreferences() }
),
migrations = listOf(
SharedPreferencesMigration(
appContext,
name
)
),
applicationScope,
produceFile = { appContext.preferencesDataStoreFile(name) }
)
}
}
使用上首先要初始化一个默认叫“default_user_preferences”的Datastore。
open class BaseApp : Application() {
override fun onCreate() {
super.onCreate()
DataStoreFactory.init(this)
}
}
2. put/get接口封装
在构造函数里一定要通过DataStoreFactory传入DataStore对象。因为DataStoreFactory会把所有的DataStore管理起来。不至于出现相同名字导致的异常。
下面接口的封装,以读写Int类型为例:
putInt对外封装了一种,通常put只需要异步,极少需要同步的情况。
getInt对外封装了四种,分别是:getIntSync/getIntFlow/getInt。
getIntSync是同步的,会阻塞当前线程.
getIntFlow返回的是Flow<Int>, 用户可以根据需求用不同的方式接受新到的数据。
getInt其实是对getIntFlow的封装,通过内含协程作用域,封装了Flow, 使用更直观更方便。
class UserPreferences(private val dataSore: DataStore<Preferences> = DataStoreFactory.getDefaultPreferencesDataStore()) {
private val dataSoreData = dataSore.data
companion object {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO);
}
fun putInt(key: String, value: Int) = putData(intPreferencesKey(key), value)
fun getIntSync(key: String, defValue: Int): Int = readNonNullData(intPreferencesKey(key), defValue)
suspend fun getIntFlow(key: String, defValue: Int): Flow<Int> =
readNonNullFlowData(intPreferencesKey(key), defValue)
fun getInt(key: String, defValue: Int, block: (Int) -> Unit) =
readEmitData(intPreferencesKey(key), defValue, block)
private fun <T> readNonNullData(key: Preferences.Key<T>, defValue: T): T {
return runBlocking {
dataSoreData.map {
it[key] ?: defValue
}.first()
}
}
private fun <T> putData(key: Preferences.Key<T>, value: T?) {
scope.launch {
dataSore.edit {
if (value != null) it[key] = value else it.remove(key)
}
}
}
private suspend fun <T> readNonNullFlowData(key: Preferences.Key<T>, defValue: T): Flow<T> {
return Companion.scope.async {
dataSoreData.map {
it[key] ?: defValue
}
}.await();
}
}
下面是完整的UserPreferences类:
//call DataStoreFactory.init(appContext) in application onCreated first.
class UserPreferences(private val dataSore: DataStore<Preferences> = DataStoreFactory.getDefaultPreferencesDataStore()) {
private val dataSoreData = dataSore.data
companion object {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO);
}
fun putString(key: String, value: String?) = putData(stringPreferencesKey(key), value)
fun putStringSet(key: String, values: Set<String>?) =
putData(stringSetPreferencesKey(key), values)
fun putInt(key: String, value: Int) = putData(intPreferencesKey(key), value)
fun putLong(key: String, value: Long) = putData(longPreferencesKey(key), value)
fun putFloat(key: String, value: Float) = putData(floatPreferencesKey(key), value)
fun putBoolean(key: String, value: Boolean) = putData(booleanPreferencesKey(key), value)
fun getString(key: String, defValue: String?): String? =
readNullableData(stringPreferencesKey(key), defValue)
fun getStringSet(key: String, defValues: Set<String>?): Set<String>? =
readNullableData(stringSetPreferencesKey(key), defValues)
fun getIntSync(key: String, defValue: Int): Int =
readNonNullData(intPreferencesKey(key), defValue)
fun getLong(key: String, defValue: Long): Long =
readNonNullData(longPreferencesKey(key), defValue)
fun getFloat(key: String, defValue: Float): Float =
readNonNullData(floatPreferencesKey(key), defValue)
fun getBooleanSync(key: String, defValue: Boolean): Boolean =
readNonNullData(booleanPreferencesKey(key), defValue)
suspend fun getStringFlow(key: String, defValue: String): Flow<String> =
readNonNullFlowData(stringPreferencesKey(key), defValue)
suspend fun getStringSetFlow(key: String, defValues: Set<String>): Flow<Set<String>> =
readNonNullFlowData(stringSetPreferencesKey(key), defValues)
suspend fun getIntFlow(key: String, defValue: Int): Flow<Int> =
readNonNullFlowData(intPreferencesKey(key), defValue)
suspend fun getLongFlow(key: String, defValue: Long): Flow<Long> =
readNonNullFlowData(longPreferencesKey(key), defValue)
suspend fun getFloatFlow(key: String, defValue: Float): Flow<Float> =
readNonNullFlowData(floatPreferencesKey(key), defValue)
suspend fun getBooleanFlow(key: String, defValue: Boolean): Flow<Boolean> =
readNonNullFlowData(booleanPreferencesKey(key), defValue)
fun getInt(key: String, defValue: Int, block: (Int) -> Unit) =
readEmitData(intPreferencesKey(key), defValue, block)
fun getBoolean(key: String, defValue: Boolean, block: (Boolean) -> Unit) =
readEmitData(booleanPreferencesKey(key), defValue, block)
fun getString(key: String, defValue: String?, block: (String?) -> Unit) =
readEmitNullData(stringPreferencesKey(key), defValue, block)
fun getStringSet(key: String, defValues: Set<String>?, block: (Set<String>?) -> Unit) =
readEmitNullData(stringSetPreferencesKey(key), defValues, block)
fun getLong(key: String, defValue: Long, block: (Long) -> Unit) =
readEmitData(longPreferencesKey(key), defValue, block)
fun getFloat(key: String, defValue: Float, block: (Float) -> Unit) =
readEmitData(floatPreferencesKey(key), defValue, block)
private fun <T> readNonNullData(key: Preferences.Key<T>, defValue: T): T {
return runBlocking {
dataSoreData.map {
it[key] ?: defValue
}.first()
}
}
private fun <T> readNullableData(key: Preferences.Key<T>, defValue: T?): T? {
return runBlocking {
dataSoreData.map {
it[key] ?: defValue
}.firstOrNull()
}
}
private suspend fun <T> readNullableEmitFlowData(key: Preferences.Key<T>, defValue: T?): Flow<T?> {
return dataSoreData.catch {
if (it is IOException) {
it.printStackTrace()
emit(emptyPreferences())
} else {
throw it
}
}.map {
it[key] ?: defValue
}
}
private fun <T> putData(key: Preferences.Key<T>, value: T?) {
scope.launch {
dataSore.edit {
if (value != null) it[key] = value else it.remove(key)
}
}
}
private suspend fun <T> readNonNullFlowData(key: Preferences.Key<T>, defValue: T): Flow<T> {
return dataSoreData.catch {
if (it is IOException) {
it.printStackTrace()
emit(emptyPreferences())
} else {
throw it
}
}.map {
it[key] ?: defValue
}
}
private fun <T> readEmitData(key: Preferences.Key<T>, defValue: T, block: (T) -> Unit) {
scope.launch {
readNonNullFlowData(key, defValue).collectLatest {
block(it)
}
}
}
private fun <T> readEmitNullData(key: Preferences.Key<T>, defValue: T?, block: (T?) -> Unit) {
scope.launch {
readNullableEmitFlowData(key, defValue).collectLatest {
block(it)
}
}
}
}
3. 项目使用
以用户同意隐私政策为例。通常我们不会直接用getBoolean/putBoolean方式,而是会封装成一个有业务意义的名字,isAgree/setAgree. 使用哪种方式的get,同步还是异步,还是返回Flow,取决于自己的业务。
open class BaseApp : Application() {
private lateinit var userPreferences: UserPreferences
companion object {
public const val KEY_IS_USER_AGREE_PRIVACY = "is_user_agree_privacy"
}
override fun onCreate() {
super.onCreate()
DataStoreFactory.init(this)
userPreferences = UserPreferences()
//同步的方式。用户点击同意/不同意的button,不会回调到这里。同意后的初始逻辑需要在其他地方处理。
userPreferences.getBooleanSync(KEY_IS_USER_AGREE_PRIVACY, false).let {
if (it) {
// do init
}
}
//用户点击同意/不同意的button后,就会回调到这里。
userPreferences.getBoolean(KEY_IS_USER_AGREE_PRIVACY, false) {
if (it) {
// do init
}
}
}
fun isAgree() = userPreferences.getBooleanSync(KEY_IS_USER_AGREE_PRIVACY, false)
fun setAgree(isAgree: Boolean) = userPreferences.putBoolean(KEY_IS_USER_AGREE_PRIVACY, isAgree)
}
封装就到这里。不足之处,请大家指正! 觉得不错,留下你的赞!