SharedPreferences读取自定义ArrayList二次恢复触发RuntimeException问题
问题分析与解决方案
我碰到过好几个类似的Gson+SharedPreferences结合使用的坑,结合你描述的「第一次stop/resume正常、第二次触发RuntimeException」的场景,大概率是以下几个细节没处理到位,咱们一个个捋:
1. 先排查Gson反序列化的泛型类型问题(高频坑)
很多人在反序列化ArrayList时会偷懒直接写gson.fromJson(json, ArrayList.class),这会让Gson无法正确推断泛型类型——第一次可能因为数据结构简单侥幸成功,但第二次数据量增加或结构变化时就会抛出类型转换异常。
正确做法是用TypeToken明确指定泛型类型:
// 错误写法:无法正确解析泛型 ArrayList<TrackPoint> savedPoints = gson.fromJson(json, ArrayList.class); // 正确写法:通过TypeToken告知Gson具体的泛型类型 Type trackListType = new TypeToken<ArrayList<TrackPoint>>() {}.getType(); ArrayList<TrackPoint> savedPoints = gson.fromJson(json, trackListType);
2. 检查SharedPreferences的写入提交逻辑
如果你的stop()方法用了apply()异步写入,第二次操作时可能数据还没完全写入磁盘,导致resume()读取到不完整或旧的JSON串,进而触发异常。
优化方案:
- 在需要确保数据写入完成的场景(比如运动记录保存),改用
commit()同步提交(它会返回布尔值告知写入是否成功); - 序列化前先拷贝一份当前运动数据,避免多线程环境下原列表被修改导致JSON生成异常:
public void stop() { try { // 先拷贝数据,避免序列化过程中原列表被修改 ArrayList<TrackPoint> copyPoints = new ArrayList<>(mTrackPoints); Gson gson = new Gson(); String json = gson.toJson(copyPoints); SharedPreferences.Editor editor = mSharedPrefs.edit(); editor.putString("track_data", json); // 用commit确保写入完成,同时判断是否成功 boolean saveSuccess = editor.commit(); if (!saveSuccess) { Log.e("TrackerService", "Failed to save track data to SharedPreferences"); } } catch (Exception e) { Log.e("TrackerService", "Error during save", e); } }
3. 避免ArrayList的数据污染
如果resume()时没有先清空现有列表,直接把读取到的数据addAll进去,会导致第二次恢复时数据叠加,甚至出现循环引用或无效数据,序列化时触发异常。
正确的恢复逻辑:
public void resume() { try { // 先清空当前列表,避免数据叠加 mTrackPoints.clear(); String json = mSharedPrefs.getString("track_data", null); if (json != null && !json.isEmpty()) { Type trackListType = new TypeToken<ArrayList<TrackPoint>>() {}.getType(); ArrayList<TrackPoint> savedPoints = gson.fromJson(json, trackListType); mTrackPoints.addAll(savedPoints); } } catch (Exception e) { Log.e("TrackerService", "Error during resume", e); } }
另外,在stop()完成后,建议也清空mTrackPoints,避免下次启动时携带旧数据。
4. 检查TrackPoint实体类的序列化兼容性
如果你的TrackPoint类缺少无参构造方法,或者包含无法被Gson序列化的字段(比如自定义对象、循环引用),也会导致第二次序列化/反序列化失败。
确保实体类满足以下要求:
- 必须有无参构造方法(Gson需要它来实例化对象);
- 所有需要序列化的字段要么是public,要么提供getter/setter;
- 避免循环引用(比如TrackPoint中引用了包含它的TrackerService实例)。
示例规范的实体类:
public class TrackPoint { private double latitude; private double longitude; private long timestamp; // 必须保留无参构造 public TrackPoint() {} public TrackPoint(double latitude, double longitude, long timestamp) { this.latitude = latitude; this.longitude = longitude; this.timestamp = timestamp; } // 可选:提供getter/setter,提升代码规范性 public double getLatitude() { return latitude; } public void setLatitude(double latitude) { this.latitude = latitude; } // ... 其他字段的getter/setter }
5. 加日志排查精准定位问题
如果以上方案都没解决,建议在stop()和resume()中添加详细日志,打印JSON串内容、异常栈信息,这样能快速定位是写入时生成了无效JSON,还是读取时反序列化失败:
// stop()中添加日志 Log.d("TrackerService", "Saved JSON length: " + json.length() + ", content snippet: " + json.substring(0, Math.min(100, json.length()))); // resume()中添加日志 Log.d("TrackerService", "Loaded JSON: " + (json == null ? "null" : json.substring(0, Math.min(100, json.length()))));
内容的提问来源于stack exchange,提问作者user86776
相关产品推荐
相关产品推荐

