Android中ViewModel的LiveData观察者为何未按预期顺序执行
问题根因
执行顺序和代码书写顺序不一致是异步非阻塞操作的回调时序特性导致的:
- 你调用
observe()注册观察者、Retrofit发起网络请求都属于非阻塞操作:代码执行到这两行时,只会完成观察者注册、把网络任务丢给后台线程排队,主线程不会等待网络请求返回,会立刻继续向下执行,所以最后一行的Finish提示会最先弹出。 - 登录、购物车接口的网络请求在后台线程执行完成后,才会触发LiveData回调,执行你写在observe里的业务逻辑,这部分代码天然比主线程同步执行的Toast晚运行。
- 原有逻辑还有个流程错误:注册完登录观察者就立刻判断
isLoggedIn()拉取购物车,这时候登录请求还没返回,token和用户信息都没存储,这个判断根本不会在登录成功后自动触发。
修复方案
按照「登录成功→存储用户凭证→拉取购物车数据→弹出完成提示」的串行流程,把后续逻辑放到对应异步回调里执行,不要把异步代码按同步顺序书写。
修正后的LoginActivity onCreate核心代码
AlertDialog alert = createAlertDialog(this); // 注册登录结果观察者 loginViewModel.getLoginResponseLiveData(email, password).observe(this, loginSignupResponse -> { if(!loginSignupResponse.isError()){ User user = loginSignupResponse.getUser(); String token = loginSignupResponse.getToken(); LoginUtils.getInstance(this).saveUserInfo(user); LoginUtils.getInstance(this).saveUserToken(token); Toast.makeText(this, "Login successful.", Toast.LENGTH_SHORT).show(); // 登录校验通过后,再发起购物车请求 String authToken = "Bearer " + token; cartViewModel.getProductsInCart(authToken).observe(this, cartResponse -> { if(cartResponse != null){ List<Cart> carts = cartResponse.getCarts(); CartUtils.getInstance().setCarts(carts); } alert.dismiss(); // 购物车数据拉取完成后再弹出完成提示 Toast.makeText(this, "Finish", Toast.LENGTH_SHORT).show(); }); }else{ alert.dismiss(); Toast.makeText(this, loginSignupResponse.getMessage(), Toast.LENGTH_SHORT).show(); } });
额外需要修复的Repository层隐患
你当前的Repository实现每次调用接口方法都会新建MutableLiveData实例,会导致重复发起请求、重复注册观察者,甚至引发内存泄漏。建议把LiveData改为Repository持有的单例成员变量,不要每次调用方法都新建,以LoginRepository为例修正:
public class LoginRepository { private static final String TAG = LoginRepository.class.getSimpleName(); // 作为成员变量持有,避免重复创建 private final MutableLiveData<LoginSignupResponse> loginLiveData = new MutableLiveData<>(); public LiveData<LoginSignupResponse> getLoginResponseData(String email, String password){ JsonObject jsonBody = new JsonObject(); jsonBody.addProperty("email", email); jsonBody.addProperty("password", password); RetrofitClient.getInstance().getApi().loginUser(jsonBody).enqueue(new Callback<LoginSignupResponse>() { @Override public void onResponse(Call<LoginSignupResponse> call, Response<LoginSignupResponse> response) { Log.d(TAG, "onResponse: Succeeded"); LoginSignupResponse loginSignupResponse; if(response.code() == 200){ loginSignupResponse = response.body(); }else{ String errorMessage = "Incorrect Information!"; try { JSONObject jObjError = new JSONObject(response.errorBody().string()); errorMessage = jObjError.getString("message"); } catch (JSONException | IOException e) { e.printStackTrace(); } loginSignupResponse = new LoginSignupResponse(errorMessage); } // 子线程更新LiveData用postValue loginLiveData.postValue(loginSignupResponse); } @Override public void onFailure(Call<LoginSignupResponse> call, Throwable t) { Log.d(TAG, "onFailure: " + t.getMessage()); } }); return loginLiveData; } }
CartRepository按照同样逻辑修改,把MutableLiveData改为成员变量持有即可。
注意:Retrofit的网络回调默认执行在子线程,更新LiveData时要使用
postValue(),不要直接调用setValue(),否则会抛出线程访问异常。
内容的提问来源于stack exchange,提问作者Phan Phanit
相关产品推荐
相关产品推荐

