切换Fragment后返回主页点击hitButton导致应用崩溃求助
解决Fragment切换返回后点击按钮崩溃的问题
问题原因
你错误地将属于Home Fragment的UI控件(hitButton、scoreTextView、sergayView)及相关逻辑放在MainActivity中实现:
- 切换到其他Fragment时,Home Fragment的视图会被销毁,MainActivity中保存的旧控件引用会失效。
- 返回Home Fragment时,Fragment会重新创建新的视图实例,但MainActivity中的控件引用仍指向已销毁的旧对象,点击按钮时就会触发空指针异常(NullPointerException),导致应用崩溃。
解决方案
将所有Home Fragment相关的UI逻辑、状态变量移到Home Fragment类中,遵循Fragment的生命周期管理控件和状态:
1. 简化MainActivity代码
移除所有属于Home Fragment的逻辑,只保留导航相关代码:
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) val navView: BottomNavigationView = binding.navView val navController = findNavController(R.id.nav_host_fragment_activity_main) val appBarConfiguration = AppBarConfiguration( setOf( R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications ) ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) } }
2. 重构Home Fragment代码
在Fragment内部处理控件初始化、点击事件和状态保存:
class HomeFragment : Fragment() { private var _binding: FragmentHomeBinding? = null private val binding get() = _binding!! private var score = 0 private var mediaPlayer: MediaPlayer? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentHomeBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 恢复之前保存的分数 savedInstanceState?.let { score = it.getInt("score") binding.scoreTextView.text = "счёт: $score" } binding.hitButton.setOnClickListener { score++ binding.scoreTextView.text = "счёт: $score" // 播放 Punch 音效,每次播放前释放旧资源 mediaPlayer?.release() mediaPlayer = MediaPlayer.create(requireContext(), R.raw.punch) mediaPlayer?.start() binding.sergayView.setImageResource(R.drawable.malyshanim) if (score == 50) { binding.scoreTextView.text = "вы получили новый уровень!" } // 使用协程实现延迟切换图片,避免Handler内存泄漏 viewLifecycleOwner.lifecycleCoroutineScope.launch { delay(200) binding.sergayView.setImageResource(R.drawable.malysh) } } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // 保存当前分数 outState.putInt("score", score) } override fun onDestroyView() { super.onDestroyView() // 释放MediaPlayer资源,防止内存泄漏 mediaPlayer?.release() mediaPlayer = null // 清空Binding引用,避免内存泄漏 _binding = null } }
关键注意事项
- 使用View Binding替代
findViewById,确保获取到当前Fragment的有效控件引用,避免空指针。 - 在Fragment的
onDestroyView方法中释放MediaPlayer资源和Binding引用,防止内存泄漏。 - 使用
viewLifecycleOwner的协程替代Handler,确保Fragment视图销毁后不会执行延迟操作,避免异常。 - Fragment的状态(如分数
score)由自身的onSaveInstanceState和onViewCreated处理,适配Fragment重建场景。
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

