Retrofit 2与Realm冲突引发GC异常问题求助
问题分析与解决方案
首先,你猜的没错——出现频繁GC日志确实和直接传递RealmObject数组给Retrofit的@Body有关。
为什么会触发GC?
RealmObject不是普通的Java对象,它背后绑定着Realm数据库的内部代理、引用追踪和状态管理逻辑。当Gson(Retrofit默认用它序列化请求体)尝试序列化RealmObject时,会递归遍历它所有的属性,包括那些Realm内部隐藏的代理字段。这会瞬间创建大量临时对象,直接触发垃圾回收,也就是你看到的Background partial concurrent mark sweep GC freed...日志。
另外,你的代码里还有几个加剧问题的细节:
SessionsRealm类本身继承了RealmObject——这明显是设计错误,它是数据库操作工具类,不应该作为数据库实体存在,这会导致额外的Realm内部开销。getAll()方法里重复创建Realm实例,且没有关闭:多次调用Realm.getDefaultInstance()却不调用close(),会造成Realm资源泄漏,进一步加重内存压力。getAll()里的数组长度计算错误:你用SessionsRealm.class的查询结果长度来初始化SessionVO数组,这两个查询的结果数量大概率不匹配,会导致数组里出现空元素,额外增加序列化负担。
具体修复步骤
1. 创建纯POJO替代RealmObject
先写一个和SessionVO结构完全一致,但不继承RealmObject的普通Java类,作为请求体的载体:
public class SessionPOJO { private long sessionStartTime; private long sessionEndTime; public SessionPOJO() {} // 从Realm对象复制属性的构造方法 public SessionPOJO(SessionVO sessionVO) { this.sessionStartTime = sessionVO.getSessionStartTime(); this.sessionEndTime = sessionVO.getSessionEndTime(); } // getter和setter public long getSessionStartTime() { return sessionStartTime; } public void setSessionStartTime(long sessionStartTime) { this.sessionStartTime = sessionStartTime; } public long getSessionEndTime() { return sessionEndTime; } public void setSessionEndTime(long sessionEndTime) { this.sessionEndTime = sessionEndTime; } }
2. 修改Retrofit API接口
把请求体的类型换成纯POJO数组:
@POST("sessions/add") Call<ResponseBody> sessionsAdd(@Body SessionPOJO[] options, @Header("Authorization") String token);
3. 修复Realm操作类的逻辑
首先,把SessionsRealm改成普通工具类,不要继承RealmObject;然后优化getAll()方法,正确获取脱离Realm的对象,并转成POJO数组:
public class SessionsRealm { private Realm realm; public SessionsRealm(){ realm = Realm.getDefaultInstance(); } // 插入逻辑调整:直接操作SessionVO实体 private void insert(final SessionVO sessionVO){ realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.insert(sessionVO); } }); } public SessionPOJO[] getAll(){ Realm realm = Realm.getDefaultInstance(); try { RealmResults<SessionVO> sessionVOs = realm.where(SessionVO.class).findAll(); // 使用copyFromRealm获取脱离Realm绑定的对象 List<SessionVO> detachedSessionVOs = realm.copyFromRealm(sessionVOs); // 转换为POJO数组 SessionPOJO[] pojoArray = new SessionPOJO[detachedSessionVOs.size()]; for (int i = 0; i < detachedSessionVOs.size(); i++) { pojoArray[i] = new SessionPOJO(detachedSessionVOs.get(i)); } return pojoArray; } finally { realm.close(); // 必须关闭Realm实例,避免资源泄漏 } } public boolean isEmpty(){ Realm realm = Realm.getDefaultInstance(); try { return realm.isEmpty(SessionVO.class); // 指定检查SessionVO表 } finally { realm.close(); } } public void deleteAll(){ Realm realm = Realm.getDefaultInstance(); try { if(!realm.isEmpty(SessionVO.class)){ realm.beginTransaction(); realm.delete(SessionVO.class); // 只删除SessionVO表 realm.commitTransaction(); } } finally { realm.close(); } } public void save(Object vo){ insert((SessionVO) vo); } public void closeRealm(){ if(realm != null && !realm.isClosed()){ realm.close(); } } }
4. 修改请求发送代码
现在传递POJO数组给Retrofit:
SessionPOJO[] sessionPOJOArray = sessionsRealm.getAll(); call = serverApi.sessionsAdd(sessionPOJOArray, getSharedPreferenceManager().getAccessToken());
额外优化建议
- 避免在UI线程执行Realm查询:如果
getAll()是在主线程调用的,建议改成异步查询,避免阻塞UI。 - 复用Realm实例:可以考虑在Activity/Fragment的生命周期内复用Realm实例,减少创建销毁的开销。
- 不推荐直接序列化Realm对象:如果一定要这么做,需要给Gson添加Realm专属的类型适配器,但这种方式仍不如纯POJO可靠。
这样修改后,Gson只会序列化纯POJO的属性,不会触发Realm内部的复杂逻辑,频繁GC的问题应该就能解决了。
内容的提问来源于stack exchange,提问作者abrutsze
相关产品推荐
相关产品推荐

