You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

切换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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 17:13:23