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
相关产品推荐
相关产品推荐

