FireBase余额扣减操作执行不停歇?技术问题咨询
问题分析与解决方案
问题根源
你代码中使用的addValueEventListener是实时监听,只要users.child(userID).child("coins")的数据发生变化,onDataChange就会被触发。当你在onDataChange里执行updateChildren扣减积分后,coins的值更新,监听会再次触发并重复执行扣减逻辑,最终形成无限循环,导致积分被无终止重复扣除。
方案一:使用单次监听替代实时监听
将addValueEventListener替换为addListenerForSingleValueEvent,它只会读取一次数据,不会持续监听数据变化,从根源上避免循环触发:
Button btn_res = (Button) findViewById(R.id.btn_res); btn_res.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int coins = Integer.parseInt(edCoins.getText().toString()); // 修正判断条件:与提示文本的"最少5000"保持一致 if(coins < 5000){ Toast.makeText(MainActivity2.this,"Минимум 5000", Toast.LENGTH_SHORT).show(); return; } else { users.child(userID).child("coins").addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { ucoins = snapshot.getValue(Integer.class); // 新增余额校验:确保用户余额足够扣减 if(ucoins >= coins){ HashMap hashMap = new HashMap(); hashMap.put("coins",ucoins - coins); users.child(userID).updateChildren(hashMap).addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Object o) { Toast.makeText(MainActivity2.this, "Заявка записана", Toast.LENGTH_SHORT).show(); // 把记录Reset的操作移到这里:确保扣减成功后再生成记录 Reset newReset = new Reset(coins); mDataBase.push().setValue(newReset); } }); } else { Toast.makeText(MainActivity2.this,"余额不足", Toast.LENGTH_SHORT).show(); } } @Override public void onCancelled(@NonNull DatabaseError error) { // 处理取消事件,给用户反馈 Toast.makeText(MainActivity2.this,"操作失败", Toast.LENGTH_SHORT).show(); } }); } } });
方案二:使用Firebase事务处理(更安全)
如果担心并发操作(比如用户多次快速点击按钮),推荐用事务处理,它能确保数据更新的原子性,避免并发导致的异常:
Button btn_res = (Button) findViewById(R.id.btn_res); btn_res.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int coins = Integer.parseInt(edCoins.getText().toString()); if(coins < 5000){ Toast.makeText(MainActivity2.this,"Минимум 5000", Toast.LENGTH_SHORT).show(); return; } users.child(userID).child("coins").runTransaction(new Transaction.Handler() { @NonNull @Override public Transaction.Result doTransaction(@NonNull MutableData mutableData) { Integer currentCoins = mutableData.getValue(Integer.class); // 校验余额是否充足 if(currentCoins == null || currentCoins < coins){ return Transaction.abort(); } mutableData.setValue(currentCoins - coins); return Transaction.success(mutableData); } @Override public void onComplete(@Nullable DatabaseError error, boolean committed, @Nullable DataSnapshot currentData) { if(committed){ Toast.makeText(MainActivity2.this, "Заявка записана", Toast.LENGTH_SHORT).show(); Reset newReset = new Reset(coins); mDataBase.push().setValue(newReset); } else { Toast.makeText(MainActivity2.this,"余额不足或操作失败", Toast.LENGTH_SHORT).show(); } } }); } });
额外注意事项
- 你原代码中判断条件
coins < 1与提示文本"Минимум 5000"逻辑不一致,已修正为coins < 5000 - 建议将记录
Reset的操作移到扣减成功的回调内,避免出现"扣减失败但生成了Reset记录"的异常情况 - 补充了
onCancelled和事务失败的反馈逻辑,提升用户体验
内容的提问来源于stack exchange,提问作者ploxo
相关产品推荐
相关产品推荐

