Firebase Realtime Database Android Java 竞态条件问题求助
解决Firebase异步请求竞态导致的用户列表顺序错乱问题
问题本质
当前代码中,getPlayerCount按Firebase内部字母顺序遍历用户ID,但每个setPlayerUsername是异步请求——登录用户的请求因本地缓存会优先完成,直接调用playerList.add(player)会打乱原本的顺序,同时依赖playerIndex关联后续setPlayerPicks也会因顺序错乱导致索引偏移。
解决方案1:固定列表位置,异步填充数据
先根据用户数量初始化playerList的结构,保证顺序与Firebase返回一致,异步请求完成后直接填充对应位置的数据,而非动态添加。
修改getPlayerCount方法:
private void getPlayerCount() { playerInPoolQuery = playerInPoolReference.child(poolId).orderByValue().equalTo(true); readPlayerCountData(playerInPoolQuery, new PicksActivity.AllPicksPlayerCountCallback() { @Override public void onSuccess(DataSnapshot dataSnapshot) { numPlayersInPool = (int) dataSnapshot.getChildrenCount(); // 初始化列表,固定长度和顺序 playerList = new ArrayList<>(numPlayersInPool); for (int i = 0; i < numPlayersInPool; i++) { playerList.add(null); // 占位,后续填充 } int playerIndex = 0; for (DataSnapshot snapshot : dataSnapshot.getChildren()) { String userId = snapshot.getKey(); setPlayerUsername(userId, playerIndex); playerIndex++; } } }); }
修改setPlayerUsername方法:
private void setPlayerUsername(final String userId, final int playerIndex) { userQuery = userReference.child(userId).child("username"); readPlayerUsernameData(userQuery, new SurvivorAllPicksActivity.AllPicksPlayerUsernameCallback() { @Override public void onSuccess(DataSnapshot dataSnapshot) { String username = (String) dataSnapshot.getValue(); AllPicks player = new AllPicks(username); // 直接填充到指定索引位置,保证顺序 playerList.set(playerIndex, player); for (int i = 1; i <= currWeek; i++) { setPlayerPicks(userId, playerIndex, i); } } }); }
解决方案2:批量获取用户数据,减少异步请求
通过Firebase的in查询一次性获取所有需要的用户名,只需一次异步请求,完全避免竞态问题:
修改getPlayerCount方法:
private void getPlayerCount() { playerInPoolQuery = playerInPoolReference.child(poolId).orderByValue().equalTo(true); readPlayerCountData(playerInPoolQuery, new PicksActivity.AllPicksPlayerCountCallback() { @Override public void onSuccess(DataSnapshot dataSnapshot) { // 收集所有用户ID,保留原顺序 List<String> userIdList = new ArrayList<>(); for (DataSnapshot snapshot : dataSnapshot.getChildren()) { userIdList.add(snapshot.getKey()); } numPlayersInPool = userIdList.size(); // 批量查询所有用户的username userReference.orderByKey().in(userIdList).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot userDataSnapshot) { // 按原userIdList顺序构建playerList for (String userId : userIdList) { DataSnapshot userSnapshot = userDataSnapshot.child(userId); String username = userSnapshot.child("username").getValue(String.class); AllPicks player = new AllPicks(username); playerList.add(player); } // 批量处理用户选择 for (int playerIndex = 0; playerIndex < userIdList.size(); playerIndex++) { String userId = userIdList.get(playerIndex); for (int i = 1; i <= currWeek; i++) { setPlayerPicks(userId, playerIndex, i); } } } @Override public void onCancelled(DatabaseError databaseError) { // 此处添加错误处理逻辑 } }); } }); }
注意事项
- 方案1适合需要逐个处理用户的场景,方案2更高效,能减少网络请求次数。
- 确保
playerList在主线程操作(Firebase回调默认在主线程,无需额外处理)。 - 使用方案1时,若需UI更新,可通过计数器跟踪已完成的请求数量,待全部完成后再刷新界面。
内容的提问来源于stack exchange,提问作者Learn2Code
相关产品推荐
相关产品推荐

