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

Android中LiveData的observe方法两次触发该如何解决?

问题根源

你遇到的observe两次触发是三个问题叠加导致的:

  • Repository中的accessToken被声明为静态变量,全局唯一且会保留上次登录结果的缓存
  • 每次点击登录按钮都会重新调用observe方法给同一个LiveData注册新的观察者,旧观察者没有解绑,加上LiveData本身的粘性特性,注册瞬间就会收到上一次缓存的值
  • 登录流程里先主动post了一次null,之后接口返回再post第二次结果,也会导致两次回调

修复步骤

1. 调整Observer注册逻辑,不要在点击事件里注册

把LiveData的观察逻辑移到onCreateView里全局只注册一次,点击登录只触发请求动作,不要重复绑定观察者。

2. 去掉Repository中accessToken的静态修饰,改用非粘性的SingleLiveEvent处理一次性事件

Toast属于一次性消费事件,普通LiveData粘性特性会导致重复回调,用SingleLiveEvent替代原生MutableLiveData,它只会把更新通知给主动订阅后的观察者,不会回调历史缓存值。

3. 优化请求逻辑,避免重复post值

不需要在请求前主动post null,接口失败、返回非201状态的场景也要主动post null触发错误提示。

具体修改代码

首先新增SingleLiveEvent类

public class SingleLiveEvent<T> extends MutableLiveData<T> {
    private final AtomicBoolean pending = new AtomicBoolean(false);
    @Override
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        super.observe(owner, t -> {
            if (pending.compareAndSet(true, false)) {
                observer.onChanged(t);
            }
        });
    }
    @Override
    public void postValue(T value) {
        pending.set(true);
        super.postValue(value);
    }
    @Override
    public void setValue(T value) {
        pending.set(true);
        super.setValue(value);
    }
}

修改Repository类

public class Repository {
    // 去掉static修饰
    private SingleLiveEvent<AccessToken> accessToken;
    private ExecutorService threadPool;
    public Repository() {
        accessToken = new SingleLiveEvent<>();
        threadPool = Executors.newFixedThreadPool(8);
    }
    public SingleLiveEvent<AccessToken> onLoginClick(String email, String password) {
        threadPool.execute(() -> {
            // 不需要提前post null
            UtenteLogin utenteLogin = new UtenteLogin(email, password);
            Call<AccessToken> call = RetrofitClient.getInstance().getMyApi().login(utenteLogin);
            call.enqueue(new Callback<AccessToken>() {
                @Override
                public void onResponse(Call<AccessToken> call, Response<AccessToken> response) {
                    if (response.code() == 201 && response.body() != null) {
                        accessToken.postValue(response.body());
                    } else {
                        // 错误场景主动post null
                        accessToken.postValue(null);
                    }
                }
                @Override
                public void onFailure(Call<AccessToken> call, Throwable t) {
                    Log.println(Log.INFO, "Login", "Login fallito");
                    // 失败场景主动post null
                    accessToken.postValue(null);
                }
            });
        });
        return accessToken;
    }
}

修改ViewModel类

public class LoginViewModel extends ViewModel {
    private final Repository repository;
    public LoginViewModel() {
        repository = new Repository();
    }
    // 新增获取登录结果LiveData的方法
    public SingleLiveEvent<AccessToken> getLoginResult() {
        return repository.accessToken;
    }
    public void login(String email, String password) {
        // 只触发登录请求,不返回LiveData
        repository.onLoginClick(email, password);
    }
}

修改LoginFragment类

public class LoginFragment extends Fragment {
    private LoginViewModel loginViewModel;
    private FragmentLoginBinding binding;
    public static final String SHARED_PREFS = "CREDENTIAL";
    public static final String key = "accessToken";
    private ExecutorService threadPool;
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        threadPool = Executors.newFixedThreadPool(5);
        loginViewModel = new ViewModelProvider(this).get(LoginViewModel.class);
        binding = FragmentLoginBinding.inflate(inflater, container, false);
        View root = binding.getRoot();
        ImageView backgroundLoginImg = binding.backgroundLoginImg;
        Glide.with(getContext()).load(R.drawable.wood).into(backgroundLoginImg);
        EditText email = binding.emailText;
        EditText password = binding.editTextPassword;
        Button login = binding.loginButton;
        // 全局只注册一次观察者
        loginViewModel.getLoginResult().observe(getViewLifecycleOwner(), accessToken -> {
            if (accessToken != null) {
                threadPool.execute(() -> {
                    SharedPreferences sharedPreferences = LoginFragment.this.getActivity()
                            .getSharedPreferences(SHARED_PREFS, Context.MODE_PRIVATE);
                    SharedPreferences.Editor editor = sharedPreferences.edit();
                    editor.putString(key, accessToken.getAccessToken());
                    editor.apply();
                });
                Toast.makeText(LoginFragment.this.getContext(), "Login effettuato",
                        Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(LoginFragment.this.getContext(), "Errore", Toast.LENGTH_SHORT)
                        .show();
            }
        });
        login.setOnClickListener(view -> {
            if (!email.getText().toString().equals("") && !password.getText().toString().equals("")) {
                // 点击只触发登录请求,不重复注册观察者
                loginViewModel.login(email.getText().toString(), password.getText().toString());
                email.getText().clear();
                password.getText().clear();
            } else {
                Toast.makeText(getContext(), "Compilare tutti i campi", Toast.LENGTH_SHORT).show();
            }
        });
        return root;
    }
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 11:45:03