片段的onClickListener在onDestroyView之后调用

时间:2013-09-03 02:02:10

标签: android android-fragments android-lifecycle

我遇到ListFragment.onListItemClick之后调用onDestroyView的问题。我在现场收到大量的错误报告(每天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)

getListView().getItemAtPosition中调用MyFragment.onListItemClick(MyFragment:1290)。 getView如何在单击处理程序回调期间返回null?我还确定片段在此阶段已分离,isAdded()为false,并且getActivity为null。

一种解决方法是将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中创建的额外视图。我是否需要手动清除我设置的每个听众?

这与未答复的q:Fragment's getView() returning null in a OnClickListener callback

类似

我在我的片段中使用setOnRetainInstance(true),顺便说一下。

3 个答案:

答案 0 :(得分:1)

您确实没有提供太多信息,但根据您提供的内容,听起来好像待处理的片段可能是您的问题。

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

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

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

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

你可以尝试一些事情:

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

  2. 检查片段isVisible()isAdded()或其他片段'是否为'允许您在执行可能在片段视图被销毁后运行的代码(即单击侦听器)之前获取有关Fragment在其生命周期中的当前状态的运行时信息的方法

  3. 因此,假设您有一个点击处理程序,当用户点击您为其他片段设置动画的内容时。您可以在最外层视图FragmentTransaction之前使用下面的代码(在片段中,它是从getView()返回的内容),这将是永久性的如果要销毁,则禁用对视图的点击,如果要重新使用该视图,则暂时禁用点击一段时间。

  4. 希望这有帮助。


    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);
          }
        }
      }
    
    }
    

答案 1 :(得分:-1)

请注意我的手臂,这是因为您的应用内部存在更多无状态碎片。我个人不鼓励保留实例,让Android尽其所能,同时使用标准机制来保持你的状态(saveInstanceState,数据库,高级类/模式,SharedPreferences等)。

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

首先不保留实例,看看会出现什么。

答案 2 :(得分:-1)

你可以在很多情况下使用mHandler.removeCallbacksAndMessages(null)。