Android 持久化存储原理与使用解析
一、核心存储方案详解
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提供临时权限控制,避免直接暴露文件路径的安全风险