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

Kotlin中findViewById获取SurfaceView子类Game返回null问题排查

解决SurfaceView子类在onWindowFocusChanged中findViewById返回null的问题

可能的原因

  • 重复调用findViewById且未缓存实例:每次触发onWindowFocusChanged都重新查找View,当视图处于销毁/ detach状态时(比如切后台),可能返回null。
  • onWindowFocusChanged触发时机不稳定:该方法可能在视图未完全初始化完成或开始销毁时被调用,此时视图树尚未稳定,导致查找失败。
  • 未做非空判断:直接调用findViewById的结果方法,一旦返回null就会引发崩溃。

解决方案

1. 缓存Game实例到Activity成员变量

在onCreate中一次性查找并保存Game实例,后续直接使用该变量,避免重复查找:

class GameActivity : AppCompatActivity() {
    private lateinit var thread: GameThread
    private val mainVM by viewModels<MainViewModelImageViewer>()
    private lateinit var gameView: Game // 新增成员变量缓存实例

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_game)
        // 初始化时缓存Game实例
        gameView = findViewById(R.id.gameImageViewwer)
    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        if(hasFocus) {
            gameView.startGame()
            gameView.resumeGame()
        }
        else {
            // 确保实例已初始化再调用方法
            if(::gameView.isInitialized) {
                mainVM.save(gameView.save3iClickCount(), gameView.save4iNumberOfRemoved())
                gameView.pauseGame()
            }
        }
    }
}

2. 使用View Binding替代findViewById

View Binding是更安全的视图获取方式,能避免ID拼写错误和空指针问题:

首先在模块的build.gradle中启用View Binding:

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

然后修改Activity代码:

class GameActivity : AppCompatActivity() {
    private lateinit var binding: ActivityGameBinding // 对应activity_game.xml的Binding类
    private lateinit var thread: GameThread
    private val mainVM by viewModels<MainViewModelImageViewer>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityGameBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        val gameView = binding.gameImageViewwer
        if(hasFocus) {
            gameView.startGame()
            gameView.resumeGame()
        }
        else {
            mainVM.save(gameView.save3iClickCount(), gameView.save4iNumberOfRemoved())
            gameView.pauseGame()
        }
    }
}

3. 添加非空判断(临时方案)

如果坚持使用findViewById,每次调用前必须添加非空判断,避免崩溃:

override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)
    val gameView = findViewById<Game>(R.id.gameImageViewwer) ?: return
    if(hasFocus) {
        gameView.startGame()
        gameView.resumeGame()
    }
    else {
        mainVM.save(gameView.save3iClickCount(), gameView.save4iNumberOfRemoved())
        gameView.pauseGame()
    }
}

额外检查点

  • 确认布局文件activity_game.xml中的Game控件IDgameImageViewwer拼写正确,且包名com.example.imageviewer.Game与实际Game类的包名一致。
  • 确保Game类的构造函数包含Context和AttributeSet参数(你的代码中已经实现,无需修改)。

内容的提问来源于stack exchange,提问作者duperele 11

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 14:27:51