1.SharePreferences
SharedPreferences:一個(gè)輕量級(jí)的存儲(chǔ)類,特別適合用于保存應(yīng)用配置參數(shù)。(是用xml文件存放數(shù)據(jù),文件存放在/data/data/<package name>/shared_prefs目錄下)

SharedPreferences使用:
1.保存數(shù)據(jù):
保存數(shù)據(jù)一般分為以下步驟:
使用Activity類的getSharedPreferences方法獲得SharedPreferences對(duì)象;
使用SharedPreferences接口的edit獲得SharedPreferences.Editor對(duì)象;
通過(guò)SharedPreferences.Editor接口的putXXX方法保存key-value對(duì);
通過(guò)過(guò)SharedPreferences.Editor接口的commit方法保存key-value對(duì)。
2.讀取數(shù)據(jù):
使用Activity類的getSharedPreferences方法獲得SharedPreferences對(duì)象;
通過(guò)SharedPreferences對(duì)象的getXXX方法獲取數(shù)據(jù);
3.示例:
//-------------------- SharePreferences -------------------------
//獲取SharePreferences
private val sp =
context.applicationContext.getSharedPreferences(BOOK_PREFERENCES_NAME, MODE_PRIVATE)
/**
* SharePreferences 存數(shù)據(jù)
*/
fun saveBookSP(book: BookBean) {
//commit默認(rèn)為false,采用異步提交。
sp.edit(commit = true) {
putString(KEY_BOOK_NAME, book.name)
putFloat(KEY_BOOK_PRICE, book.price)
putString(KEY_BOOK_TYPE, book.type.name)
}
}
/**
* SharePreferences 獲取數(shù)據(jù)
*/
val mBookInfo: BookBean
get() {
sp.apply {
var bookName = getString(KEY_BOOK_NAME, "") ?: ""
var bookPrice = getFloat(KEY_BOOK_PRICE, 0F)
var bookStr = getString(KEY_BOOK_TYPE, Type.MATH.name)
var bookType: Type = Type.valueOf(bookStr ?: Type.MATH.name)
return BookBean(bookName, bookPrice, bookType)
}
}
4.SharedPreferences缺點(diǎn):
- SP第一次加載數(shù)據(jù)時(shí)需要全量加載,當(dāng)數(shù)據(jù)量大時(shí)可能會(huì)阻塞UI線程造成卡頓
- SP讀寫文件不是類型安全的,且沒有發(fā)出錯(cuò)誤信號(hào)的機(jī)制,缺少事務(wù)性API
- commit() / apply()操作可能會(huì)造成ANR問(wèn)題:
commit()是同步提交,會(huì)在UI主線程中直接執(zhí)行IO操作,當(dāng)寫入操作耗時(shí)比較長(zhǎng)時(shí)就會(huì)導(dǎo)致UI線程被阻塞,進(jìn)而產(chǎn)生ANR;apply()雖然是異步提交,但異步寫入磁盤時(shí),如果執(zhí)行了Activity / Service中的onStop()方法,那么一樣會(huì)同步等待SP寫入完畢,等待時(shí)間過(guò)長(zhǎng)時(shí)也會(huì)引起ANR問(wèn)題。針對(duì)apply()我們展開來(lái)看一下:
SharedPreferencesImpl#EditorImpl.java中最終執(zhí)行了apply()函數(shù):
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
//采用final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
...
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//異步執(zhí)行磁盤寫入操作
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
創(chuàng)建一個(gè)awaitCommit的Runnable任務(wù)并將其加入到QueuedWork中,該任務(wù)內(nèi)部直接調(diào)用了CountDownLatch.await()方法,即直接在UI線程執(zhí)行等待操作,那么我們看QueuedWork中何時(shí)執(zhí)行這個(gè)任務(wù)。
QueuedWork.java:
public class QueuedWork {
private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
public static void waitToFinish() {
...
Handler handler = getHandler();
try {
//8.0之后優(yōu)化,會(huì)主動(dòng)嘗試執(zhí)行寫磁盤任務(wù)
processPendingWork();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
//從隊(duì)列中取出任務(wù)
finisher = sFinishers.poll();
}
//如果任務(wù)為空,則跳出循環(huán),UI線程可以繼續(xù)往下執(zhí)行
if (finisher == null) {
break;
}
//任務(wù)不為空,執(zhí)行CountDownLatch.await(),即UI線程會(huì)阻塞等待
finisher.run();
}
} finally {
sCanDelay = true;
}
}
}
waitToFinish()方法會(huì)嘗試從Runnable任務(wù)隊(duì)列中取任務(wù),如果有的話直接取出并執(zhí)行,我們看看哪里調(diào)用了waitToFinish():
ActivityThread.java
@Override
public void handleStopActivity(IBinder token, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
// Make sure any pending writes are now committed.
if (!r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
}
private void handleStopService(IBinder token) {
QueuedWork.waitToFinish();
}
可以看到在ActivityThread中handleStopActivity、handleStopService方法中都會(huì)調(diào)用waitToFinish()方法,即在Activity的onStop()中、Service的onStop()中都會(huì)先同步等待寫入任務(wù)完成才會(huì)繼續(xù)執(zhí)行。
所以apply()雖然是異步寫入磁盤,但是如果此時(shí)執(zhí)行到Activity/Service的onStop(),依然可能會(huì)阻塞UI線程導(dǎo)致ANR。
2.DataStore
??Jetpack DataStore 是一種改進(jìn)的數(shù)據(jù)存儲(chǔ)解決方案,允許您使用協(xié)議緩沖區(qū)存儲(chǔ)鍵值對(duì)或類型化對(duì)象。
??DataStore 使用 Kotlin 協(xié)程和 Flow 以異步、一致的事務(wù)方式存儲(chǔ)數(shù)據(jù)。并且可以對(duì)SP數(shù)據(jù)進(jìn)行遷移,旨在取代SP。如果正在使用SharedPreferences 存儲(chǔ)數(shù)據(jù),請(qǐng)考慮遷移到 DataStore。
Jetpack DataStore 有兩種實(shí)現(xiàn)方式:
- Preferences DataStore:以鍵值對(duì)的形式存儲(chǔ)在本地類似 SharedPreferences 。
- Proto DataStore:存儲(chǔ)類的對(duì)象(typed objects ),通過(guò) protocol buffers 將對(duì)象序列化存儲(chǔ)在本地。
Preferences DataStore使用
1.添加依賴項(xiàng):
implementation 'androidx.datastore:datastore-preferences:1.0.0'
2.構(gòu)建Preferences DataStore:
/**
* TODO:創(chuàng)建 Preferences DataStore
* 參數(shù)1:name:創(chuàng)建Preferences DataStore文件名稱。
* 會(huì)在/data/data/項(xiàng)目報(bào)名/files/下創(chuàng)建名為pf_dataastore的文件
* 參數(shù)2:corruptionHandler:如果DataStore在試圖讀取數(shù)據(jù)時(shí),數(shù)據(jù)無(wú)法反序列化,會(huì)拋出androidx.datastore.core.CorruptionException,
* 此時(shí)會(huì)執(zhí)行corruptionHandler。
* 參數(shù)3:produceMigrations:SP產(chǎn)生遷移到Preferences DataStore。ApplicationContext作為參數(shù)傳遞給這些回調(diào),遷移在對(duì)數(shù)據(jù)進(jìn)行任何訪問(wèn)之前運(yùn)行。
* 參數(shù)4:scope:協(xié)成的作用域,默認(rèn)IO操作在Dispatchers.IO線程執(zhí)行。
*/
val Context.dataStorePf: DataStore<Preferences> by preferencesDataStore(
//文件名稱
name = "preferences_dataStore")
當(dāng)我們構(gòu)建后,會(huì)在/data/data/<package name>/files/下創(chuàng)建名為preferences_dataStore的文件如下:

Preferences DataStore使用:
1.構(gòu)建Preferences DataStore
//常量
const val BOOK_PREFERENCES_NAME = "book_preferences"
const val KEY_BOOK_NAME = "key_book_name"
const val KEY_BOOK_PRICE = "key_book_price"
const val KEY_BOOK_TYPE = "key_book_type"
/**
* TODO:創(chuàng)建 Preferences DataStore
* 參數(shù)1:name:創(chuàng)建Preferences DataStore文件名稱。
* 會(huì)在/data/data/項(xiàng)目報(bào)名/files/下創(chuàng)建名為pf_dataastore的文件
* 參數(shù)2:corruptionHandler:如果DataStore在試圖讀取數(shù)據(jù)時(shí),數(shù)據(jù)無(wú)法反序列化,會(huì)拋出androidx.datastore.core.CorruptionException,
* 此時(shí)會(huì)執(zhí)行corruptionHandler。
* 參數(shù)3:produceMigrations:SP產(chǎn)生遷移到Preferences DataStore。ApplicationContext作為參數(shù)傳遞給這些回調(diào),遷移在對(duì)數(shù)據(jù)進(jìn)行任何訪問(wèn)之前運(yùn)行。
* 參數(shù)4:scope:協(xié)成的作用域,默認(rèn)IO操作在Dispatchers.IO線程執(zhí)行。
*/
val Context.dataStorePf: DataStore<Preferences> by preferencesDataStore(
name = "preferences_dataStore")
2.存儲(chǔ)的實(shí)體類:
data class BookBean( var name: String = "",
var price: Float = 0f,
var type: Type = Type.ENGLISH) {
}
enum class Type{
MATH, //數(shù)學(xué)
CHINESE, //語(yǔ)文
ENGLISH //英語(yǔ)
}
3.數(shù)據(jù)存儲(chǔ)/獲?。?/strong>
Activity中:
//-------------------- Preferences DataStore -------------------------
/**
* TODO:Preferences DataStore 保存數(shù)據(jù)
*/
fun savePD(view: View) {
val book = BookBean("張三", 25f, Type.CHINESE)
viewModel.saveBookPD(book)
}
/**
* TODO:Preferences DataStore 獲取數(shù)據(jù)
*/
fun getPD(view: View) {
lifecycleScope.launch {
viewModel.bookPfFlow.collect {
tv_pd_data.text = it.toString()
}
}
}
ViewModel中:
//-------------------- Preferences DataStore -------------------------
/**
* TODO:Preferences DataStore 保存數(shù)據(jù) 必須在協(xié)程中進(jìn)行
*/
fun saveBookPD(bookBean: BookBean) {
viewModelScope.launch {
dataStoreRepo.saveBookPD(bookBean)
}
}
/**
* TODO:Preferences DataStore 獲取數(shù)據(jù)
*/
val bookPfFlow = dataStoreRepo.bookPDFlow
Repository類中:
//-------------------- Preferences DataStore -------------------------
/**
* Preferences DataStore 存數(shù)據(jù)
*/
suspend fun saveBookPD(book: BookBean) {
context.dataStorePf.edit { preferences ->
preferences[PreferenceKeys.P_KEY_BOOK_NAME] = book.name
preferences[PreferenceKeys.P_KEY_BOOK_PRICE] = book.price
preferences[PreferenceKeys.P_KEY_BOOK_TYPE] = book.type.name
}
}
/**
* Preferences DataStore 獲取數(shù)據(jù)
*/
val bookPDFlow: Flow<BookBean> = context.dataStorePf.data
.map { preferences ->
// No type safety.
val name = preferences[PreferenceKeys.P_KEY_BOOK_NAME] ?: ""
val bookPrice = preferences[PreferenceKeys.P_KEY_BOOK_PRICE] ?: 0f
val bookType =
Type.valueOf(preferences[PreferenceKeys.P_KEY_BOOK_TYPE] ?: Type.MATH.name)
return@map BookBean(name, bookPrice, bookType)
}
SP遷移至Preferences DataStore
如果想將項(xiàng)目的SP進(jìn)行遷移,只需要在Preferences DataStore在構(gòu)建時(shí)配置參數(shù)3,如下:
//SharedPreference文件名
const val BOOK_PREFERENCES_NAME = "book_preferences"
val Context.dataStorePf: DataStore<Preferences> by preferencesDataStore(
name = "preferences_dataStore",
//將SP遷移到Preference DataStore中
produceMigrations = { context ->
listOf(SharedPreferencesMigration(context, BOOK_PREFERENCES_NAME))
})
這樣構(gòu)建完成時(shí),SP中的內(nèi)容也會(huì)遷移到Preferences DataStore中了,注意遷移是一次性的,即執(zhí)行遷移后,SP文件會(huì)被刪除.
3.MMKV
MMKV 是基于 mmap 內(nèi)存映射的 key-value 組件,底層序列化/反序列化使用 protobuf 實(shí)現(xiàn),性能高,穩(wěn)定性強(qiáng)。
MMKV 原理:
-
內(nèi)存準(zhǔn)備
通過(guò) mmap 內(nèi)存映射文件,提供一段可供隨時(shí)寫入的內(nèi)存塊,App 只管往里面寫數(shù)據(jù),由操作系統(tǒng)負(fù)責(zé)將內(nèi)存回寫到文件,不必?fù)?dān)心 crash 導(dǎo)致數(shù)據(jù)丟失。 -
數(shù)據(jù)組織
數(shù)據(jù)序列化方面我們選用 protobuf 協(xié)議,pb 在性能和空間占用上都有不錯(cuò)的表現(xiàn)。 -
寫入優(yōu)化
考慮到主要使用場(chǎng)景是頻繁地進(jìn)行寫入更新,我們需要有增量更新的能力。我們考慮將增量 kv 對(duì)象序列化后,append 到內(nèi)存末尾。 -
空間增長(zhǎng)
使用 append 實(shí)現(xiàn)增量更新帶來(lái)了一個(gè)新的問(wèn)題,就是不斷 append 的話,文件大小會(huì)增長(zhǎng)得不可控。我們需要在性能和空間上做個(gè)折中。
MMKV使用:
1.添加依賴:
implementation 'com.tencent:mmkv:1.2.13'
2.Application的onCreate方法中初始化
class App:Application() {
override fun onCreate() {
super.onCreate()
val rootDir = MMKV.initialize(this)
Log.e("TAG","mmkv root: $rootDir")
}
}
3.數(shù)據(jù)存儲(chǔ)/獲?。?/strong>
MMKV kv = MMKV.defaultMMKV();
kv.encode("bool", true);
boolean bValue = kv.decodeBool("bool");
kv.encode("int", Integer.MIN_VALUE);
int iValue = kv.decodeInt("int");
kv.encode("string", "Hello from mmkv");
String str = kv.decodeString("string");