Flutter单例模式下shared_preferences偶发未初始化问题排查
问题原因分析
- 初始化时序问题:main函数中调用
initializeSharedPreferences()是异步操作,但未等待其完成就直接执行runApp(MyApp())。这会导致App启动时UI代码可能在_prefs初始化完成前就调用读取方法,多数情况下初始化速度快不会触发问题,但release模式下编译优化或设备性能波动时,偶尔会出现时序颠倒,引发空指针崩溃。 - 断言失效:代码中用
assert(_prefs != null)做检查,但assert在release模式会被编译移除,无法起到实际的空保护作用,一旦_prefs为null,调用!强制解包会直接导致崩溃灰屏。 - 后台恢复时的实例有效性:部分移动平台在App进入后台后,可能会回收SharedPreferences的底层实例,导致单例中的
_prefs变为无效的空引用,切回前台时读取数据就会触发异常。
解决思路与代码修改
1. 修正main函数的初始化流程
确保App在SharedPreferences完全初始化后再启动,将main改为异步函数并等待初始化完成:
void main() async { WidgetsFlutterBinding.ensureInitialized(); await initializeSharedPreferences(); runApp(MyApp()); } void initializeSharedPreferences() async { await SharedUserData.instance.init(); SharedUserData.instance.initializeHomeScreenDailies(); }
注意:WidgetsFlutterBinding.ensureInitialized()只需调用一次,放在main最开头即可。
2. 替换断言为release模式有效的空检查
把所有assert替换为实际的空判断,避免强制解包崩溃,可根据业务需求选择抛出异常或返回默认值:
// 读取方法示例 int? readCurrentDataSeed() { if (_prefs == null) { throw StateError('SharedPreferences未初始化,请先调用init()'); // 或者返回默认值:return -1; } return _prefs!.getInt(_APP_CURRENT_DATA_SEED); } // 写入方法示例 Future<void> writeCurrentDataSeed(int currentDataSeed) async { if (_prefs == null) { throw StateError('SharedPreferences未初始化,请先调用init()'); } await _prefs!.setInt(_APP_CURRENT_DATA_SEED, currentDataSeed); }
3. 监听App生命周期,后台恢复时校验实例有效性
通过WidgetsBindingObserver监听App状态,当从后台切回前台时,检查_prefs是否有效,必要时重新初始化:
class SharedUserData with WidgetsBindingObserver { // ... 原有代码 ... SharedUserData._privateConstructor() { WidgetsBinding.instance.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); // 当App从后台恢复到前台时 if (state == AppLifecycleState.resumed) { _checkAndReinitializePrefs(); } } Future<void> _checkAndReinitializePrefs() async { // 检查_prefs是否为空或无效 if (_prefs == null || !_prefs!.containsKey(_APP_CURRENT_DATA_SEED)) { _prefs = await SharedPreferences.getInstance(); // 可选:重新初始化每日数据 initializeHomeScreenDailies(); } } // ... 原有代码 ... }
4. 增加初始化状态判断
给SharedUserData添加isInitialized getter,方便外部判断是否完成初始化,避免提前调用方法:
bool get isInitialized => _prefs != null;
额外建议
- 避免在UI线程中直接执行大量SharedPreferences读写操作,可封装到单独的异步任务中,防止阻塞UI。
- 若业务允许,可给读取方法设置合理的默认值,减少异常对UI的影响。
内容的提问来源于stack exchange,提问作者user21766581
相关产品推荐
相关产品推荐

