Фрагмент getView() возвращает null в обратном вызове OnClickListener

Я использую библиотеку поддержки, и у меня есть фрагмент (я назову «MyFragment»), реализующий метод, вызываемый одним из представлений фрагмента во время события OnClick. OnClickListener устанавливается в методе OnActivityCreate следующим образом:

@Override
public void onActivityCreated(Bundle inState) {
    super.onActivityCreated(inState);

    ViewGroup base = (ViewGroup) getView();
    TextView tv = (TextView) base.findViewById(R.id.monografiat);
    tv.setOnClickListener(new OnClickListener() {                            
        @Override
        public void onClick(View v) {
            showStuff(); // MyFragment:150
        }
    });
}

где showStuff() запускает изменение представления фрагмента, что-то столь же простое, как обновление видимости элемента:

private void showStuff() {  //MyFragment:95
    ViewGroup base = (ViewGroup) getView();
    LinearLayout ll = (LinearLayout) base.findViewById(R.id.someview); // MyFragment:97
    ll.setVisibility(View.VISIBLE);
}

В моих тестах все работает нормально, и то же самое можно сказать о большинстве моих пользователей, однако сегодня я получил единственный отчет ANR из консоли разработчика Google Play, в котором говорится, что приложение аварийно завершилось для пользователя с NullPointerException в строке:

LinearLayout ll = (LinearLayout) base.findViewById(R.id.someview);

Который означает, что:

ViewGroup base = (ViewGroup) getView();

вернул ноль. Это отчет:

java.lang.NullPointerException
    at mypackage.MyFragment.showStuff(MyFragment.java:97)
    at mypackage.MyFragment.access$0(MyFragment.java:95)
    at mypackage.MyFragment$2.onClick(MyFragment.java:150)
    at android.view.View.performClick(View.java:2538)
    at android.view.View$PerformClick.run(View.java:9152)
    at android.os.Handler.handleCallback(Handler.java:587)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:123)
    at android.app.ActivityThread.main(ActivityThread.java:3687)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
    at dalvik.system.NativeStart.main(Native Method)

Я решил проблему (или так я думаю, поскольку у меня нет метода воспроизведения...), просто окружив оскорбительные строки:

if(base != null) {
}

мое лучшее предположение состоит в том, что обратный вызов был запланирован после вызова onDestroyView для MyFragment, в результате чего getView() возвращает значение null.

Тем не менее, я весьма озадачен этой ошибкой, поэтому мне хотелось бы узнать ваше мнение по этому вопросу:

  1. предполагая, что моя гипотеза верна, является ли нормальным выполнение обратного вызова OnClick при отсутствии макета, или я могу рассматривать это поведение как ошибку (честно: представление было явно там, когда я щелкнул его, почему он должен умереть на меня)?

  2. Связана ли эта проблема с тем, что я использую библиотеку поддержки (последняя версия, 13 мая 2013 г.)?

  3. Если это не ошибка и я что-то упустил, не могли бы вы указать мне соответствующую документацию?

Спасибо за Ваше внимание!


person Rick77    schedule 22.07.2013    source источник
comment
По какой причине вы используете ViewGroup вместо View? Можете ли вы также проверить, определено ли R.id.someview в представлении, которое возвращает getView()?   -  person Vikram    schedule 22.07.2013
comment
Не в этом конкретном случае, однако я знаю, что макет является подклассом ViewGroup (и в противном случае Java выдаст исключение ClassCastException). Что касается R.id.someview, то если бы соответствующее представление не было бы найдено, исключение было бы выброшено на строку 98 (вместо 97)   -  person Rick77    schedule 22.07.2013
comment
Вы правы, я полностью пропустил это.   -  person Vikram    schedule 22.07.2013
comment
нет: все равно спасибо   -  person Rick77    schedule 22.07.2013
comment
Обратный вызов onActivityCreated() предназначен для MyFragment, верно? Кроме того, переменная base, используемая в onActivityCreated(), это ...?   -  person user    schedule 22.07.2013
comment
ой... мой плохой, в попытке упростить я пропустил эту строку. Я подтверждаю, что onActivityCreated предназначен для файла MyFragment (весь код принадлежит этому классу). Спасибо за замечание!   -  person Rick77    schedule 22.07.2013
comment
Есть аналогичный отчет о сбое, цепной вызов onClick разбился, потому что фрагмент getView == null... Очень запутанно, как это могло произойти...   -  person Happy Dev    schedule 15.09.2014
comment
По какой-то причине вы не инициируете эту переменную l1 один раз в своем onCreateView или что-то в этом роде? Еще одна вещь: если showStuff является private членом вашего фрагмента, вам даже не нужно вызывать getView, чтобы получить переменную base.   -  person Soroush    schedule 06.12.2014
comment
не было особой причины инициализировать ll в onCreateView (по производительности и памяти), и я предпочитаю (по причине DRY/опрятности) избегать кэширования переменных в экземпляре, если их легко вычислить и их сохранение не критично (кстати, код, который я написал, отлично работает на младшем GT5500). Что касается второго вопроса, извините, но я не думаю, что понял его: насколько я знаю, getView - это правильный метод для получения базового вида фрагмента (если он есть).   -  person Rick77    schedule 06.12.2014


Ответы (1)


До появления Gingerbread в механизме кликов View была ошибка, заключавшаяся в том, что обратный вызов для выполнения клика не удалялся из очереди сообщений, когда View отсоединялся от Window. Это было исправлено в ICS (или, возможно, в Honeycomb). Поэтому, если вы поддерживаете Gingerbread или более раннюю версию, и ваши прослушиватели кликов обращаются к другим ссылкам, которые удаляются при отсоединении View, вам следует также удалить прослушиватели кликов при отсоединении.

Обратите внимание, что это относится ко всем типам прослушивателей кликов, а не только к OnClickListener. Однако это не относится к прослушивателям длинных кликов, поскольку их обратные вызовы были удалены с самого начала.

person corsair992    schedule 16.12.2014
comment
Это идеально соответствует проблеме (жаль, что я не нашел об этом, когда разрабатывал рассматриваемое приложение... :|): не могли бы вы предоставить какой-нибудь авторитетный источник для этой проблемы? Тогда я отмечу ваш ответ как решение. Спасибо - person Rick77; 16.12.2014
comment
Я не знаю ни одного старого отчета об ошибке, на который можно ссылаться, но наиболее авторитетным источником является источник;) - person corsair992; 16.12.2014
comment
Я вижу: при беглом взгляде я бы сказал, что проблема в строке 5843 (отсутствует removeTapCallback), когда окно теряет фокус. Отличная работа, правда! Благодарю вас! - person Rick77; 16.12.2014
comment
На самом деле реальное исправление находится в строке #9796 с включением removePerformClickCallback() среди других удалений обратного вызова в обратный вызов onDetachedFromWindow(). Я связался с этой частью разницы, но она переходит к разделу только тогда, когда страница полностью загружена. И добро пожаловать - даже если вы вернете меня в мои поиски шляпы охотника за сокровищами с помощью значка невоспетого героя, проголосовав и приняв мой ответ :) - person corsair992; 16.12.2014