Android中单例无引用时会销毁吗?Repository模式遇数据丢失问题
问题解答:切换标签页后Repository中LiveData数据丢失
这种数据变为0的现象不正常,因为伴生对象属于类级别的单例,其持有的news MutableLiveData理论上会在应用进程存活期间保留数据。出现这个问题大概率是你的实现方式有漏洞,下面分析原因并给出修复方案:
可能的原因
- MutableLiveData被意外重置:你的
news变量用了var修饰,意味着它可以被重新赋值。如果在下载逻辑中每次请求都创建新的MutableLiveData并赋值给news,旧的LiveData会被丢弃,新的LiveData初始为空列表,就会出现返回Fragment后数据为0的情况。 - 错误的单例实现逻辑:虽然伴生对象是单例,但如果你的Repository在某些场景下被多次初始化(比如误写了构造函数调用),可能导致数据状态混乱。
- Fragment/ViewModel生命周期未正确绑定:如果你的ViewModel是直接
new出来的,而非通过ViewModelProvider获取,当Fragment因切换标签页被销毁重建时,ViewModel会被重新创建,若此时未正确复用Repository的单例数据,也可能出现数据丢失。
修复方案
1. 修正Repository的实现
把news改为val避免被意外替换,同时用更可靠的单例模式实现,更新数据时修改LiveData内部的列表而非替换LiveData对象:
class NewsRepository { // 用val确保LiveData对象不会被替换,初始化为空列表 val news: MutableLiveData<MutableList<Item>> = MutableLiveData(mutableListOf()) private val mainRepository = MainRepository(ApiHelper(RetrofitBuilder.apiService)) // 双重校验锁实现单例,避免多线程问题 companion object { @Volatile private var instance: NewsRepository? = null fun getInstance(): NewsRepository { return instance ?: synchronized(this) { instance ?: NewsRepository().also { instance = it } } } } // 示例:正确的新闻数据获取逻辑 fun fetchNews() { mainRepository.getNews().enqueue(object : Callback<List<Item>> { override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) { response.body()?.let { fetchedItems -> // 获取当前LiveData中的列表,更新后重新设置 val currentList = news.value ?: mutableListOf() currentList.clear() currentList.addAll(fetchedItems) news.postValue(currentList) } } override fun onFailure(call: Call<List<Item>>, t: Throwable) { // 处理请求失败逻辑 } }) } }
2. 正确使用ViewModel
ViewModel必须通过ViewModelProvider获取,确保在Fragment销毁重建时不会被重新创建,从而保持对Repository单例的引用:
class NewsViewModel : ViewModel() { private val newsRepository = NewsRepository.getInstance() // 对外暴露不可变的LiveData,避免UI层直接修改数据 val newsLiveData: LiveData<MutableList<Item>> = newsRepository.news init { // 仅当数据为空时触发下载,避免重复请求 if (newsLiveData.value.isNullOrEmpty()) { newsRepository.fetchNews() } } }
3. Fragment中正确绑定ViewModel与观察者
在Fragment中通过viewLifecycleOwner订阅LiveData,确保生命周期同步:
class NewsFragment : Fragment() { private lateinit var viewModel: NewsViewModel override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { // 通过ViewModelProvider获取ViewModel,关联当前Fragment的生命周期 viewModel = ViewModelProvider(this)[NewsViewModel::class.java] // 订阅LiveData,用viewLifecycleOwner确保Fragment销毁时自动取消订阅 viewModel.newsLiveData.observe(viewLifecycleOwner) { items -> // 在这里更新UI,比如给RecyclerView设置数据源 } return inflater.inflate(R.layout.fragment_news, container, false) } }
总结
- 永远用
val声明LiveData对象,防止被意外替换 - 避免依赖伴生对象的静态单例,改用更可靠的单例实现(或依赖注入框架如Hilt)
- 必须通过
ViewModelProvider获取ViewModel,保证生命周期一致性 - 更新LiveData时修改内部列表而非替换LiveData,确保观察者能收到正确的更新
内容的提问来源于stack exchange,提问作者Oscar Berggren
相关产品推荐
相关产品推荐

