BottomNavigation切换Fragment时RecyclerView条目重复 arraylist.clear失效
问题根因
列表重复追加是三个代码逻辑错误共同导致的,和Fragment控件、TabLayout/BottomNavigation组件本身无关:
- Adapter的
setItems()方法使用addAll()追加数据,每次调用不会清空原有列表,直接把新数据拼接在旧数据尾部 - 列表集合被加了
static修饰,属于类级别的全局变量,不会随Fragment实例销毁重置,多个Fragment实例会共用同一份脏数据 - 每次切换底部导航都新建Fragment实例,重复注册Firebase数据监听器,且没有在Fragment销毁时移除监听器,导致多份监听器同时触发回调
- 你之前加的
audioFileArrayList == null判断只是临时拦住了重复加载,没有解决根本问题,还会导致后续数据库数据变动时列表无法正常刷新。
修复步骤
1. 修正Adapter的数据更新逻辑
去掉Adapter里列表的static修饰,setItems方法每次更新数据前先清空原有列表,不要用追加逻辑:
public class AudioFileAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private Context context; // 去掉static修饰,每个Adapter实例持有独立的列表数据 private ArrayList<AudioFile> audioFileList = new ArrayList<>(); public AudioFileAdapter(Context ctx) { this.context = ctx; } public void setItems(ArrayList<AudioFile> audioFile) { // 先清空旧数据,再添加新拉取的数据,禁止直接addAll追加 audioFileList.clear(); audioFileList.addAll(audioFile); // 数据变更后直接在Adapter内部通知刷新,不需要在外部重复调用 notifyDataSetChanged(); } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.audio_item, parent, false); return new AudioFileViewHolder(view); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, @SuppressLint("RecyclerView") int position) { AudioFileViewHolder audioFileViewHolder = (AudioFileViewHolder) holder; AudioFile audioFile = audioFileList.get(position); audioFileViewHolder.txtArtist.setText(audioFile.getArtist()); audioFileViewHolder.txtTitle.setText(audioFile.getTitle()); Glide.with(context).load(audioFile.getImgURL()).into(audioFileViewHolder.imageViewPicture); holder.itemView.setOnClickListener(view -> { Bundle bundle = new Bundle(); bundle.putInt("posicion", position); Intent intent = new Intent(context, Reproductor.class); intent.putExtras(bundle); context.startActivity(intent); }); ((AudioFileViewHolder) holder).imageViewMenu.setOnClickListener(view -> { PopupMenu popupMenu = new PopupMenu(context, view); popupMenu.getMenuInflater().inflate(R.menu.audio_item_popup_menu, popupMenu.getMenu()); popupMenu.show(); popupMenu.setOnMenuItemClickListener(menuItem -> { switch (menuItem.getItemId()) { case R.id.agregar_a_lista_ID: break; case R.id.eliminar_de_biblioteca_ID: eliminar(position); break; } return true; }); }); } public void eliminar(int position) { String id = audioFileList.get(position).getId(); DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference(); Query delete = databaseReference.child("biblioteca").orderByChild("id").equalTo(id); delete.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // 删除后不需要手动清空列表,ValueEventListener会自动拉取最新数据触发刷新 for (DataSnapshot data : dataSnapshot.getChildren()) { data.getRef().removeValue(); } } @Override public void onCancelled(DatabaseError databaseError) {} }); } @Override public int getItemCount() { return audioFileList.size(); } public interface ItemClickListener { void onItemClick(AudioFile audioFile); } }
2. 修复Fragment的静态变量和监听器泄漏问题
去掉Fragment里列表的static修饰,在onDestroyView生命周期中移除Firebase监听器,避免重复注册回调:
public class BibliotecaFragment extends Fragment { FragmentBibliotecaBinding binding; RecyclerView recyclerView; AudioFileAdapter audioFileAdapter; // 去掉static修饰 private ArrayList<AudioFile> audioFileArrayList; // 持有数据库引用和监听器实例,方便销毁时移除 private DatabaseReference dbr; private ValueEventListener firebaseListener; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentBibliotecaBinding.inflate(inflater, container, false); recyclerView = binding.BibliotecaFragmentRecyclerViewID; recyclerView.setHasFixedSize(true); LinearLayoutManager manager = new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false); recyclerView.setLayoutManager(manager); audioFileAdapter = new AudioFileAdapter(getContext()); recyclerView.setAdapter(audioFileAdapter); loadData(); return binding.getRoot(); } @Override public void onDestroyView() { super.onDestroyView(); // 页面销毁时移除监听器,避免内存泄漏和重复回调 if (dbr != null && firebaseListener != null) { dbr.removeEventListener(firebaseListener); } binding = null; } public void loadData() { dbr = FirebaseDatabase.getInstance().getReference().child("biblioteca"); firebaseListener = new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { audioFileArrayList = new ArrayList<>(); for (DataSnapshot data : snapshot.getChildren()) { AudioFile audioFile = data.getValue(AudioFile.class); audioFileArrayList.add(audioFile); } audioFileAdapter.setItems(audioFileArrayList); } @Override public void onCancelled(@NonNull DatabaseError error) { // 建议在此处添加错误日志,方便后续排查数据库拉取问题 } }; dbr.addValueEventListener(firebaseListener); } }
3. 优化BottomNavigation切换逻辑(可选,建议修改)
当前每次切换导航都新建Fragment实例会造成不必要的性能开销,缓存已创建的Fragment实例即可:
public class MainActivity extends AppCompatActivity { ActivityMainBinding binding; // 缓存Fragment实例,避免重复创建 private Fragment bibliotecaFragment; private Fragment playlistsFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); if (getSupportActionBar() != null) { getSupportActionBar().hide(); } bibliotecaFragment = new BibliotecaFragment(); playlistsFragment = new PlayListsFragment(); getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout_id, bibliotecaFragment).commit(); binding.bottomNavigationID.setOnItemSelectedListener(item -> { Fragment selectedFragment = null; switch (item.getItemId()) { case R.id.biblioteca_ID: selectedFragment = bibliotecaFragment; break; case R.id.playlists_ID: selectedFragment = playlistsFragment; break; } if (selectedFragment != null) { getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout_id, selectedFragment).commit(); } return true; }); } }
开发注意事项
- 不要随意给和页面、实例绑定的变量加
static修饰,static变量生命周期和APP进程一致,很容易造成内存泄漏、数据状态混乱 - 所有注册的监听器、回调,一定要在对应页面销毁时主动移除,避免后台触发不必要的逻辑
- RecyclerView全量更新数据时,必须先清空旧数据再添加新数据,不能直接追加。
内容的提问来源于stack exchange,提问作者Rubs
相关产品推荐
相关产品推荐

