Сериализация прослушивателя Android

У меня есть класс CustomDialog, который расширяет DialogFragment.. Я переопределяю метод onCreateDialog, чтобы получить нужный диалог.

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    dialog = new Dialog(activity, styleId);
    view = activity.getLayoutInflater().inflate(layoutId, null);
    dialog.setContentView(view);
    if (listener != null) {
        listener.onViewInit(view, this);
    }
    return dialog;

}

Это код создания пользовательского диалога. После того, как представление раздуто, я вызываю метод слушателя listener.onViewInit(view, this) типа OnViewInitListener, который является интерфейсом и расширяет Serializable, чтобы привязать пользовательский код к представлению (просмотр текстов, слушателей и т. д.), так что при вращении я хочу потерять логику нажатия кнопки.

@Override
public void onSaveInstanceState(Bundle bundle) {
    bundle.putInt("layoutId", layoutId);
    bundle.putInt("styleId", styleId);
    bundle.putSerializable("listener", listener);
    super.onSaveInstanceState(bundle);

}

public RsCustomDialog setOnListenerAssignment(OnViewInitListener listener) {
    this.listener = listener;
    return this;
}

Когда я реализую OnViewInitListener из Activity, при изменении ориентации все работает так, как ожидалось: onCreateDialog вызывается каждый раз, когда фрагмент воссоздается, и ошибок посылок нет, но когда я нажимаю кнопку истории приложений (справа)

person Giorgi EgoTwin Khutsishvili    schedule 09.10.2014    source источник


Ответы (4)


Вы не можете сериализовать и восстановить прослушиватель.

Сериализация (включая использование Parcelable) сохраняет состояние экземпляра объекта, а десериализация помещает это состояние в новый экземпляр объекта.

Слушатели не имеют состояния, поэтому ваша реализация Parcelable ничего не сохраняет и не восстанавливает. Переменная слушателя — это ссылка на экземпляр объекта (который, как известно, реализует интерфейс слушателя). Если создается новый экземпляр этого объекта (например, из-за поворота или уничтожения процесса из-за нехватки памяти), диалоговое окно не помогает восстановить указатель на предыдущий экземпляр. Предыдущий экземпляр больше не существует, а вновь созданный (правильный) экземпляр не существовал во время вызова onSaveInstanceState.

Две возможные альтернативы:

  • Если прослушиватель предназначен для действия, к которому привязан диалог, то его можно восстановить в методе onAttach DialogFragment.
  • Если нет, то действие может установить прослушиватель фрагментов при его создании (или повторном создании). Чтобы получить ссылку на автоматически восстановленный экземпляр фрагмента, можно использовать getFragmentManager().findFragmentById(id) или getFragmentManager().findFragmentByTag(tag).
person Stan Kurdziel    schedule 16.03.2016
comment
Однако второй вариант немного сложен, так как если действие создается повторно, оно потеряет всех сохраненных слушателей... если только оно не сохранено с использованием статической переменной или в приложении, но это не очень хорошая практика... - person User; 19.05.2017
comment
@lxx Я согласен, статическая переменная - не очень хорошая идея - я также не могу придумать случай, когда экземпляр приложения имеет смысл, но это не значит, что его не существует. Моя мысль о втором варианте заключалась в том, чтобы либо создать новый экземпляр прослушивателя, либо установить экземпляр фрагмента, полученный из FragmentManager, в качестве прослушивателя. - person Stan Kurdziel; 19.05.2017

Ваш OnViewInitListener должен быть статическим и сериализуемым и иметь внутри все сериализуемые поля. Если вы ссылаетесь на Activity из него, вы делаете это неправильно. Чтобы решить проблему, вы можете:

  1. Ссылка на экземпляр действия, хранящийся в статической переменной WeakReference, которая заполняется при создании действия.
  2. Используйте широковещательные приемники
  3. Перерегистрируйте прослушиватель, когда фрагмент будет восстановлен с новым и правильным контекстом.
person Eugene Popovich    schedule 09.10.2014
comment
Привет, спасибо за ответ! Я могу сказать, что я проверил 1 и 3 из вашего списка: 1) Я ссылаюсь на переменные из статической ссылки на активность, то есть в BaseActivity. 3) Я не хочу перерегистрировать слушателя, моя цель - зарегистрировать его один раз, а затем забыть об этом. При изменении конфигурации он должен автоматически восстанавливать прослушиватель. Но я всегда использую правильный контекст в классе CustomDialog. 2- Я не знаю, что вы имеете в виду, как это может помочь? - person Giorgi EgoTwin Khutsishvili; 09.10.2014

вы можете написать что-то в onResume и onStop, что вы слушатель отменяете регистрацию в методе onStop, регистрируете в методе onResume

person wnp    schedule 10.06.2017
comment
Это больше похоже на комментарий, чем на ответ. - person danglingpointer; 10.06.2017

Ну, я решил это! Что я сделал, так это реализовал Parcable следующим образом:

public abstract class OnClickListener implements DialogInterface.OnClickListener, Parcelable {
    @Override
    public abstract void onClick(DialogInterface dialog, int which);

    @Override
    public void writeToParcel(Parcel dest, int flags) {

    }

    @Override
    public int describeContents() {
        return 0;
    }
}

Итак, мой код ConfirmDialog остается прежним:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    return new AlertDialog.Builder(getActivity())
        .setTitle(title).setMessage(text)
        .setPositiveButton(R.string.yes, onYes)
        .setNegativeButton(R.string.no, onNo).create();
}

@Override
public void onSaveInstanceState(Bundle bundle) {
    super.onSaveInstanceState(bundle);
    bundle.putParcelable("onYes", onYes);
    bundle.putParcelable("onNo", onNo);
}

Единственное ограничение — не используйте автоматические переменные, которые не являются Parcelable в методе onClick. Вот мой пример отображения этого диалога:

       showConfirmDialog(getString(R.string.sure_want_to_exit), new base.dialog.OnClickListener() {

        @Override
        public void onClick(DialogInterface dialog, int which) {
            ((NewProtocol) getCurrentActivity()).exit = true;               
            getCurrentActivity().finish();
        }
    }, null);

getCurrentActivity() — это статический метод, который возвращает текущую активную активность.

person Giorgi EgoTwin Khutsishvili    schedule 09.10.2014
comment
Ничего не записывая в посылку, мы ничего не получаем. Это вообще ничего не сохраняет и не десериализует. Вам нужно установить прослушиватель из действия, когда состояние экземпляра будет восстановлено. Не сохраняйте указатель на ничто. Этот ответ намного хуже, чем сбой, потому что он молча прерывает работу слушателей в крайних случаях, когда достигаются пределы памяти. Это может привести к тому, что огромные ошибки останутся незамеченными в рабочей среде. - person colintheshots; 09.05.2017
comment
Почему это вообще работает? На самом деле, это также работает без явного сохранения в onSaveInstanceState. getArguments().getParcelable("listener") всегда возвращает один и тот же объект, переданный в Bundle, независимо от того, сколько раз менялась ориентация телефона. - person Saad Farooq; 21.11.2017