跳棋游戏drawPawns代码运行触发ANR弹窗,如何优化消除该问题
问题根因分析
ANR出现的核心原因是当前drawPawns方法存在多余耗时操作,且大概率存在高频重复调用、或执行了大量无效UI渲染的问题:
setBackgroundResource每次调用都会重新解码对应drawable资源,全量遍历所有棋盘格子时如果格子数量多、方法被重复调用,累计耗时会快速上涨,阻塞主线程- 方法没有做脏校验,每次调用都全量更新所有格子,哪怕大部分格子的棋子状态没有发生变化
- 如果该方法被放到非UI线程执行,或者主线程同时被其他计算任务阻塞,也会触发ANR
具体优化方案
1. 提前缓存Drawable资源,避免重复解码
不要每次调用setBackgroundResource都让系统重新加载资源,提前在页面/视图初始化阶段就把所有用到的棋子drawable缓存起来:
// 类成员变量,在onCreate/View初始化阶段完成赋值 private Drawable whitePawnDrawable; private Drawable brownPawnDrawable; private Drawable whiteQueenDrawable; private Drawable brownQueenDrawable; private Drawable emptyDrawable; // 初始化逻辑,整个页面生命周期只执行一次 private void initDrawables(Context context) { whitePawnDrawable = ContextCompat.getDrawable(context, R.drawable.white_pawn); brownPawnDrawable = ContextCompat.getDrawable(context, R.drawable.brown_pawn); whiteQueenDrawable = ContextCompat.getDrawable(context, R.drawable.white_queen); brownQueenDrawable = ContextCompat.getDrawable(context, R.drawable.brown_queen); emptyDrawable = new ColorDrawable(Color.TRANSPARENT); }
之后更新背景时直接用setBackground方法传入缓存好的drawable,跳过资源解码步骤。
2. 增加脏数据校验,只更新状态发生变化的格子
不要每次全量遍历所有格子,维护上一次的棋子状态数组,仅对比更新状态发生变化的格子:
// 类成员变量,存储上一次渲染的棋子状态 private int[] lastPawnState; public void drawPawns() { if (lastPawnState == null) { lastPawnState = new int[playableTile.length]; } for(int i=0; i<playableTileView.length; i++) { int currentState = playableTile[i].getIsTaken(); // 状态没有变化直接跳过渲染 if (currentState == lastPawnState[i]) { continue; } // 状态变化才更新UI,同时记录新状态 lastPawnState[i] = currentState; Drawable targetDrawable; switch (currentState) { case 1: targetDrawable = whitePawnDrawable; break; case -1: targetDrawable = brownPawnDrawable; break; case 2: targetDrawable = whiteQueenDrawable; break; case -2: targetDrawable = brownQueenDrawable; break; default: targetDrawable = emptyDrawable; break; } playableTileView[i].setBackground(targetDrawable); } }
如果是玩家走子、AI走子的常规场景,甚至可以不用遍历整个数组,直接只更新走子涉及到的2-3个格子(起点、落点、被吃的棋子位置),性能提升会更明显。
3. 线程逻辑优化
所有调用drawPawns的逻辑都要放在主线程执行,不要在子线程直接操作UI;如果有其他耗时计算(比如AI走子逻辑、胜负判定逻辑),放到子线程执行,计算完成后再切主线程更新UI。
如果确实需要全量刷新棋盘,不要在动画、手势响应等对时延要求高的场景调用该方法。
内容的提问来源于stack exchange,提问作者Franklyn Oreben
相关产品推荐
相关产品推荐

