IllegalArgumentException при использовании Otto с сохраненным фрагментом

Я использую Otto 1.3.3, и когда я возобновляю работу с приложением, иногда я получаю IllegalArgumentException со следующей трассировкой стека :

Caused by: java.lang.IllegalArgumentException: Producer method for type class 
com.couchsurfing.mobile.ui.setup
        .SessionProviderFragment$SessionConnectionStateChangeEvent found on 
        type class com.couchsurfing.mobile.ui.setup.SessionProviderFragment, 
        but already registered by type class 
        com.couchsurfing.mobile.ui.setup.SessionProviderFragment.
    at com.squareup.otto.Bus.register(Bus.java:194)
    at com.couchsurfing.mobile.ui.BaseRetainedFragment
       .onCreate(BaseRetainedFragment.java:20)

Экземпляр SessionProviderFragment сохранен, пожалуйста, найдите ниже расширенный класс:

public abstract class BaseRetainedFragment extends SherlockFragment {

    @Inject
    Bus bus;

    @Override
    public void onCreate(final Bundle state) {
        super.onCreate(state);
        ((CouchsurfingApplication) getActivity().getApplication()).inject(this);
        setRetainInstance(true);
        bus.register(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        bus.unregister(this);
        bus = null;
    }
}

Я пробовал оба, используя bus.register(this) в onAttach() или onCreate(), но это не помогло.


person Niqo    schedule 15.05.2013    source источник
comment
Просто подумайте - возможно, вы пропускаете активность, на которой размещен фрагмент, так что в некоторых случаях (например, ротация устройства) вы создаете второй экземпляр этого действия со вторым экземпляром фрагмента.   -  person Andy Dennie    schedule 15.05.2013


Ответы (4)


Правильное место для регистрации на шине находится в onResume(), а правильное место для отмены регистрации - в onPause(), например:

public abstract class BaseRetainedFragment extends RoboSherlockFragment {
    @Inject private Bus bus;

    @Override
    public void onCreate(final Bundle state) {
        super.onCreate(state);
        setRetainInstance(true);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }

    @Override
    public void onPause() {
        super.onDestroy();
        bus.unregister(this);
    }
}

Обратите внимание, что вызов onDestroy() не гарантируется.

Возможно, вы собираетесь прокомментировать это и сказать: «Эй, Крис, если я зарегистрируюсь в onResume() и события будут запущены до того, как я нажму на этот метод, я не получу события! Вы были бы правы, но это означает, что вы не используете продюсеров, как следовало бы.

Также обратите внимание, если вы используете roboguice-sherlock вам не нужно вводить себе инъекцию. Вам также не нужно null Bus, когда Фрагмент выходит за пределы области видимости, сборщик мусора очистит его за вас.

person Christopher Perry    schedule 11.10.2013
comment
Что касается комментария о onDestroy(), вы говорите, что otto может просочиться, если мы вызовем unregister в onDestroy? Когда я читаю документы для onDestroy(), я понимаю, что они означают, что onDestory не следует использовать для сохранения данных, поскольку процесс может быть спонтанно остановлен / перезапущен (как видно на диаграммах жизненного цикла активности). В этом случае активность (и otto) должна быть освобождена. - person Kyle Ivey; 02.04.2014
comment
Кроме специальных целей, не _1 _ / _ 2_ OttoBus во время соответствующего onResume - ›onPause с фрагментом. Я только что нашел лазейку. Если над фрагментом, который предположительно принимает события шины, отображается диалоговое окно, вызывается onPause() на фрагменте и неожиданно отменяется регистрация OttoBus. - person Thuy Trinh; 31.05.2014

Я использовал Otto и EventBus в основном для передачи обновлений из фоновых служб в Activities и Fragments. Я не знаю вашего точного варианта использования, но наиболее частым использованием для меня было обновление пользовательского интерфейса (например, ProgressBar, status message и т. Д.).

Сказав это, я считаю наиболее эффективным зарегистрировать шину в методе onViewCreated() фрагмента и отменить регистрацию в методе onDestroyView(). При условии, что сообщения шины являются постоянными (через provider для Otto или sticky для EventBus), таким образом вы не потеряете никаких сообщений.

person eeVoskos    schedule 15.05.2013

Я использую один «Сохраненный фрагмент» для каждого действия, чтобы сохранить состояние запроса сеанса HTTP. Моя проблема заключалась в том, что я неправильно создал экземпляр своего «Сохраненного фрагмента».

Раньше я использовал onCreate ():

if (savedInstanceState == null) {
    sessionProviderFragment = new SessionProviderFragment();
    getSupportFragmentManager().beginTransaction().add(sessionProviderFragment,
        SessionProviderFragment.TAG).commit();
}

По-видимому, приведенный выше код может создать несколько SessionProviderFragment при выходе из действия и повторном его открытии позже. Похоже, что правильный путь:

sessionProviderFragment = (SessionProviderFragment) getSupportFragmentManager()
    .findFragmentByTag(SessionProviderFragment.TAG);

// If not retained (or first time running), we need to create it.
if (sessionProviderFragment == null) {
    sessionProviderFragment = new SessionProviderFragment();
    getSupportFragmentManager().beginTransaction().add(sessionProviderFragment,
            SessionProviderFragment.TAG).commit();
}
if (savedInstanceState == null) {
    initUiFragment();
}

Я также переместил регистр шины / отмену регистрации в onResume / onPause в моем BaseFragment, чтобы быть уверенным, что у меня всегда будет один SessionProviderFragment, зарегистрированный на шине за раз.

person Niqo    schedule 18.05.2013

На самом деле небезопасно иметь @Produce на Fragment, потому что более одного экземпляра фрагмента могут существовать (и быть зарегистрированы на шине) одновременно.

На мой взгляд, @Produce действительно имеет смысл только на singleton.

person Karsten    schedule 17.05.2013