Flutter shared_preferences在Android谷歌应用商店生产环境失效
问题背景
使用shared_preferences: ^2.0.15实现Flutter应用自动登录功能,Android调试模式下一切正常,但发布至Google Play商店后自动登录失效。尝试过flutter build appbundle --release --no-shrink构建版本,问题依旧。
实现方式:通过main函数初始化单例类管理SharedPreferences实例,自动登录逻辑在StartupTask中执行。相关代码如下:
主函数代码
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await PrefsSingleton().init(); // 后续逻辑... }
SharedPreferences单例类
import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; class PrefsSingleton { static final PrefsSingleton _instance = PrefsSingleton._internal(); late final SharedPreferences _prefs; factory PrefsSingleton() { return _instance; } PrefsSingleton._internal(); Future<SharedPreferences> init() async => _prefs = await SharedPreferences.getInstance(); Future<bool> setBool(String key, bool value) => _prefs.setBool(key, value); bool? getBool(String key) => _prefs.getBool(key); Future<bool> setString(String key, String value) => _prefs.setString(key, value); String? getString(String key) => _prefs.getString(key); Future<bool> setJsonEncodedString(String key, dynamic value) => _prefs.setString(key, json.encode(value)); dynamic getJsonDecodedString(String key) { var jsonString = _prefs.getString(key); if (jsonString != null) { return jsonDecode(jsonString); } return null; } }
自动登录逻辑
class AutoLogin implements StartupTask { @override Future<dynamic> runTask() async { var alreadyLoggedIn = PrefsSingleton().getBool("user.isLoggedIn") ?? false; if (alreadyLoggedIn) { var domain = PrefsSingleton().getString("user.domain"); var pincode = PrefsSingleton().getString("user.pincode"); await PrefsSingleton().setBool("user.isLoggedIn", false); if (domain != null && pincode != null) { var loggedIn = await ApiUserRepo().loginUser(domain, pincode); if (loggedIn) { await PluginSyncRepo().getSyncData(); } } } } }
环境信息
- Flutter版本:3.0.5
- Dart版本:2.17.6
- Build Gradle版本:7.0.0
依赖包列表
cupertino_icons: ^1.0.2, flutter_riverpod: ^2.0.0-dev.9, device_info_plus: ^4.0.0, percent_indicator: ^4.2.2, pin_code_fields: ^7.4.0, flutter_spinkit: ^5.1.0, url_launcher: ^6.1.0, rest_client: ^2.1.4, crypto: ^3.0.2, catcher: ^0.7.0, shared_preferences: ^2.0.15, uuid: ^3.0.6, flutter_facebook_auth: ^4.3.4, intl: ^0.17.0, image_picker: ^0.8.5+3, connectivity_plus: ^2.3.2, json_annotation: ^4.5.0, reorderables: ^0.5.0, cached_network_image: ^3.2.1, string_validator: ^0.3.0, image: ^3.2.0, heic_to_jpg: ^0.2.0, path_provider: ^2.0.11, package_info_plus: ^1.4.2, camera: ^0.10.0+1, dropdown_button2: ^1.7.2
可能的原因及解决方案
1. R8/ProGuard混淆破坏SharedPreferences逻辑
即使使用--no-shrink,Google Play对App Bundle会默认启用R8混淆,可能导致SharedPreferences相关类被误删或篡改。需在android/app/proguard-rules.pro中添加保护规则:
-keep class io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin { *; } -keep class androidx.preference.PreferenceManager { *; } -keep class android.content.SharedPreferences { *; } -keep class android.content.SharedPreferences$Editor { *; }
同时确保android/app/build.gradle的release配置正确:
buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }
2. 初始化时机不匹配
虽然main函数中await了PrefsSingleton.init(),但如果AutoLogin.runTask()的执行没有等待初始化完成,可能导致读取时_prefs尚未赋值。可在单例类中添加初始化状态检查,并在自动登录前确认:
// 在PrefsSingleton中添加 bool _isInitialized = false; Future<SharedPreferences> init() async { _prefs = await SharedPreferences.getInstance(); _isInitialized = true; return _prefs; } bool get isInitialized => _isInitialized; // 在AutoLogin.runTask中先验证 @override Future<dynamic> runTask() async { if (!PrefsSingleton().isInitialized) { await PrefsSingleton().init(); } // 原有逻辑... }
3. Flutter Riverpod预览版潜在问题
flutter_riverpod 2.0.0-dev.9为预览版,可能存在异步初始化冲突。建议升级至稳定版(当前最新稳定版为2.3.0+),或确保SharedPreferences初始化完成后再初始化Riverpod的ProviderScope。
4. 验证SharedPreferences读写状态
在发布版中添加日志捕获(利用已集成的catcher),确认登录成功后数据是否正常写入:
// 登录成功后添加 bool setLoginStatus = await PrefsSingleton().setBool("user.isLoggedIn", true); bool setDomain = await PrefsSingleton().setString("user.domain", domain); bool setPincode = await PrefsSingleton().setString("user.pincode", pincode); // 用catcher记录日志 Catcher.reportCheckedError( null, StackTrace.current, message: "SP写入结果: isLoggedIn=$setLoginStatus, domain=$setDomain, pincode=$setPincode" );
同时在自动登录逻辑中添加读取日志,确认是否能获取到数据。
5. 测试发布版签名包
如果启用了Google Play App Signing,本地调试签名与发布签名不同,可尝试用发布版签名密钥打包APK安装到设备上测试,确认是否能正常读取SharedPreferences数据。
内容的提问来源于stack exchange,提问作者anton-definit

