一、核心存儲方案詳解
1.?SharedPreferences (SP)
使用方式:
// 獲取實例
SharedPreferences sp = getSharedPreferences("user_prefs", MODE_PRIVATE);// 寫入數據
sp.edit().putString("username", "john_doe").putInt("login_count", 5).apply(); // 異步提交// 讀取數據
String username = sp.getString("username", "default");
int loginCount = sp.getInt("login_count", 0);
原理流程:
優點:
簡單易用,Android 原生支持
適合存儲小量鍵值對數據
缺點:
???全量寫入:修改單個值也重寫整個文件
???ANR 風險:
apply()
?異步提交在生命周期回調時可能阻塞主線程??多進程不安全:
MODE_MULTI_PROCESS
?已廢棄??無類型安全:讀取時需手動轉換類型
使用場景:
低頻修改的簡單配置(如用戶主題設置、功能開關)
2.?MMKV(微信開源)
使用方式:
// build.gradle
implementation 'com.tencent:mmkv:1.3.0'
// 初始化
String rootDir = MMKV.initialize(this);// 獲取實例
MMKV kv = MMKV.defaultMMKV();// 寫入數據
kv.encode("session_token", "a1b2c3d4");
kv.encode("user_points", 1500);// 讀取數據
String token = kv.decodeString("session_token");
int points = kv.decodeInt("user_points");
原理流程:
優點:
??高性能:讀寫速度比SP快100倍+
🔒?多進程支持:完善的文件鎖機制
📦?高效存儲:Protobuf編碼節省50%空間
🔐?加密支持:AES加密敏感數據
缺點:
? 需引入三方庫
?? 數據模型較簡單(適合鍵值對)
使用場景:
高頻讀寫數據(如用戶積分)、多進程共享配置、替代SP的所有場景
3.?DataStore(Google官方)
使用方式(Preferences DataStore):
// build.gradle
implementation "androidx.datastore:datastore-preferences:1.0.0"
// 定義Key
val USER_NAME = stringPreferencesKey("user_name")
val LOGIN_COUNT = intPreferencesKey("login_count")// 創建DataStore
val dataStore: DataStore<Preferences> = context.createDataStore(name = "settings")// 寫入數據
suspend fun saveData(name: String, count: Int) {dataStore.edit { preferences ->preferences[USER_NAME] = namepreferences[LOGIN_COUNT] = count}
}// 讀取數據
val userNameFlow: Flow<String> = dataStore.data.map { preferences -> preferences[USER_NAME] ?: "" }
Proto DataStore(類型安全):
// user_prefs.proto
message UserPrefs {string name = 1;int32 login_count = 2;bool is_premium = 3;
}
val Context.userPrefsStore: DataStore<UserPrefs> by dataStore(fileName = "user_prefs.pb",serializer = UserPrefsSerializer
)// 直接操作對象
viewModelScope.launch {context.userPrefsStore.updateData { prefs ->prefs.toBuilder().setLoginCount(10).build()}
}
優點:
🛡??類型安全:Protobuf 編譯時校驗
??異步操作:基于協程,無主線程阻塞風險
🔄?數據流支持:響應式數據更新
🔄?平滑遷移:提供SP遷移工具
缺點:
📚 學習曲線較陡峭(需掌握協程/Protobuf)
🚫 不支持多進程
使用場景:
新項目開發、復雜數據模型存儲、響應式配置更新
二、Intent 數據傳輸限制
使用方式:
// 傳遞數據
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("user_id", 12345);
intent.putExtra("document", pdfByteArray); // 危險操作!
startActivity(intent);// 接收數據
int userId = getIntent().getIntExtra("user_id", 0);
byte[] pdfData = getIntent().getByteArrayExtra("document");
限制原因:
為什么只能傳少量數據?
Binder 限制:IPC傳輸緩沖區固定為?1MB(Android 8.0+ 部分設備2MB)
性能問題:大數據序列化/反序列化消耗CPU和內存
穩定性風險:可能引發OOM或ANR
生命周期不匹配:Activity可能被銷毀重建,丟失數據
解決方案:
數據大小 | 推薦方案 | 示例 |
---|---|---|
< 100KB | 直接Intent傳遞 | intent.putExtra("id", 123) |
100KB ~ 1MB | FileProvider共享文件 | 傳遞content:// URI |
> 1MB | 持久化存儲+標識傳遞 | 數據庫ID/文件路徑 |
復雜對象 | Parcelable序列化 | 實現Parcelable接口 |
三、方案對比與選型指南
維度 | SharedPreferences | MMKV | DataStore | Intent |
---|---|---|---|---|
存儲類型 | 鍵值對 | 鍵值對 | 鍵值對/Protobuf對象 | 臨時數據 |
性能 | 低 | 極高 | 高 | 中 |
線程安全 | 主線程風險 | 安全 | 安全(協程) | 主線程安全 |
多進程 | ? | ? | ? | ?(IPC) |
ANR風險 | 高 | 無 | 無 | 高(大數據) |
數據大小 | <1MB | 無限制 | 無限制 | <1MB |
推薦場景 | 低頻配置項 | 高頻讀寫/多進程 | 新項目/類型安全 | 小數據傳遞 |
四、常見問題總結
Q1:SharedPreferences有什么缺陷?如何優化?
A:
主要缺陷:
全量寫入導致I/O性能差
apply()
異步提交可能引發ANR(ActivityThread等待QueuedWork)多進程不安全(
MODE_MULTI_PROCESS
已廢棄)無類型安全檢查
優化方案:
遷移到MMKV或DataStore
避免存儲超過1MB數據
對高頻修改項單獨拆分文件
Q2:MMKV為什么比SharedPreferences快?
A:
MMKV通過三重優化實現高性能:
mmap內存映射:文件直連內存,省去系統調用和數據拷貝
Protobuf編碼:比XML節省50%+存儲空間
增量更新:只追加修改數據,避免全文件重寫
多進程鎖:通過文件鎖實現安全并發訪問
Q3:DataStore相比SP的核心優勢?
A:
DataStore的四大優勢:
無ANR設計:協程異步API徹底避免主線程阻塞
類型安全:Proto DataStore支持編譯時類型檢查
響應式編程:通過Flow實現數據變更監聽
事務支持:
edit{}
塊內操作保證原子性
Q4:Intent為什么不能傳大數據?
A:
Intent傳輸受限于三點:
Binder IPC限制:傳輸緩沖區固定1-2MB
序列化開銷:大數據序列化消耗CPU/內存,可能導致ANR
生命周期風險:Activity重建時系統可能丟棄Intent數據
解決方案:
<1MB:直接使用Intent
1-10MB:通過FileProvider傳遞URI
10MB:持久化存儲后傳遞標識符
五、場景選擇
六、高頻問題總結
SP的
apply()
和commit()
區別?apply()
異步提交但不返回結果,commit()
同步提交并返回boolean結果。注意apply()
可能導致ANR。MMKV如何保證多進程安全?
通過
fcntl
文件鎖實現寫互斥,跨進程場景使用pthread_mutex
(需處理robust屬性)。DataStore如何從SP遷移?
val dataStore = createDataStore(preferencesMigration = SharedPreferencesMigration(context, "sp_name"))
Intent傳遞大Bitmap的正確方式?
// 步驟1:保存到文件 File file = saveBitmapToCache(bitmap); // 步驟2:通過FileProvider生成URI Uri uri = FileProvider.getUriForFile(context, "com.example.provider", file);// 步驟3:傳遞URI并設置權限 intent.putExtra("image_uri", uri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
為什么推薦用FileProvider不用絕對路徑?
FileProvider提供臨時權限控制,避免直接暴露文件路徑的安全風險