安卓Marshmallow设备中SharedPreferences存储的字符串重启APP后被修改?
哇,这个问题确实挺磨人的,尤其是只在单台Marshmallow设备上出现,排查24小时肯定费了不少精力!我来帮你梳理下思路和解决方案:
问题本质
你遇到的核心问题是Android 6.0(Marshmallow)的SharedPreferences在持久化字符串时,自动将CR LF(\r\n)转换成了LF(\n),重启APP读取后,字符串的字节结构发生了变化,导致和原服务器返回的字符串equals()不匹配。至于在线对比工具显示相同,是因为多数工具会自动归一化换行符格式,忽略CR的存在,所以肉眼看起来一致,但底层字节是不同的。
可行的解决方案
这里给你几个具体的解决办法,按推荐程度排序:
Base64编码存储(最稳妥)
把要存储的字符串先转成Base64编码,这样所有控制字符都会被转换成普通字符,完全避免系统对字符串的修改。示例代码:// 存储时 String originalStr = "你的服务器返回字符串"; String encodedStr = Base64.encodeToString(originalStr.getBytes(), Base64.DEFAULT); sharedPreferences.edit().putString("key", encodedStr).apply(); // 读取时 String encodedStr = sharedPreferences.getString("key", ""); String restoredStr = new String(Base64.decode(encodedStr, Base64.DEFAULT));这种方法能彻底解决任何字符编码或控制字符的存储问题,适合对字符串完整性要求高的场景。
统一换行符格式
在存储前手动将所有CR LF替换成LF,或者反过来,确保存储和读取时的换行符格式一致。比如:// 存储前统一换行符为LF String normalizedStr = originalStr.replace("\r\n", "\n"); sharedPreferences.edit().putString("key", normalizedStr).apply(); // 如果需要和服务器返回的字符串对比,读取后再转回去(或者服务器端也统一格式) String restoredStr = sharedPreferences.getString("key", "").replace("\n", "\r\n");这个方法简单直接,但需要确保服务器返回的字符串格式是固定的,或者你能控制对比时的格式统一。
改用内部文件存储
如果SharedPreferences的这个行为实在无法规避,可以考虑用Android的内部文件存储来保存字符串,直接写入字节流,完全由自己控制存储内容:// 存储到内部文件 try (FileOutputStream fos = context.openFileOutput("my_string.txt", Context.MODE_PRIVATE)) { fos.write(originalStr.getBytes()); } catch (IOException e) { e.printStackTrace(); } // 读取文件 try (FileInputStream fis = context.openFileInput("my_string.txt")) { byte[] buffer = new byte[fis.available()]; fis.read(buffer); String restoredStr = new String(buffer); } catch (IOException e) { e.printStackTrace(); }这种方式完全绕过SharedPreferences的处理逻辑,能保证字符串的原始字节不被修改。
补充说明
Marshmallow的SharedPreferences实现和后续Android版本有一些差异,可能是当时的底层存储逻辑对字符串做了自动换行符归一化处理,而后续版本修复了这个行为,所以只在这台设备上出现问题。用Base64编码是最通用的解决方案,几乎不会有兼容性问题。
内容的提问来源于stack exchange,提问作者Hanzyusuf

