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

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;
    }

}

问题原因分析

  1. Event实例状态复用:Event类的hasBeenHandled是实例级变量,一旦被处理过,再次调用getContentIfNotHandled()就会返回null。重复点击时,若ViewModel未创建新的Event实例,就会复用旧实例的已处理状态,导致空指针。
  2. 重复注册观察者:在getDataOra的观察者回调内每次都创建并注册新的eventRC_Conferma,多次点击后会存在多个观察者。第一个观察者标记Event为已处理后,后续观察者调用getContentIfNotHandled()得到null,触发空指针。
  3. 未判空直接使用:观察者中直接调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 02:47:02