安卓Firebase实时数据库更新删除触发无限循环异常
问题根因
数据无限循环、应用卡死是Firebase实时数据库监听器的错误用法导致,和字段本身无关,班次时间、票价字段之前能更新只是测试时刚好输入值和旧值一致,未触发数据变更回调,没有进入死循环而已。
核心错误点:
EditBus.java中,你在更新按钮的点击事件里给当前巴士节点绑定了ValueEventListener,该监听器会在监听节点的任何数据发生变更时立刻触发onDataChange回调,而你在回调内直接执行updateChildren写操作,写操作又会触发监听器回调,回调再次触发写操作,直接形成无限触发的死循环。- 每次点击更新按钮都会新增一个从未移除的
ValueEventListener,点击次数越多,并发触发的回调越多,应用卡死速度越快。 AddBus.java中存在完全相同的逻辑错误:在点击事件里给Bus根节点绑定永久监听器,回调内执行setValue写操作,同样会触发无限循环;同时该文件存在笔误,引用了未定义的timingsEdit变量,正常情况下无法通过编译。- 你当前以巴士编号作为节点ID存储数据,修改巴士编号时仅更新节点内的
busNum字段,不会同步修改节点存储路径,会导致数据ID和实际值不匹配的问题。
修复方案
所有写操作(新增、更新、删除)不需要绑定ValueEventListener,直接调用Firebase提供的写方法,通过写操作自带的完成回调处理结果即可。
1. 修复EditBus.java
替换原有更新按钮点击逻辑和删除方法,增加非空校验、操作完成回调,避免重复跳转:
updateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String busNumVar= busNumEdit.getText().toString().trim(); String srcVar=srcEdit.getText().toString().trim(); String destVar=destEdit.getText().toString().trim(); String timingsVar=timingsEdit.getText().toString().trim(); String priceVar=priceEdit.getText().toString().trim(); if(busNumVar.isEmpty() || srcVar.isEmpty() || destVar.isEmpty() || timingsVar.isEmpty() || priceVar.isEmpty()){ Toast.makeText(Edit_Bus.this, "所有字段不能为空", Toast.LENGTH_SHORT).show(); return; } Map<String,Object> map=new HashMap<>(); map.put("busNum", busNumVar); map.put("src",srcVar); map.put("dest",destVar); map.put("timings",timingsVar); map.put("price",priceVar); map.put("busID",busNumVar); // 如果你需要支持修改巴士编号作为主键,替换下面的更新逻辑为:先在新ID节点写入数据,写入成功后删除旧ID节点 databaseReference.updateChildren(map).addOnCompleteListener(task -> { if(task.isSuccessful()){ Toast.makeText(Edit_Bus.this, "班次更新成功", Toast.LENGTH_SHORT).show(); startActivity(new Intent(Edit_Bus.this,MainActivity2.class)); finish(); }else{ Toast.makeText(Edit_Bus.this, "更新失败:"+task.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); } }); private void deleteBus(){ databaseReference.removeValue().addOnCompleteListener(task -> { if(task.isSuccessful()){ Toast.makeText(this, "班次已删除", Toast.LENGTH_SHORT).show(); startActivity(new Intent(Edit_Bus.this,MainActivity2.class)); finish(); }else{ Toast.makeText(this, "删除失败:"+task.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); }
注意:如果你的业务逻辑允许修改巴士编号,不能直接用updateChildren更新,需要先获取旧节点的完整数据,写入到以新巴士编号为ID的节点下,写入成功后再删除旧ID节点,否则会出现节点ID和数据内busNum字段不一致的问题。
2. 修复AddBus.java
移除多余的监听器,修复变量名笔误,增加非空校验:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 修复笔误:替换不存在的timingsEdit为已定义的timings变量 String busNumVar=busNum.getText().toString().trim(); String srcVar=src.getText().toString().trim(); String destVar=dest.getText().toString().trim(); String timingsVar=timings.getText().toString().trim(); String priceVar=price.getText().toString().trim(); if(busNumVar.isEmpty() || srcVar.isEmpty() || destVar.isEmpty() || timingsVar.isEmpty() || priceVar.isEmpty()){ Toast.makeText(Add_Bus.this, "所有字段不能为空", Toast.LENGTH_SHORT).show(); return; } BusID=busNumVar; BusRVModel busRVModel=new BusRVModel(busNumVar,srcVar,destVar,timingsVar,priceVar,BusID); databaseReference.child(BusID).setValue(busRVModel).addOnCompleteListener(task -> { if(task.isSuccessful()){ Toast.makeText(Add_Bus.this, "班次添加成功", Toast.LENGTH_SHORT).show(); startActivity(new Intent(Add_Bus.this,MainActivity2.class)); finish(); }else{ Toast.makeText(Add_Bus.this, "添加失败:"+task.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); } });
3. 开发注意事项
ValueEventListener属于永久监听器,只要不主动调用removeEventListener移除,会一直监听对应节点的数据变化,绝对不要在该监听器的onDataChange回调中执行对同一节点的写操作,否则必然触发无限循环。- 如果需要用监听器更新UI,务必在Activity的
onStop生命周期中移除已绑定的监听器,避免内存泄漏和后台无效回调触发。 - Firebase所有写操作都是异步执行,不要在调用写方法后立刻执行页面跳转等逻辑,要在操作完成回调中根据执行结果做后续处理,避免数据未写入完成就跳转导致的数据不一致。
内容的提问来源于stack exchange,提问作者CypherZ
相关产品推荐
相关产品推荐

