屏幕旋转时避免生成新GameInstance ID,恢复游戏实例状态的方案
游戏实例状态保持与UI重建解决方案
一、解决屏幕旋转重复生成GameInstanceID的问题
核心是精准区分场景,不在onCreate中无条件生成新ID:
分场景获取ID:
在onCreate中按优先级判断ID来源:先检查是否从历史游戏列表跳转传入ID,再检查是否是屏幕旋转的状态恢复,最后才是首次启动新游戏生成ID。private lateinit var currentGameInstanceId: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_game) currentGameInstanceId = when { // 从历史列表进入,直接取传入的ID intent.hasExtra("GAME_INSTANCE_ID") -> intent.getStringExtra("GAME_INSTANCE_ID")!! // 屏幕旋转重建,恢复之前保存的ID savedInstanceState != null -> savedInstanceState.getString("GAME_INSTANCE_ID")!! // 首次启动新游戏,生成唯一ID else -> UUID.randomUUID().toString() } // 初始化游戏:加载数据、构建UI setupGame(currentGameInstanceId) } // 保存ID用于屏幕旋转恢复 override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString("GAME_INSTANCE_ID", currentGameInstanceId) }隔离新游戏触发逻辑:
把“新游戏”按钮的点击事件作为唯一的新游戏入口,点击时才生成新ID并初始化,避免onCreate的默认逻辑干扰。
二、GridLayout替代方案与UI重建优化
如果GridLayout在状态恢复时表现异常,推荐以下两种更可靠的方案:
1. RecyclerView + GridLayoutManager
RecyclerView自带状态管理,适配动态数据更灵活:
- 封装分数单元格为ViewHolder,用列表承载所有分数数据;
- 从Room加载游戏数据后,直接绑定到Adapter,RecyclerView自动重建网格布局。
// 分数数据模型 data class ScoreRecord(val playerId: String, val round: Int, val score: Int) // Grid适配器 class ScoreGridAdapter(private val scores: List<ScoreRecord>) : RecyclerView.Adapter<ScoreGridAdapter.ScoreHolder>() { inner class ScoreHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val scoreTv: TextView = itemView.findViewById(R.id.tv_score) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScoreHolder { val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.item_score_cell, parent, false) return ScoreHolder(itemView) } override fun onBindViewHolder(holder: ScoreHolder, position: Int) { val record = scores[position] holder.scoreTv.text = record.score.toString() // 可添加分数修改的交互逻辑 } override fun getItemCount() = scores.size } // 初始化RecyclerView private fun setupGame(gameId: String) { val rvGrid = findViewById<RecyclerView>(R.id.rv_score_grid) // 列数设为玩家数量,实现网格布局 rvGrid.layoutManager = GridLayoutManager(this, getPlayerCount(gameId)) // 从Room加载该游戏的所有分数记录 val scoreRecords = scoreDao.getScoresByGameId(gameId) rvGrid.adapter = ScoreGridAdapter(scoreRecords) }
2. ConstraintLayout 动态生成单元格
如果偏好直接操作视图,可通过ConstraintLayout动态创建分数单元格:
- 提前计算每个单元格的行列约束;
- 加载游戏数据后,循环生成TextView并设置约束、绑定分数值,屏幕旋转时重新执行该逻辑即可。
三、完整流程落地(暂停、历史列表)
- 暂停游戏:无需额外处理,只要确保分数修改实时同步到Room即可,退出时标记游戏实例为“未完成”。
- 历史游戏列表:
- 从Room查询所有游戏实例,按“已完成/未完成”分组展示;
- 点击列表项时,通过Intent传递对应
gameInstanceId到游戏Activity; - 游戏Activity接收ID后,从Room加载全量数据,重建UI继续游戏。
核心原则:所有游戏状态数据持久化到Room,依赖gameInstanceId作为唯一标识,任何场景下都通过ID拉取完整数据重建状态,避免依赖内存状态保存。
内容的提问来源于stack exchange,提问作者Christof Plieschnegger
相关产品推荐
相关产品推荐

