Android Event Wrapper使用问题:重复点击触发空指针异常
MutableLiveData Event Wrapper重复点击触发NullPointerException问题
问题描述
首次使用MutableLiveData的Event Wrapper方案,单次点击按钮运行正常,重复点击两次触发NullPointerException。创建两个独立观察者时功能正常,但复用同一观察者多次调用时,getContentIfNotHandled()返回null值。
错误栈信息
java.lang.NullPointerException: Attempt to invoke virtual method 'int com.example.montaggioapp.montaggio.model.ReturnCode.getReturnCode()' on a null object reference at com.example.montaggioapp.montaggio.InCorsoActivity$2.onChanged(InCorsoActivity.java:147) at com.example.montaggioapp.montaggio.InCorsoActivity$2.onChanged(InCorsoActivity.java:143) at androidx.lifecycle.LiveData.considerNotify(LiveData.java:133) at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:146) at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:468) at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:425) at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360) at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:202) at androidx.lifecycle.LiveData.observe(LiveData.java:205) at com.example.montaggioapp.montaggio.InCorsoActivity.lambda$onCreate$0$com-example-montaggioapp-montaggio-InCorsoActivity(InCorsoActivity.java:164) at com.example.montaggioapp.montaggio.InCorsoActivity$$ExternalSyntheticLambda1.onChanged(Unknown Source:6) at androidx.lifecycle.LiveData.considerNotify(LiveData.java:133) at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:151) at androidx.lifecycle.LiveData.setValue(LiveData.java:309) at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50) at androidx.lifecycle.LiveData$1.run(LiveData.java:93) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
相关代码
Activity代码
@AndroidEntryPoint public class InCorsoActivity extends AppCompatActivity implements OnClickListener<Causale>{ private static final String MY_PREFS = "SAVE_VALUE"; private static final String MY_CONFIG = "VALUE_CONFIG"; private SharedPreferences prefs,config; private SharedPreferences.Editor editor; private AlertDialog.Builder builder; private InCorsoViewModel inCorsoViewModel; ActivityInCorsoBinding binding; Observer<Event<ReturnCode>> eventRC_DatiLav, eventRC_Conferma; private SpinnerAdapter adapter; private String pin, ordPro; @RequiresApi(api = Build.VERSION_CODES.N) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_in_corso); prefs = getSharedPreferences(MY_PREFS,MODE_PRIVATE); config = getSharedPreferences(MY_CONFIG,MODE_PRIVATE); editor = prefs.edit(); builder = new AlertDialog.Builder(this); inCorsoViewModel = new ViewModelProvider(this).get(InCorsoViewModel.class); binding = DataBindingUtil.setContentView(this,R.layout.activity_in_corso); binding.setLifecycleOwner(this); binding.setInCorso(inCorsoViewModel); binding.qtaProdotte.setShowSoftInputOnFocus(false); binding.qtaProdotte.requestFocus(); List<SpinnerViewModel> items = new ArrayList<>(); items.add(CausaleMapper.map(new Causale("SEL. CAUSALE"), (OnClickListener<Causale>) this)); items.add(CausaleMapper.map(new Causale("FINE LAV"), (OnClickListener<Causale>) this)); items.add(CausaleMapper.map(new Causale("PAUSA"), (OnClickListener<Causale>) this)); items.add(CausaleMapper.map(new Causale("SOSPESO"), (OnClickListener<Causale>) this)); String serverName = config.getString("serverName",""); String port = config.getString("port",""); String schemaOrion = config.getString("schemaOrion",""); pin= prefs.getString("pin","0") ; ordPro = prefs.getString("ordPro","0"); String baseurl = "http://"+serverName+":"+port+"/ords/"+schemaOrion; eventRC_DatiLav = new Observer<Event<ReturnCode>>() { @Override public void onChanged(Event<ReturnCode> returnCodeEvent) { ReturnCode returnCode = returnCodeEvent.getContentIfNotHandled(); int rc = returnCode.getReturnCode(); if (rc < 0 ){ String msg = returnCode.getReturnMessage(); message(msg, new Cb() { @Override public void onOkCliked() { Intent intent = new Intent(getApplicationContext(), SelezionaAttActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); }else { String[] strDatiLav = returnCode.getReturnMessage().split(";"); binding.OrdineInCorso.setText(strDatiLav[0]); binding.codParteInCorso.setText(strDatiLav[1]); binding.desParteInCorso.setText(strDatiLav[2]); binding.PezziOrdInCorso.setText(strDatiLav[3]); binding.PezziProdInCorso.setText(strDatiLav[4]); binding.StatoInCorso.setText(prefs.getString("stato"," ")); binding.inDateInCorso.setText(getCurrentDate()); adapter = new SpinnerAdapter(getApplicationContext(),R.layout.item_view, BR.viewModel,items); binding.spinner.setAdapter(adapter); } } }; //inCorsoViewModel.makeApiDatiLavCall(ordPro); inCorsoViewModel.makeApiDatiLavCall(ordPro,baseurl); inCorsoViewModel.getLiveDataApiDatiLav().observe(this,eventRC_DatiLav); inCorsoViewModel.getDataOra(binding).observe(this, inCorso -> { if (TextUtils.isEmpty(Objects.requireNonNull(inCorso).getStrDate())) { binding.inDateInCorso.setError("Data non selezionata"); binding.inDateInCorso.requestFocus(); } else if (TextUtils.isEmpty(Objects.requireNonNull(inCorso).getStrTime())) { binding.inTimeInCorso.setError("Orario non selezionato"); binding.inTimeInCorso.requestFocus(); } else { eventRC_Conferma = new Observer<Event<ReturnCode>>() { @Override public void onChanged(Event<ReturnCode> returnCodeEvent) { ReturnCode returnCode = returnCodeEvent.getContentIfNotHandled(); int rc = returnCode.getReturnCode(); if (rc < 0 ){ String msg = returnCode.getReturnMessage(); message(msg, () -> { }); }else { String msg = returnCode.getReturnMessage(); messageEseguito(msg, () -> { Intent intent = new Intent(getApplicationContext(),SelezionaAttActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); }); } } }; inCorsoViewModel.makeApiCall(pin,ordPro,baseurl); inCorsoViewModel.getLiveData().observe(this,eventRC_Conferma); } }); binding.btnAnnulaInCorso.setOnClickListener(view -> { }); }
ViewModel代码
private MutableLiveData<Event<ReturnCode>> liveDataApiCall; @Inject ApiInterface apiInterface; @Inject public InCorsoViewModel(){ this.liveDataApiCall = new MutableLiveData<>(); } @Override public MutableLiveData<Event<ReturnCode>> getLiveData() { return liveDataApiCall; } @RequiresApi(api = Build.VERSION_CODES.N) @Override public void makeApiCall(String pin, String ordPro,String baseurl) { String tipoMov = prefs.getString("tipoMov",""); String idMac = ""; String dataOra = date.getValue()+" "+time.getValue(); String quant = qta.getValue() != null ? qta.getValue() : "0"; String param = ""; AggOrdRepo aggOrdRepo = new AggOrdRepo(apiInterface); aggOrdRepo.getAggiornaOrdine(baseurl,pin,ordPro,tipoMov,idMac,dataOra,quant,param,liveDataApiCall,toastMessageObserver); }
Repository代码
private Disposable disposable; private ApiInterface apiInterface; private MutableLiveData<Event<ReturnCode>> liveDataApiDatiLav; private MutableLiveData<String> toastMessageObserver; public DatiLavRepo(ApiInterface apiInterface){ this.apiInterface = apiInterface; } public void getDatiLav(String baseurl, String ordPro, MutableLiveData<Event<ReturnCode>> liveDataApiDatiLav, MutableLiveData<String> toastMessageObserver){ String HTTPS_PATH_GETORD = null; try { HTTPS_PATH_GETORD = baseurl+"/gm/montaggio?p_cmd=3&p_p1="+ URLEncoder.encode(ordPro,"UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } this.liveDataApiDatiLav = liveDataApiDatiLav; this.toastMessageObserver = toastMessageObserver; disposable = apiInterface.getDatiLavorazione(HTTPS_PATH_GETORD) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(this::handlerResponse,this::handlerError); } private void handlerResponse(ReturnCode rc) { liveDataApiDatiLav.postValue(new Event(rc)); } private void handlerError(Throwable t) { toastMessageObserver.setValue("Something unexpected happened to our request: "+t.getMessage()); } public class EventObserver<T> implements Observer<Event<T>> { private OnEventChanged onEventChanged; public EventObserver(OnEventChanged onEventChanged) { this.onEventChanged = onEventChanged; } @Override public void onChanged(@Nullable Event<T> tEvent) { if (tEvent != null && tEvent.getContentIfNotHandled() != null && onEventChanged != null) onEventChanged.onUnhandledContent(tEvent.getContentIfNotHandled()); } interface OnEventChanged<T> { void onUnhandledContent(T data); } } public class Event<T> { private boolean hasBeenHandled = false; private T content; public Event(T content) { this.content = content; } public T getContentIfNotHandled() { if (hasBeenHandled) { return null; } else { hasBeenHandled = true; return content; } } public boolean isHandled() { return hasBeenHandled; } }
问题原因分析
- Event实例状态复用:
Event类的hasBeenHandled是实例级变量,一旦被处理过,再次调用getContentIfNotHandled()就会返回null。重复点击时,若ViewModel未创建新的Event实例,就会复用旧实例的已处理状态,导致空指针。 - 重复注册观察者:在
getDataOra的观察者回调内每次都创建并注册新的eventRC_Conferma,多次点击后会存在多个观察者。第一个观察者标记Event为已处理后,后续观察者调用getContentIfNotHandled()得到null,触发空指针。 - 未判空直接使用:观察者中直接调用
returnCode.getReturnCode(),未先判断returnCode是否为null。
解决方案
1. 避免重复注册观察者
将eventRC_Conferma的创建和注册移到onCreate方法中,仅在getDataOra回调内触发API调用:
// 在onCreate中初始化并注册观察者 eventRC_Conferma = new Observer<Event<ReturnCode>>() { @Override public void onChanged(Event<ReturnCode> returnCodeEvent) { ReturnCode returnCode = returnCodeEvent.getContentIfNotHandled(); if (returnCode == null) { return; } int rc = returnCode.getReturnCode(); if (rc < 0 ){ String msg = returnCode.getReturnMessage(); message(msg, () -> {}); }else { String msg = returnCode.getReturnMessage(); messageEseguito(msg, () -> { Intent intent = new Intent(getApplicationContext(),SelezionaAttActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); }); } } }; inCorsoViewModel.getLiveData().observe(this, eventRC_Conferma); // 简化getDataOra的观察者回调 inCorsoViewModel.getDataOra(binding).observe(this, inCorso -> { if (TextUtils.isEmpty(Objects.requireNonNull(inCorso).getStrDate())) { binding.inDateInCorso.setError("Data non selezionata"); binding.inDateInCorso.requestFocus(); } else if (TextUtils.isEmpty(Objects.requireNonNull(inCorso).getStrTime())) { binding.inTimeInCorso.setError("Orario non selezionato"); binding.inTimeInCorso.requestFocus(); } else { inCorsoViewModel.makeApiCall(pin, ordPro, baseurl); } });
2. 增加空指针判断
在所有观察者中,先判断returnCode是否为null再执行后续逻辑:
@Override public void onChanged(Event<ReturnCode> returnCodeEvent) { ReturnCode returnCode = returnCodeEvent.getContentIfNotHandled(); if (returnCode == null) { return; } int rc = returnCode.getReturnCode(); // 后续业务逻辑... }
3. 使用自定义EventObserver简化逻辑
直接使用已定义的EventObserver,它已内置空值判断,无需手动处理:
inCorsoViewModel.getLiveData().observe(this, new EventObserver<>(returnCode -> { int rc = returnCode.getReturnCode(); if (rc < 0 ){ String msg = returnCode.getReturnMessage(); message(msg, () -> {}); }else { String msg = returnCode.getReturnMessage(); messageEseguito(msg, () -> { Intent intent = new Intent(getApplicationContext(),SelezionaAttActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); }); } }));
4. 确保每次发送新的Event实例
保持Repository中handlerResponse的逻辑,每次API响应都创建新的Event对象,避免复用旧实例状态:
private void handlerResponse(ReturnCode rc) { liveDataApiDatiLav.postValue(new Event<>(rc)); }
内容的提问来源于stack exchange,提问作者Scripta14
相关产品推荐
相关产品推荐

