You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter单例模式下shared_preferences偶发未初始化问题排查

问题原因分析
  1. 初始化时序问题:main函数中调用initializeSharedPreferences()是异步操作,但未等待其完成就直接执行runApp(MyApp())。这会导致App启动时UI代码可能在_prefs初始化完成前就调用读取方法,多数情况下初始化速度快不会触发问题,但release模式下编译优化或设备性能波动时,偶尔会出现时序颠倒,引发空指针崩溃。
  2. 断言失效:代码中用assert(_prefs != null)做检查,但assert在release模式会被编译移除,无法起到实际的空保护作用,一旦_prefs为null,调用!强制解包会直接导致崩溃灰屏。
  3. 后台恢复时的实例有效性:部分移动平台在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 02:52:17