Android 持久化存储原理与使用解析

发布于:2025-07-25 ⋅ 阅读:(17) ⋅ 点赞:(0)
一、核心存储方案详解
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] = name
        preferences[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");

限制原因:

为什么只能传少量数据?

  1. Binder 限制:IPC传输缓冲区固定为 1MB(Android 8.0+ 部分设备2MB)

  2. 性能问题:大数据序列化/反序列化消耗CPU和内存

  3. 稳定性风险:可能引发OOM或ANR

  4. 生命周期不匹配: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:
主要缺陷:

  1. 全量写入导致I/O性能差

  2. apply()异步提交可能引发ANR(ActivityThread等待QueuedWork)

  3. 多进程不安全(MODE_MULTI_PROCESS已废弃)

  4. 无类型安全检查

优化方案:

  1. 迁移到MMKV或DataStore

  2. 避免存储超过1MB数据

  3. 对高频修改项单独拆分文件

Q2:MMKV为什么比SharedPreferences快?

A:
MMKV通过三重优化实现高性能:

  1. mmap内存映射:文件直连内存,省去系统调用和数据拷贝

  2. Protobuf编码:比XML节省50%+存储空间

  3. 增量更新:只追加修改数据,避免全文件重写

  4. 多进程锁:通过文件锁实现安全并发访问

Q3:DataStore相比SP的核心优势?

A:
DataStore的四大优势:

  1. 无ANR设计:协程异步API彻底避免主线程阻塞

  2. 类型安全:Proto DataStore支持编译时类型检查

  3. 响应式编程:通过Flow实现数据变更监听

  4. 事务支持edit{}块内操作保证原子性

Q4:Intent为什么不能传大数据?

A:
Intent传输受限于三点:

  1. Binder IPC限制:传输缓冲区固定1-2MB

  2. 序列化开销:大数据序列化消耗CPU/内存,可能导致ANR

  3. 生命周期风险:Activity重建时系统可能丢弃Intent数据

解决方案:

  • <1MB:直接使用Intent

  • 1-10MB:通过FileProvider传递URI

  • 10MB:持久化存储后传递标识符


五、场景选择


六、高频问题总结

  1. SP的apply()commit()区别?

    apply()异步提交但不返回结果,commit()同步提交并返回boolean结果。注意apply()可能导致ANR。

  2. MMKV如何保证多进程安全?

    通过fcntl文件锁实现写互斥,跨进程场景使用pthread_mutex(需处理robust属性)。

  3. DataStore如何从SP迁移?

    val dataStore = createDataStore(preferencesMigration = SharedPreferencesMigration(context, "sp_name"))

  4. 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);
  5. 为什么推荐用FileProvider不用绝对路径?

    FileProvider提供临时权限控制,避免直接暴露文件路径的安全风险


网站公告

今日签到

点亮在社区的每一天
去签到