【问题标题】:Fragment's OnClickListener called after onDestroyView在 onDestroyView 之后调用 Fragment 的 OnClickListener
【发布时间】:2013-09-06 03:22:34
【问题描述】:

我有一个问题,ListFragment.onListItemClickonDestroyView 之后被调用。我在现场收到了很多错误报告(大约 1000 个活跃用户每天 10-20 个),但我发现重现它的唯一方法是在单击整个屏幕时敲击后退按钮。成百上千的用户真的在这样做吗? 这是痕迹:

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)

由在 MyFragment.onListItemClick (MyFragment:1290) 中调用 getListView().getItemAtPosition 引起。 getView 如何在单击处理程序回调期间返回 null?我还确定在这个阶段片段被分离,isAdded() 为假,getActivity 为空。

一种解决方法是将getListView 替换为从回调中传入的listView public void onListItemClick(ListView listView, View v, int position, long id),但其他功能仍需要更新 UI 的其他部分,所以这只会将问题转移到其他地方。 相反,我取消了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 中创建的额外视图。我需要手动清除我这样设置的每个侦听器吗?

这与未回答的问题类似:Fragment's getView() returning null in a OnClickListener callback

顺便说一句,我在片段中使用了setOnRetainInstance(true)

【问题讨论】:

  • 可以给片段代码吗?
  • 你能发布你的点击代码吗?你在那里做任何背景工作吗?您是否在 onclick 中检查 getView() == null 吗?
  • 您的应用支持轮播吗?当您的手机旋转时,当前活动被破坏并创建一个新活动。这意味着如果用户在旋转之前单击了某个项目,则单击将返回,但 Activity 上下文和视图将为空。按照建议检查视图是否不为空,请确保您没有模拟尝试通过旋转访问上下文(因为它已被破坏)。
  • 您是否为 OnClickListener 使用静态内部类?作为快速修复,我要做的是覆盖 onBackPressed() 并将所有元素上的 Clickable 设置为 false 或放入一个状态布尔值,如果按下返回,则不执行任何操作。请记住,ClickListener 对它所侦听的元素具有隐式引用,因此即使您 setOnItemClickListener(null),它仍将引用该对象。甚至可能是 OnDestroy 延迟到足以在设置的 null 发生之前被调用。

标签: android android-fragments android-lifecycle


【解决方案1】:

您确实没有提供太多信息,但根据您提供的信息,听起来 Fragment pending Transactions 可能是您的问题。

在 Android 中,无论何时更改或实例化片段,都通过待处理事务完成,除非另有说明。这本质上是一种竞争条件。

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

UI 线程有一个工作队列,它需要在任何给定时间执行。即使您在运行上述代码后提交了 FragmentTransaction,它实际上已在队列末尾的 UI 线程上排队,在当前待处理的所有内容完成后发生。这意味着,如果在事务未决时发生点击事件(这很容易发生,即您在屏幕上进行垃圾点击,或者用多个手指点击),这些点击事件将被放置在 UI 线程队列中之后 FragmentTransaction。

最终结果是Fragment Transaction被处理,你的Fragment View被销毁,然后你调用getView()它返回null。

您可以尝试一些方法:

  1. getSupportFragmentManager().executePendingTransactions()这将立即执行所有待处理事务,并删除待处理方面

  2. 1234563可能会在 Fragments 视图被销毁后运行(即点击侦听器)
  3. 假设您有一个单击处理程序,当用户单击某个内容时,您会为另一个片段设置动画。您可以在最外面的视图上使用类似下面的代码,在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);
      }
    }
  }

}

【讨论】:

  • 这个问题实际上与Fragment 事务无关 - 它是由前 ICS(/Honeycomb?) 实现中的一个错误引起的,当@987654332 时,执行点击的回调保留在消息队列中@ 与 Window 分离。使Views 在分离时不可点击不会解决这个问题,因为任何已经在队列中的回调仍然会被执行——你需要实际删除所有的点击监听器来解决这个问题。我在其他链接的question 上添加了对该效果的答案。
  • @corsair992 你有这个缺陷的问题跟踪器的链接吗?我正在调查在较新的设备上可能会发生这样的事情的可能性。谢谢!
  • @FearTheCron:正如我在answer 中提到的链接问题,我不知道有关此问题的任何旧错误报告。这只是我在查看 Android 源代码以查找报告问题的潜在原因时发现的。
【解决方案2】:

我敢打赌,这是由于存在于您的应用程序内部某处的额外无状态片段。我个人不鼓励保留实例,让 Android 尽其所能,而您使用标准机制来保留您的状态(saveInstanceState、数据库、高级类/模式、SharedPreferences 等)。

就个人而言,我在保留片段实例时遇到了很多问题(通常是通过更改配置重新创建或重新激活片段或离开并重新进入应用程序时),通常导致两个片段,其中一个连接到意见,无国籍,因此无用;而“真实的”保持之前的状态,与视图没有任何联系,因此最终会出现异常和你不想拥有的各种幻想。

从不保留实例开始,看看会发生什么。

【讨论】:

    【解决方案3】:

    您可以在许多情况下使用 mHandler.removeCallbacksAndMessages(null) 为我工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-12-15
      • 1970-01-01
      • 2019-04-08
      • 1970-01-01
      • 2020-03-03
      • 1970-01-01
      • 2016-05-20
      相关资源
      最近更新 更多