采用Repository单例与MVP架构的注册流程中数据遭GC回收问题咨询
这种情况我之前做注册流程时也踩过坑!单例看起来是全局存在的,但在Android环境下,它并不是绝对不会被垃圾回收的——当应用退到后台,系统内存吃紧的时候,没有被强引用链牢牢抓住的单例(以及它内部的signupData)很容易被回收掉,导致之前填写的注册数据丢失。
下面给你几个实用的解决方案,你可以根据需求选:
1. 最可靠:用持久化存储保存数据
这是最稳妥的方案,不管进程是否被杀死,数据都能保留。你可以选择SharedPreferences(适合少量简单数据)、Room数据库(适合复杂结构)或者文件存储。以SharedPreferences为例,修改你的SignupRepository:
public class SignupRepository { private SignupData signupData; // 初始化时加载已保存的数据(需要传入Context) public SignupRepository(Context context) { signupData = loadSignupData(context); } // 保存当前注册数据到SharedPreferences public void saveSignupData(Context context) { SharedPreferences sp = context.getSharedPreferences("SignupStorage", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("param1", signupData.getParam1()); // 把其他需要跨屏保存的参数也存进去 editor.apply(); // 异步保存,不阻塞主线程 } // 从SharedPreferences加载数据 private SignupData loadSignupData(Context context) { SignupData data = new SignupData(); SharedPreferences sp = context.getSharedPreferences("SignupStorage", Context.MODE_PRIVATE); data.setParam1(sp.getString("param1", "")); // 加载其他参数,默认值根据业务设置 return data; } // 原有方法不变 public void setSignupParam1(String param1) { signupData.setParam1(param1); } public SignupData getSignupData() { return signupData; } }
然后在每个注册步骤完成后(比如用户填完参数1点击下一步),调用saveSignupData();在每个注册页面初始化时,调用getSignupData()就能拿到之前保存的数据,完全不用担心GC回收的问题。
2. 绑定Application生命周期,减少单例被回收的概率
如果你的需求只是在进程存活时保留数据(进程被杀死的话数据丢失也能接受),可以让单例持有Application的强引用——因为Application的生命周期和整个应用一致,只要进程没被杀死,它就不会被回收,单例也会跟着存活。修改单例的实现:
public class SignupRepository { private static SignupRepository instance; private final Context appContext; private SignupData signupData; // 私有构造方法,传入Application Context private SignupRepository(Context appContext) { this.appContext = appContext.getApplicationContext(); signupData = new SignupData(); } // 获取单例的方法,必须传入Context(建议传Application Context) public static synchronized SignupRepository getInstance(Context context) { if (instance == null) { instance = new SignupRepository(context); } return instance; } // 原有方法不变 public void setSignupParam1(String param1) { signupData.setParam1(param1); } public SignupData getSignupData() { return signupData; } }
注意调用getInstance()时,尽量传Application Context,避免传入Activity Context导致内存泄漏。这种方法能降低单例被GC回收的概率,但如果系统直接杀死了进程,数据还是会丢失,所以适合对数据持久性要求不高的场景。
3. 更符合Android架构:用ViewModel管理跨屏数据
如果你的项目用了AndroidX,ViewModel是更好的选择——它的生命周期跨配置变更(比如屏幕旋转),而且只要进程没被杀死,后台时ViewModel也不会被销毁,完美适配注册流程的跨屏数据传递。
首先创建一个SignupViewModel:
import androidx.lifecycle.ViewModel; public class SignupViewModel extends ViewModel { private SignupData signupData = new SignupData(); public void setSignupParam1(String param1) { signupData.setParam1(param1); } public SignupData getSignupData() { return signupData; } }
然后在每个注册页面的Activity/Fragment中获取ViewModel:
// 在Activity中 SignupViewModel viewModel = new ViewModelProvider(this).get(SignupViewModel.class); // 设置参数 viewModel.setSignupParam1("用户输入的内容"); // 获取参数 SignupData currentData = viewModel.getSignupData();
ViewModel会自动帮你管理数据的生命周期,不用自己操心单例的GC问题,而且比单例更灵活,也不会有内存泄漏的风险,非常推荐在Android项目中使用这种方式。
总结一下:如果要确保数据绝对不丢失,优先选持久化存储;如果只是进程存活时临时保存,绑定Application的单例或者ViewModel都可以,其中ViewModel更符合现代Android架构的最佳实践。
内容的提问来源于stack exchange,提问作者amitavk

