You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 21:01:02