Flutter数据存储:SharedPreferences的本地存储应用
Flutter 本地存储简介
在移动应用开发中,数据存储是一个至关重要的环节。本地存储允许应用在设备上保存数据,以便在应用关闭和重新打开后仍能访问这些数据。Flutter 提供了多种本地存储解决方案,其中 SharedPreferences
是一种轻量级、简单易用的键值对存储方式,适用于存储少量的简单数据,如用户设置、应用配置等。
SharedPreferences 基础
SharedPreferences
是 Flutter 提供的一种持久化存储机制,它基于 Android 平台的 SharedPreferences
和 iOS 平台的 NSUserDefaults
实现。它以键值对的形式存储数据,支持的数据类型包括布尔值(bool
)、整数(int
)、双精度浮点数(double
)、字符串(string
)和字符串列表(List<String>
)。
引入依赖
在使用 SharedPreferences
之前,需要在 pubspec.yaml
文件中引入依赖:
dependencies:
shared_preferences: ^2.0.15
然后运行 flutter pub get
来获取依赖。
读写数据
读取数据
要读取 SharedPreferences
中的数据,首先需要获取 SharedPreferences
的实例。可以通过 SharedPreferences.getInstance()
方法异步获取实例:
import 'package:shared_preferences/shared_preferences.dart';
Future<void> readData() async {
final prefs = await SharedPreferences.getInstance();
// 读取布尔值
final bool? boolValue = prefs.getBool('bool_key');
// 读取整数
final int? intValue = prefs.getInt('int_key');
// 读取双精度浮点数
final double? doubleValue = prefs.getDouble('double_key');
// 读取字符串
final String? stringValue = prefs.getString('string_key');
// 读取字符串列表
final List<String>? stringListValue = prefs.getStringList('string_list_key');
}
在上述代码中,getBool
、getInt
、getDouble
、getString
和 getStringList
方法分别用于获取相应类型的数据。如果键不存在,这些方法将返回 null
。
写入数据
写入数据同样需要获取 SharedPreferences
的实例,然后使用相应的 set
方法来写入数据:
import 'package:shared_preferences/shared_preferences.dart';
Future<void> writeData() async {
final prefs = await SharedPreferences.getInstance();
// 写入布尔值
await prefs.setBool('bool_key', true);
// 写入整数
await prefs.setInt('int_key', 42);
// 写入双精度浮点数
await prefs.setDouble('double_key', 3.14);
// 写入字符串
await prefs.setString('string_key', 'Hello, SharedPreferences!');
// 写入字符串列表
await prefs.setStringList('string_list_key', ['item1', 'item2']);
}
上述代码中,setBool
、setInt
、setDouble
、setString
和 setStringList
方法分别用于设置相应类型的数据。这些方法返回一个 Future<bool>
,表示操作是否成功。
数据类型处理细节
布尔值
布尔值的存储和读取相对简单。在存储时,使用 setBool
方法,在读取时使用 getBool
方法。注意,当读取不存在的布尔值键时,会返回 null
。如果应用有默认值需求,可以在获取时进行判断并设置默认值:
Future<void> handleBoolData() async {
final prefs = await SharedPreferences.getInstance();
// 写入布尔值
await prefs.setBool('is_user_logged_in', true);
// 读取布尔值并设置默认值
final bool isLoggedIn = prefs.getBool('is_user_logged_in') ?? false;
}
整数
整数的存储和读取使用 setInt
和 getInt
方法。与布尔值类似,读取不存在的键会返回 null
。如果应用对整数有特定的取值范围要求,例如表示等级的整数不能小于 1,在写入时可以进行验证:
Future<void> handleIntData() async {
final prefs = await SharedPreferences.getInstance();
int userLevel = 5;
// 验证并写入
if (userLevel >= 1) {
await prefs.setInt('user_level', userLevel);
}
// 读取整数并设置默认值
final int level = prefs.getInt('user_level') ?? 1;
}
双精度浮点数
双精度浮点数用于存储小数。SharedPreferences
使用 setDouble
和 getDouble
方法来处理双精度浮点数。在存储一些精度要求较高的数据,如经纬度时会很有用:
Future<void> handleDoubleData() async {
final prefs = await SharedPreferences.getInstance();
double latitude = 34.0522;
double longitude = -118.2437;
await prefs.setDouble('latitude', latitude);
await prefs.setDouble('longitude', longitude);
final double? storedLatitude = prefs.getDouble('latitude');
final double? storedLongitude = prefs.getDouble('longitude');
}
字符串
字符串是最常用的数据类型之一。SharedPreferences
提供了 setString
和 getString
方法。字符串可以用于存储文本信息,如用户的昵称、应用的版本号等:
Future<void> handleStringData() async {
final prefs = await SharedPreferences.getInstance();
String username = 'JohnDoe';
await prefs.setString('username', username);
final String? storedUsername = prefs.getString('username');
}
字符串列表
字符串列表适用于存储一组相关的文本数据,如用户收藏的文章标题列表。SharedPreferences
使用 setStringList
和 getStringList
方法来处理字符串列表:
Future<void> handleStringListData() async {
final prefs = await SharedPreferences.getInstance();
List<String> favoriteArticles = ['Article 1', 'Article 2', 'Article 3'];
await prefs.setStringList('favorite_articles', favoriteArticles);
final List<String>? storedArticles = prefs.getStringList('favorite_articles');
}
应用场景示例
用户设置存储
假设应用有一个夜间模式的设置,用户可以切换夜间模式的开关。可以使用 SharedPreferences
来存储这个设置:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ThemeSettings {
static const String key = 'is_night_mode';
static Future<bool> getIsNightMode() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(key) ?? false;
}
static Future<void> setIsNightMode(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(key, value);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: ThemeSettings.getIsNightMode(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container();
}
final isNightMode = snapshot.data!;
return MaterialApp(
theme: isNightMode? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
appBar: AppBar(
title: Text('Theme Setting Example'),
),
body: Center(
child: Switch(
value: isNightMode,
onChanged: (value) async {
await ThemeSettings.setIsNightMode(value);
setState(() {});
},
),
),
),
);
},
);
}
}
在上述代码中,ThemeSettings
类封装了获取和设置夜间模式的逻辑。MyApp
组件通过 FutureBuilder
根据存储的夜间模式设置来构建应用的主题。
登录状态管理
可以使用 SharedPreferences
来存储用户的登录状态。当用户登录成功后,将登录状态设置为 true
,在应用每次启动时检查登录状态:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginStatus {
static const String key = 'is_logged_in';
static Future<bool> getIsLoggedIn() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(key) ?? false;
}
static Future<void> setIsLoggedIn(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(key, value);
}
}
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login Page'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
// 模拟登录成功
await LoginStatus.setIsLoggedIn(true);
Navigator.pushReplacementNamed(context, '/home');
},
child: Text('Login'),
),
),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
// 模拟注销
await LoginStatus.setIsLoggedIn(false);
Navigator.pushReplacementNamed(context, '/login');
},
child: Text('Logout'),
),
),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: LoginStatus.getIsLoggedIn(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container();
}
final isLoggedIn = snapshot.data!;
return MaterialApp(
initialRoute: isLoggedIn? '/home' : '/login',
routes: {
'/login': (context) => LoginPage(),
'/home': (context) => HomePage(),
},
);
},
);
}
}
在这个示例中,LoginStatus
类负责管理登录状态的存储和获取。MyApp
组件根据登录状态决定应用的初始路由。
注意事项
数据大小限制
虽然 SharedPreferences
适用于存储少量数据,但不同平台对其存储大小有一定限制。在 Android 上,一般建议存储的数据量不超过 1MB。如果存储的数据量过大,可能会导致性能问题或存储失败。因此,在使用 SharedPreferences
时,要确保存储的数据量在合理范围内。
异步操作
SharedPreferences
的读取和写入操作都是异步的。这意味着在获取 SharedPreferences
实例以及进行读写操作时,都需要使用 await
关键字或处理 Future
。如果不处理异步操作,可能会导致数据读取或写入不及时,影响应用的逻辑。
数据一致性
由于 SharedPreferences
的写入操作是异步的,在多个地方同时写入数据时,可能会出现数据一致性问题。例如,一个地方写入了 A
值,另一个地方同时写入了 B
值,最终存储的值可能不确定。为了避免这种情况,可以在关键的写入操作处使用同步机制,或者在读取数据时进行验证和修复。
兼容性
虽然 SharedPreferences
基于 Android 和 iOS 原生的存储机制实现,但在不同版本的操作系统上可能会有一些细微差异。在开发过程中,要进行充分的测试,确保应用在各种目标平台和版本上都能正常工作。
性能优化
批量操作
尽量避免频繁的单次读写操作。可以将多个相关的数据读写操作合并为一次批量操作。例如,同时读取多个键的值或者同时设置多个键的值。这样可以减少异步操作的次数,提高性能。
Future<void> batchOperation() async {
final prefs = await SharedPreferences.getInstance();
final batch = prefs.batch();
batch.setBool('bool_key', true);
batch.setInt('int_key', 42);
batch.setString('string_key', 'Batch operation');
await batch.commit();
}
在上述代码中,使用 batch
方法创建一个 SharedPreferencesBatch
对象,然后通过该对象进行多个设置操作,最后使用 commit
方法提交这些操作。
缓存策略
对于一些不经常变化的数据,可以在内存中进行缓存。在应用启动时读取 SharedPreferences
中的数据并缓存到内存中,在需要使用这些数据时优先从内存缓存中获取。只有当缓存数据过期或者需要更新时,才重新从 SharedPreferences
中读取。这样可以减少对 SharedPreferences
的读取次数,提高应用的响应速度。
class DataCache {
static Map<String, dynamic> _cache = {};
static Future<dynamic> getValue(String key) async {
if (_cache.containsKey(key)) {
return _cache[key];
}
final prefs = await SharedPreferences.getInstance();
final value = prefs.get(key);
if (value != null) {
_cache[key] = value;
}
return value;
}
static Future<void> setValue(String key, dynamic value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.set(key, value);
_cache[key] = value;
}
}
在上述代码中,DataCache
类实现了一个简单的缓存机制。getValue
方法优先从缓存中获取数据,如果缓存中没有,则从 SharedPreferences
中读取并更新缓存。setValue
方法在更新 SharedPreferences
的同时也更新缓存。
与其他存储方式的比较
与 SQLite 的比较
- 数据结构:
SharedPreferences
以简单的键值对形式存储数据,适用于存储少量、结构简单的数据。而 SQLite 是一种关系型数据库,支持复杂的数据结构和关系,适用于存储大量结构化数据,如应用的用户信息表、订单列表等。 - 性能:对于简单的读写操作,
SharedPreferences
的性能较好,因为它的操作相对简单。但在处理大量数据的复杂查询和事务时,SQLite 更具优势,它可以通过索引等机制优化查询性能,并且支持事务处理保证数据一致性。 - 使用难度:
SharedPreferences
使用非常简单,只需要进行基本的键值对读写操作。而 SQLite 的使用相对复杂,需要了解 SQL 语句以及数据库的设计和管理。
与文件存储的比较
- 数据格式:
SharedPreferences
只能存储特定的数据类型(布尔值、整数、双精度浮点数、字符串和字符串列表),数据格式相对固定。文件存储则更加灵活,可以存储任意格式的数据,如 JSON、XML 等序列化后的数据。 - 存储位置和管理:
SharedPreferences
由系统管理存储位置,不同平台有固定的存储路径,应用开发者无需过多关心存储位置。文件存储则需要开发者自己管理文件的路径、命名和权限等,灵活性高但也增加了开发的复杂度。 - 数据访问:
SharedPreferences
通过键值对直接访问数据,速度较快。文件存储则需要读取整个文件并解析数据,对于大数据量的文件读取和解析可能会消耗更多资源和时间。
高级应用
数据加密存储
在一些对数据安全性要求较高的场景下,可以对 SharedPreferences
中存储的数据进行加密。可以使用 Flutter 的加密库,如 encrypt
库。首先在 pubspec.yaml
文件中引入依赖:
dependencies:
encrypt: ^5.0.0
然后对数据进行加密和解密操作:
import 'package:encrypt/encrypt.dart';
import 'package:shared_preferences/shared_preferences.dart';
class EncryptedPreferences {
static final Encrypter encrypter = Encrypter(AES(Key.fromLength(32)));
static Future<void> setEncryptedString(String key, String value) async {
final encrypted = encrypter.encrypt(value);
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, encrypted.base64);
}
static Future<String?> getEncryptedString(String key) async {
final prefs = await SharedPreferences.getInstance();
final encryptedBase64 = prefs.getString(key);
if (encryptedBase64 == null) {
return null;
}
final encrypted = Encrypted.fromBase64(encryptedBase64);
return encrypter.decrypt(encrypted);
}
}
在上述代码中,EncryptedPreferences
类封装了对字符串数据的加密存储和读取操作。使用 AES
加密算法对字符串进行加密和解密,在存储时将加密后的结果以 Base64 编码的形式存储在 SharedPreferences
中。
数据版本控制
随着应用的更新,存储在 SharedPreferences
中的数据结构可能会发生变化。为了确保数据的兼容性,可以引入数据版本控制。在存储数据时,同时存储一个版本号。当应用启动时,检查版本号并根据版本号进行数据迁移:
import 'package:shared_preferences/shared_preferences.dart';
class DataVersion {
static const String key = 'data_version';
static const int currentVersion = 2;
static Future<int> getVersion() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(key) ?? 0;
}
static Future<void> setVersion(int version) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(key, version);
}
static Future<void> migrateData() async {
final version = await getVersion();
if (version == 0) {
// 版本 0 到版本 1 的迁移逻辑
final prefs = await SharedPreferences.getInstance();
final oldValue = prefs.getString('old_key');
if (oldValue != null) {
await prefs.setString('new_key', oldValue);
await prefs.remove('old_key');
}
await setVersion(1);
}
if (version == 1) {
// 版本 1 到版本 2 的迁移逻辑
final prefs = await SharedPreferences.getInstance();
final oldIntValue = prefs.getInt('old_int_key');
if (oldIntValue != null) {
final newIntValue = oldIntValue * 2;
await prefs.setInt('new_int_key', newIntValue);
await prefs.remove('old_int_key');
}
await setVersion(2);
}
}
}
在上述代码中,DataVersion
类管理数据的版本号。migrateData
方法根据当前数据版本号执行相应的迁移逻辑,确保数据在应用更新后仍能正常使用。
通过以上对 SharedPreferences
在 Flutter 中的详细介绍,包括基础使用、应用场景、注意事项、性能优化以及与其他存储方式的比较和高级应用等方面,开发者可以全面掌握 SharedPreferences
在 Flutter 本地存储中的应用,根据应用的需求合理选择和使用这种存储方式。