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
相关产品推荐
相关产品推荐

