Android 本地存储方案深度解析:SharedPreferences、DataStore、MMKV 全面对比

164 阅读6分钟

前言:

在 Android 开发中,键值对存储 是最常见的持久化需求之一。
无论是保存登录 Token、主题模式、还是缓存用户偏好,我们常见的方案有三种:

👉 SharedPreferences
👉 DataStore(官方推荐替代)
👉 MMKV(腾讯出品高性能方案)

那么,三者到底有什么区别?该如何选择?本篇将从原理、性能、使用方式、优缺点到最佳实践,带你一文搞懂。


🧩 一、整体对比总览

特性SharedPreferencesDataStoreMMKV
存储方式XML 文件Preferences / Proto(二进制)mmap(内存映射 + protobuf)
线程安全❌ 需自行控制✅ 完全线程安全✅ 完全线程安全
异步支持部分异步 (apply)✅ 完全异步(协程)✅ 几乎同步(极快)
性能中等(磁盘 I/O 频繁)稍慢(安全但略重)⚡️最快(mmap 内存映射)
跨进程❌ 不支持❌ 不支持(默认)✅ 支持
数据类型基础类型基础类型 / Proto 对象基础类型 + ByteArray
文件位置/shared_prefs/*.xml/datastore/*.preferences_pb/files/mmkv/*.mmkv
官方推荐度✅ 老项目使用✅ 官方推荐方案✅ 企业级性能首选

🧠 二、原理与机制分析

1️⃣ SharedPreferences:老牌 XML 存储

SharedPreferences 是 Android 最早的轻量存储方案,底层基于 XML 文件 实现。

  • 写入:commit() 同步写入磁盘;apply() 异步提交。
  • 读取:同步加载 XML 文件解析为内存 Map。
  • 问题:在主线程频繁读写会导致卡顿或 ANR。
val sp = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
sp.edit().putString("token", "abc123").apply()

val token = sp.getString("token", null)

✅ 优点:

  • 系统自带,API 简单稳定。
  • 无需额外依赖。

❌ 缺点:

  • XML I/O 成本高。
  • 线程安全性差。
  • 并发写入容易丢数据。

2️⃣ DataStore:Google 官方现代替代方案

DataStore 是 Jetpack 组件之一,用于替代 SharedPreferences。

分为两种模式:

🧾 Preferences DataStore

无需定义结构,直接使用键值对:

val Context.dataStore by preferencesDataStore(name = "settings")
val KEY_TOKEN = stringPreferencesKey("token")

suspend fun saveToken(context: Context, token: String) {
    context.dataStore.edit { prefs ->
        prefs[KEY_TOKEN] = token
    }
}

val tokenFlow: Flow<String?> = context.dataStore.data
    .map { prefs -> prefs[KEY_TOKEN] }

🧬 Proto DataStore

基于 Protobuf 定义 Schema,可存储复杂对象:

syntax = "proto3";
message UserProfile {
  string name = 1;
  int32 age = 2;
}

然后使用自动生成的 UserProfileSerializer 存储类型化数据。

✅ 优点:

  • 异步、线程安全、无数据丢失。
  • 原生支持 Flow 响应式更新
  • 官方推荐未来方向。

❌ 缺点:

  • 首次初始化耗时略高。
  • 不支持跨进程。
  • Proto 模式使用门槛稍高。

3️⃣ MMKV:腾讯出品的极致性能方案

MMKV = Memory Mapped Key-Value
核心原理:mmap 内存映射文件 + protobuf 序列化

数据在内存中映射到磁盘,写入无需频繁磁盘 I/O,性能极高。

MMKV.initialize(context)
val mmkv = MMKV.defaultMMKV()

mmkv.encode("token", "abc123")
mmkv.encode("isLogin", true)

val token = mmkv.decodeString("token")

✅ 优点:

  • ⚡️极快(毫秒级甚至微秒级)。
  • ✅ 完全线程安全。
  • ✅ 支持多进程、加密存储。
  • ✅ API 简洁易用。

❌ 缺点:

  • 需额外依赖库。
  • 少数场景文件损坏风险(官方有修复机制)。
  • 不是官方组件。

⚡️ 三、性能实测对比(单位:毫秒)

操作SharedPreferencesDataStoreMMKV
写入 1 次~1.5 ms~3–5 ms~0.1 ms
读取 1 次~1.2 ms~2 ms~0.05 ms
并发写入❌ 阻塞/丢失✅ 协程安全✅ 无锁高效
初始化时间稍慢

👉 结论:
在频繁读写场景(如聊天缓存、游戏状态)中,MMKV 的性能优势极其明显。


🧭 四、选型建议

使用场景推荐方案
简单用户配置✅ DataStore(Preferences)
高频缓存 / IM / 游戏✅ MMKV
响应式 UI(Flow 监听)✅ DataStore
旧项目快速兼容✅ SharedPreferences
多进程共享数据✅ MMKV
存储复杂对象✅ Proto DataStore 或 MMKV

🧰 五、实际项目中的组合使用建议

在成熟项目中,推荐按用途分层存储:

AppSettingStore —— DataStore(语言、主题、隐私设置)
AppCache —— MMKV(Token、临时缓存)
LegacyConfig —— SharedPreferences(旧数据迁移)

SharedPreferences → DataStore 迁移示例:

context.dataStore.updateData { prefs ->
    val oldSp = context.getSharedPreferences("old_prefs", Context.MODE_PRIVATE)
    prefs.toMutablePreferences().apply {
        this[stringPreferencesKey("token")] = oldSp.getString("token", "") ?: ""
    }
}


🧪 附录:三种存储方案 Demo 对比与性能测试

📁 一、SharedPreferences 示例

class SpDemo(private val context: Context) {

    private val sp = context.getSharedPreferences("sp_demo", Context.MODE_PRIVATE)

    fun saveData(key: String, value: String) {
        val start = System.currentTimeMillis()
        sp.edit().putString(key, value).apply()
        Log.d("SP_TEST", "写入耗时: ${System.currentTimeMillis() - start} ms")
    }

    fun readData(key: String): String? {
        val start = System.currentTimeMillis()
        val value = sp.getString(key, null)
        Log.d("SP_TEST", "读取耗时: ${System.currentTimeMillis() - start} ms")
        return value
    }
}


📦 二、DataStore(Preferences)示例

依赖:

implementation "androidx.datastore:datastore-preferences:1.1.1"

使用:

val Context.dataStore by preferencesDataStore(name = "datastore_demo")

object DataKeys {
    val KEY_NAME = stringPreferencesKey("name")
}

class DataStoreDemo(private val context: Context) {

    suspend fun saveData(value: String) {
        val start = System.currentTimeMillis()
        context.dataStore.edit { prefs ->
            prefs[DataKeys.KEY_NAME] = value
        }
        Log.d("DATASTORE_TEST", "写入耗时: ${System.currentTimeMillis() - start} ms")
    }

    fun readData(): Flow<String?> = context.dataStore.data.map { prefs ->
        val start = System.currentTimeMillis()
        val value = prefs[DataKeys.KEY_NAME]
        Log.d("DATASTORE_TEST", "读取耗时: ${System.currentTimeMillis() - start} ms")
        value
    }
}

监听更新(响应式 UI):

lifecycleScope.launch {
    dataStoreDemo.readData().collect { value ->
        Log.d("DATASTORE_FLOW", "DataStore 监听到新数据:$value")
    }
}


⚡️ 三、MMKV 示例

依赖:

implementation 'com.tencent:mmkv-static:1.3.4'

使用:

class MMKVDemo(context: Context) {

    private val mmkv = MMKV.defaultMMKV()

    fun saveData(key: String, value: String) {
        val start = System.currentTimeMillis()
        mmkv.encode(key, value)
        Log.d("MMKV_TEST", "写入耗时: ${System.currentTimeMillis() - start} ms")
    }

    fun readData(key: String): String? {
        val start = System.currentTimeMillis()
        val value = mmkv.decodeString(key)
        Log.d("MMKV_TEST", "读取耗时: ${System.currentTimeMillis() - start} ms")
        return value
    }
}


⚙️ 四、统一测试入口(对比三种方案性能)

class StorageCompareActivity : AppCompatActivity() {

    private lateinit var spDemo: SpDemo
    private lateinit var dsDemo: DataStoreDemo
    private lateinit var mmkvDemo: MMKVDemo

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        spDemo = SpDemo(this)
        dsDemo = DataStoreDemo(this)
        mmkvDemo = MMKVDemo(this)

        lifecycleScope.launch {
            testPerformance()
        }
    }

    private suspend fun testPerformance() {
        val testKey = "test_key"
        val testValue = "Hello Android!"

        // 1. SharedPreferences
        spDemo.saveData(testKey, testValue)
        spDemo.readData(testKey)

        // 2. DataStore
        dsDemo.saveData(testValue)
        dsDemo.readData().firstOrNull()

        // 3. MMKV
        mmkvDemo.saveData(testKey, testValue)
        mmkvDemo.readData(testKey)
    }
}

运行后可在 Logcat 中对比输出:

SP_TEST: 写入耗时: 2 ms
SP_TEST: 读取耗时: 1 ms
DATASTORE_TEST: 写入耗时: 4 ms
DATASTORE_TEST: 读取耗时: 3 ms
MMKV_TEST: 写入耗时: 0 ms
MMKV_TEST: 读取耗时: 0 ms

🧩 五、实测结果总结

操作SharedPreferencesDataStoreMMKV
单次写入~1–2 ms~3–5 ms<0.1 ms
单次读取~1 ms~2 ms<0.1 ms
并发安全⚠️ 较差✅ 安全✅ 安全
是否异步部分✅ 完全异步✅ 快速同步
跨进程❌ 不支持❌ 不支持✅ 支持

✅ 六、推荐实践(项目实战结构)

com.example.app.storage
│
├── MMKVDemo.kt         → 高频缓存 / 登录状态
├── DataStoreDemo.kt     → 用户设置、隐私选项
└── SpDemo.kt            → 旧版本迁移支持

混合方案示例:

object AppStorage {
    val userPrefs by lazy { DataStoreDemo(App.context) }
    val cache by lazy { MMKVDemo(App.context) }
    val legacy by lazy { SpDemo(App.context) }
}


💬 七、总结

方案优点缺点一句话总结适用场景关键特性
SharedPreferences✅ 简单、系统原生❌ 性能差、线程不安全、I/O 开销大老牌方案,适合小量配置,性能一般。老项目、低频读写简单、兼容性强
DataStore✅ 异步、线程安全、支持 Flow 响应式❌ 初始化慢、不支持跨进程官方推荐替代方案,安全、响应式。响应式偏好存储Flow + 协程安全
MMKV⚡️ 极高性能、线程安全、跨进程、支持加密⚠️ 库体积略大、需额外依赖企业级高性能存储方案,快得离谱。高频读写、高性能场景mmap + Protobuf,极快

如果你的 App 是中小型应用,推荐使用 DataStore

如果是 IM、社交、游戏类高性能需求,推荐使用 MMKV

如果是老项目,可以逐步从 SharedPreferences 迁移。

💡 参考资料: