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

Fragment中调用removeObserver无法移除LiveData观察者问题求助

问题描述

我有一个作为Activity标签页的Fragment,想要在这个Fragment中一次性观察FJViewModel的LiveData,但调用removeObserver后观察者并未被移除,onChanged方法仍反复执行。奇怪的是,用getAllFilters()替代getFiltersById(0)时,removeObserver能正常工作,观察者只执行一次。我只会Java,看不懂Kotlin方案,请问问题出在哪?

相关代码如下:

Fragment代码

public class JInPersonFragment extends Fragment {

    private FJViewModel fjViewModel;
    private FragmentJInPersonBinding _InPerson;

    public static JInPersonFragment newInstance() {return new JInPersonFragment();}

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        _InPerson = FragmentJInPersonBinding.inflate(getLayoutInflater());
        fjViewModel = new ViewModelProvider(requireActivity()).get(FJViewModel.class);

        Observer<Filterj> observer = new Observer<Filterj>() {
            @Override
            public void onChanged(Filterj filterj)
            {// 代码反复进入onChanged方法
                Log.i("jinperson",""+(filterj==null)); 

                 // ... 其他业务逻辑

                fjViewModel.getFiltersById(0).removeObserver(this);
            }
        };

        fjViewModel.getFiltersById(0).observe(getViewLifecycleOwner(), observer);

       return _InPerson.getRoot();
    }
}

ViewModel代码

public class FJViewModel extends AndroidViewModel
{
    private FJRepository repo;
    private LiveData<List<Filterj>> allFilters;

    public FJViewModel(@NonNull Application application)
    {
        super(application);
        repo = new FJRepository(application);
        allFilters = repo.getAllFilters();
    }

    public void insert(Filterj filter) {
        repo.insert(filter);
    }
    public void update(Filterj filter) {
        repo.update(filter);
    }
    public void delete(Filterj filter) {
        repo.delete(filter);
    }
    public LiveData<List<Filterj>> getAllFilters(){
        return allFilters;
    }
    public LiveData<Filterj> getFiltersById(int id){
        return repo.getFiltersById(id);
    }
}

DAO代码

@Dao
public interface FJDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insert(Filterj filter);
    @Update
    void update(Filterj filter);
    @Delete
    void delete(Filterj filter);

    @Query("SELECT * FROM filter_jalase ORDER BY id ASC")
    LiveData<List<Filterj>> getAllFilters();

    @Query("SELECT * FROM filter_jalase WHERE id = :id")
    LiveData<Filterj> getFiltersById(int id);
}
问题原因

核心问题在于每次调用getFiltersById(int id)都会返回一个全新的LiveData实例,而getAllFilters()返回的是ViewModel中初始化好的同一个LiveData对象:

  • 当你调用fjViewModel.getFiltersById(0).observe(...)时,观察的是第一个LiveData实例;
  • 而在onChanged里调用fjViewModel.getFiltersById(0).removeObserver(this)时,拿到的是第二个全新的LiveData实例,移除操作作用在新实例上,对之前正在被观察的实例完全无效,所以原来的观察者会一直收到回调。
解决方案

要解决这个问题,必须确保观察和移除观察者操作的是同一个LiveData实例,以下是两种可行方案:

方案一:在ViewModel中缓存LiveData实例

修改ViewModel,为需要重复使用的LiveData做缓存(单个id用变量,多个id用Map):

public class FJViewModel extends AndroidViewModel
{
    private FJRepository repo;
    private LiveData<List<Filterj>> allFilters;
    // 缓存id=0对应的LiveData
    private LiveData<Filterj> filterById0;
    // 如果需要支持多个id,用Map缓存
    // private Map<Integer, LiveData<Filterj>> filterMap;

    public FJViewModel(@NonNull Application application)
    {
        super(application);
        repo = new FJRepository(application);
        allFilters = repo.getAllFilters();
        // filterMap = new HashMap<>();
    }

    // ... 增删改方法保持不变

    public LiveData<List<Filterj>> getAllFilters(){
        return allFilters;
    }

    public LiveData<Filterj> getFiltersById(int id){
        if (id == 0) {
            if (filterById0 == null) {
                filterById0 = repo.getFiltersById(id);
            }
            return filterById0;
        }
        // 多id场景的缓存逻辑
        // filterMap.computeIfAbsent(id, repo::getFiltersById);
        // return filterMap.get(id);
        return repo.getFiltersById(id);
    }
}

方案二:在Fragment中提前保存LiveData实例

在Fragment的onCreateView中先获取一次LiveData实例并保存,后续观察和移除都用这个实例:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    _InPerson = FragmentJInPersonBinding.inflate(getLayoutInflater());
    fjViewModel = new ViewModelProvider(requireActivity()).get(FJViewModel.class);

    // 提前获取并保存目标LiveData实例
    LiveData<Filterj> targetLiveData = fjViewModel.getFiltersById(0);

    Observer<Filterj> observer = new Observer<Filterj>() {
        @Override
        public void onChanged(Filterj filterj)
        {
            Log.i("jinperson",""+(filterj==null)); 

            // ... 其他业务逻辑

            // 用保存的实例移除观察者
            targetLiveData.removeObserver(this);
        }
    };

    targetLiveData.observe(getViewLifecycleOwner(), observer);

   return _InPerson.getRoot();
}

额外优化:一次性观察工具方法

如果你的需求只是获取一次数据,不需要持续观察,可以自己实现一个一次性观察的工具类,简化代码:

public class LiveDataUtils {
    public static <T> void observeOnce(LiveData<T> liveData, LifecycleOwner owner, Observer<T> observer) {
        liveData.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(T t) {
                observer.onChanged(t);
                liveData.removeObserver(this);
            }
        });
    }
}

在Fragment中使用:

LiveDataUtils.observeOnce(fjViewModel.getFiltersById(0), getViewLifecycleOwner(), filterj -> {
    Log.i("jinperson",""+(filterj==null)); 
    // ... 其他业务逻辑
});

内容的提问来源于stack exchange,提问作者arman javadi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:51:08