Фрагмент OnClickListener, вызванный после onDestroyView

У меня проблема, когда ListFragment.onListItemClick вызывается после onDestroyView. Я получаю много сообщений об ошибках в поле (10-20 в день ~ 1000 активных пользователей), но единственный способ, которым я нашел, чтобы воспроизвести его, - это забить кнопку "Назад", щелкая по всему экрану. Действительно ли сотни пользователей действительно это делают? Это трассировка:

java.lang.IllegalStateException: Content view not yet created
at au.com.example.activity.ListFragment.ensureList(ListFragment.java:860)
at au.com.example.activity.ListFragment.getListView(ListFragment.java:695)
at au.com.example.activity.MyFragment.onListItemClick(MyFragment.java:1290)
at au.com.example.activity.ListFragment$2.onItemClick(ListFragment.java:90)
at android.widget.AdapterView.performItemClick(AdapterView.java:301)
at android.widget.AbsListView.performItemClick(AbsListView.java:1519)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:3278)
at android.widget.AbsListView$1.run(AbsListView.java:4327)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)

Вызывается при вызове getListView().getItemAtPosition в MyFragment.onListItemClick (MyFragment: 1290). Как getView вернуть значение null во время обратного вызова обработчика щелчка? Я также решил, что фрагмент был отсоединен на этом этапе, isAdded() был ложным, а getActivity - null.

Одним из способов было бы заменить getListView на listView, переданный из обратного вызова public void onListItemClick(ListView listView, View v, int position, long id), но другим функциям все равно придется обновлять другие части пользовательского интерфейса, так что это просто переместит проблему в другое место. Вместо этого я отключил обратный вызов в onDestroyView:

public void onDestroyView() {           
        mHandler.removeCallbacks(mRequestFocus);
        if(mList!=null){
             mList.setOnItemClickListener(null);
        }
        mList = null;
        mListShown = false;
        mEmptyView = mProgressContainer = mListContainer = null;
        mStandardEmptyView = null;
        super.onDestroyView();
    }

Но у меня все еще есть эта проблема onClick в других (не-список) фрагментах тоже. Как именно структура обычно подавляет эти обратные вызовы при удалении фрагмента (например, в onBackPressed -> popBackStackImmediate())? В onDestroyView я исключаю дополнительные представления, которые я создал в onCreateView. Нужно ли вручную очищать каждый прослушиватель, который я установил так?

Это аналогичная проблема с неотвеченным q: getView() Fragment, возвращающим null в обратном вызове OnClickListener

Я использую setOnRetainInstance(true) в своих фрагментах, кстати.

Ответ 1

Вы действительно не предоставили очень много информации, но основываясь на том, что вы дали, это звучит как Fragment в ожидании транзакций может быть вашей проблемой.

В Android, всякий раз, когда вы меняете или создаете фрагменты, все это делается через ожидающие транзакции, если не сказано иначе. Это по существу условие гонки.

getSupportFragmentManager().beginTransaction()
    .replace(R.id.container, new ExampleFragment()
    .commit();

В потоке пользовательского интерфейса есть очередь работы, которую он должен выполнять в любой момент времени. Несмотря на то, что после выполнения вышеуказанного кода вы совершили FragmentTransaction, он фактически был поставлен в очередь в потоке пользовательского интерфейса в конце очереди, чтобы произойти после того, как все, что в настоящее время ожидается, завершено. Это означает, что если события щелчка происходят во время ожидания транзакции (что может легко произойти, т.е. Вы щелкаете клики на экране или щелкаете несколькими пальцами), эти события щелчка будут помещены в очередь потоков пользовательских интерфейсов после FragmentTransaction.

Конечным результатом является то, что транзакция фрагмента обрабатывается, фрагмент View уничтожается, а затем вы вызываете getView() и он возвращает null.

Вы можете попробовать несколько вещей:

  • getSupportFragmentManager().executePendingTransactions() Затем он выполнит все ожидающие транзакции и удалит ожидающий аспект

  • Проверьте, не найден ли метод фрагмента isVisible() или isAdded() или какой-либо другой фрагмент ', который позволяет вам получать информацию о текущем состоянии текущего состояния, в котором находится фрагмент, до того, как вы выполните код, который может потенциально запускаться после уничтожения фрагмента (т.е. прослушивания).

  • Итак, скажем, у вас есть обработчик кликов, когда пользователь нажимает на что-то, что вы анималируете на другой фрагмент. Вы можете использовать что-то вроде приведенного ниже кода, который вы выполняете до FragmentTransaction в своем внешнем виде (в фрагменте это будет то, что возвращается от getView()), и это либо навсегда отключит клики для представления, если он будет уничтожен или временно отключит клики в течение некоторого периода времени, если вы собираетесь повторно использовать представление.

Надеюсь, что это поможет.


public class ClickUtil {

  /**
   * Disables any clicks inside the given given view.
   *
   * @param view The view to iterate over and disable all clicks.
   */
  public static void disable(View view) {
    disable(view, null);
  }

  /**
   * Disables any clicks inside the given given view for a certain amount of time.
   *
   * @param view The view to iterate over and disable all clicks.
   * @param millis The number of millis to disable clicks for.
   */
  public static void disable(View view, Long millis) {
    final List<View> clickableViews = (millis == null) ? null : new ArrayList<View>();
    disableRecursive(view, clickableViews);

    if (millis != null) {
      MainThread.handler().postDelayed(new Runnable() {
        @Override public void run() {

          for (View v : clickableViews) {
            v.setClickable(true);
          }

        }
      }, millis);
    }
  }

  private static void disableRecursive(View view, List<View> clickableViews) {
    if (view.isClickable()) {
      view.setClickable(false);

      if (clickableViews != null)
        clickableViews.add(view);
    }

    if (view instanceof ViewGroup) {
      ViewGroup vg = (ViewGroup) view;
      for (int i = 0; i < vg.getChildCount(); i++) {
        disableRecursive(vg.getChildAt(i), clickableViews);
      }
    }
  }

}

Ответ 2

Положите руку на нее из-за дополнительных фрагментов без гражданства, живущих где-то внутри вашего приложения. Я лично препятствовал бы сохранению экземпляра и позволял Android делать с ним все возможное, в то время как вы используете стандартный механизм для сохранения своего состояния (saveInstanceState, базы данных, классы/шаблоны высокого уровня, SharedPreferences и т.д.).

Лично у меня было много проблем при сохранении экземпляра фрагмента (обычно при повторном создании или повторной активации фрагментов через изменения конфигурации или выходе и повторном входе в приложение), в результате, как правило, на двух фрагментах, один из которых связан к взглядам, без гражданства, таким образом бесполезным; и "реальный", сохраняющий прежнее состояние без какой-либо связи с представлениями, следовательно, заканчивая исключениями и всякими причудами, которые вы не хотите иметь.

Начните с того, что вы не сохраните экземпляр и не увидите, что происходит.

Ответ 3

Вы можете использовать mHandler.removeCallbacksAndMessages(null) для меня во многих ситуациях.