Fragment切换场景下的Executor生命周期管理问题
解决方案
核心问题在于测试状态和Executor实例绑定在Fragment上,而Fragment会随页面切换销毁重建,导致状态丢失和重复创建实例。正确的做法是将测试逻辑和状态托管到ViewModel中,其生命周期与所属Activity一致,不受Fragment重建影响。
步骤1:重构HomeViewModel,托管测试逻辑和状态
修改HomeViewModel,将测试状态、Executor、任务实例移至其中,并提供启动/停止测试的方法:
import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import java.util.concurrent.Executors import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit class HomeViewModel(application: Application) : AndroidViewModel(application) { // 标记测试是否正在运行的LiveData,用于Fragment观察更新UI private val _isTestRunning = MutableLiveData<Boolean>(false) val isTestRunning: LiveData<Boolean> = _isTestRunning private val scheduledExecutor = Executors.newSingleThreadScheduledExecutor() private var testFuture: ScheduledFuture<*>? = null fun toggleTest() { if (_isTestRunning.value == true) { // 停止测试 testFuture?.cancel(true) _isTestRunning.value = false } else { // 启动测试 val st = ScheduledTests() // 你的测试任务类 testFuture = scheduledExecutor.scheduleAtFixedRate(st, 0, 5, TimeUnit.SECONDS) _isTestRunning.value = true } } // ViewModel销毁时关闭Executor,避免内存泄漏 override fun onCleared() { super.onCleared() testFuture?.cancel(true) scheduledExecutor.shutdown() } }
步骤2:修改HomeFragment,依赖ViewModel控制逻辑
移除Fragment中与测试相关的局部变量,改为通过ViewModel获取状态和调用方法:
package com.giorgio.roamingqosapp.ui.home import ... class HomeFragment : Fragment() { private var _binding: FragmentHomeBinding? = null private val binding get() = _binding!! private lateinit var homeViewModel: HomeViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { // 使用Activity级别的ViewModelProvider,确保ViewModel与Activity生命周期绑定 homeViewModel = ViewModelProvider(requireActivity())[HomeViewModel::class.java] _binding = FragmentHomeBinding.inflate(inflater, container, false) val root: View = binding.root // 初始化运营商、漫游状态显示(保留原有逻辑) val tm = requireActivity().getSystemService(Activity.TELEPHONY_SERVICE) as TelephonyManager val SIMOperatorName = tm.simOperatorName val isRoaming = tm.isNetworkRoaming SIMOperatorName?.let { binding.registeredOperator.text = getString(R.string.registered_operator, it) } binding.roaming.text = getString( R.string.roaming, if (isRoaming) "Roaming" else "Home" ) binding.registeredImageView.setImageDrawable( AppCompatResources.getDrawable(requireContext(), R.drawable.led_verde) ) // 初始化图标点击事件 binding.testsOn.setOnClickListener { homeViewModel.toggleTest() } binding.testsOff.setOnClickListener { homeViewModel.toggleTest() } // 观察测试状态,自动更新UI homeViewModel.isTestRunning.observe(viewLifecycleOwner) { isRunning -> if (isRunning) { binding.testsOn.visibility = View.GONE binding.testsOff.visibility = View.VISIBLE } else { binding.testsOn.visibility = View.VISIBLE binding.testsOff.visibility = View.GONE } } return root } override fun onDestroyView() { super.onDestroyView() _binding = null } }
关键说明
- ViewModel的作用:ViewModel在Activity存活期间始终存在,Fragment切换时不会被销毁,因此测试状态和Executor实例得以保留,避免重复创建。
- LiveData状态观察:Fragment通过观察ViewModel中的
isTestRunningLiveData,在状态变化时自动更新图标显示,确保返回HomeFragment时UI与实际测试状态一致。 - Executor生命周期管理:在ViewModel的
onCleared方法中关闭Executor并取消任务,避免APP退出后出现内存泄漏。 - 避免重复实例:所有测试启动/停止操作都通过ViewModel统一处理,确保同一时间只有一个测试任务在运行。
内容的提问来源于stack exchange,提问作者Giorgio Torassa
相关产品推荐
相关产品推荐

