【问题标题】:causing a java.IllegalStateException error, No Activity, only when navigating to Fragment for the SECOND time仅在第二次导航到 Fragment 时导致 java.IllegalStateException 错误,无活动
【发布时间】:2013-02-02 12:08:54
【问题描述】:

我遇到了一个非常令人费解的错误,我什至不知道如何开始解决。

我有一个带有一个活动的简单应用程序,视图是用片段实现的。其中一个片段内部有一个 ViewPager;所以我决定我想使用 v4 支持库的 getChildFragmentManager 类。我还不得不使用导致问题的 ActionBarSherlock,因为它没有随 v4 库的 v11 一起提供。

我通过将 ABS 中的 v4 支持库替换为 v11 库来解决此问题,并且所有内容都已编译并似乎可以正常工作,包括 ViewPager。

这是奇怪的部分:

第一次打开带有 ViewPager 的 Fragment,它可以正常工作;但是第二次导航到,应用程序崩溃,给出一个无用的堆栈跟踪。通过调试,我发现问题出在 getChildFragmentManager 返回的 FragmentManager 上;它抛出无活动错误。

有人知道是什么原因造成的吗?

我将发布您认为相关的代码。

谢谢你, 大卫

【问题讨论】:

  • 请发布完整的 logcat 输出
  • 究竟什么是“无活动”错误?就像在片段中没有附加到一个活动?导致错误的代码行是什么?

标签: android android-fragments actionbarsherlock android-viewpager android-support-library


【解决方案1】:

我点击了jeremyvillalobos answer 中的链接(非常有帮助),该链接将我带到了this workaround

public class CustomFragment extends Fragment {
    private static final Field sChildFragmentManagerField;

    static {
        Field f = null;
        try {
            f = Fragment.class.getDeclaredField("mChildFragmentManager");
            f.setAccessible(true);
        } catch (NoSuchFieldException e) {
            Log.e(LOGTAG, "Error getting mChildFragmentManager field", e);
        }
        sChildFragmentManagerField = f;
    }

    @Override
    public void onDetach() {
        super.onDetach();

        if (sChildFragmentManagerField != null) {
            try {
                sChildFragmentManagerField.set(this, null);
            } catch (Exception e) {
                Log.e(LOGTAG, "Error setting mChildFragmentManager field", e);
            }
        }
    }

    ...
}

它对我很有效,无需重新实例化片段。

【讨论】:

  • 我用这个扩展了我的 Fragments 并且像一个魅力一样工作。解决了我很久的很多问题!干得好。
  • 这应该是答案:)
  • 嗨...它对我不起作用..我的代码在这里:github.com/gbhola-bst/FragmentsTesting
  • 当我回到 pagerFragment(包含 viewPager 的片段)时,我无法获得任何视图。如果我使用你的代码,它会给出一个异常。
  • 我能够在片段之间导航而不会崩溃,但是使用后退按钮然后再次导航到它会导致崩溃。将我的所有片段更改为从此类扩展,而是解决了这个问题。所以我猜这个实现还没有被修复。
【解决方案2】:

这似乎是在

报告的错误

https://code.google.com/p/android/issues/detail?id=42601

变量

FragmentManagerImpl mChildFragmentManager;

在 Fragment.java 中,分离时未设置为 null。所以下次加载片段时,变量仍然指向最后一个父级。

正如在该线程中所讨论的,一种解决方法是重新实例化 Fragment。

在我的例子中,我在 ActionBar 选项卡中的片段之间切换。有问题的 Fragment 已经嵌套了 Fragment 并且在返回文件加载器 Fragment 时正在崩溃应用程序。所以这是解决方法代码:

class MainTabsListener implements ActionBar.TabListener {
    public Fragment fragment;
    public int TabPosition;

    public MainTabsListener(Fragment fragment, int tab_position) {
        this.fragment = fragment;
        TabPosition = tab_position;
    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        CurrentFragment = fragment;
        CurrentTabSelectedPos = TabPosition;

        /**
         * This is a work-around for Issue 42601
         * https://code.google.com/p/android/issues/detail?id=42601
         * 
         * The method getChildFragmentManager() does not clear up
         * when the Fragment is detached.
         */
        if( fragment instanceof FileLoaderFragment ){
            fragment = reinstatiateFileLoaderFragment();
        }

        ft.replace(R.id.fragment_container, fragment);

    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        ft.remove(fragment);
    }

}

【讨论】:

    【解决方案3】:

    遗憾的是,这是支持 v4 的错误,仍然存在 :(

    当您通过导航抽屉或其他类似的东西选择其他片段时,具有子片段的片段被分离。所以那些子片段的 fragmentManager(getChildFragmentManager()) 不再存在。当这些片段返回时,发生了错误。炸弹!

    很明显,支持v4应该在onDetach()中清理mChildFragmentManager,但它没有,所以我们必须依靠自己。比如片段中包含子片段的如下代码:

    @Override
        public void onDetach() {
            try {
                Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
                childFragmentManager.setAccessible(true);
                childFragmentManager.set(this, null);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            super.onDetach();
        }
    

    一切都会好起来的,祝你有美好的一天:)

    【讨论】:

      【解决方案4】:

      你的错误可能是 android.view.InflateException 吗?

      如果是这样,你应该动态地膨胀 Fragment,不要使用 XML 布局。

      并且,您不应将定义为 XML 布局的片段定位到片段事务。

      【讨论】:

      • 不,这不是错误。我确实暂时修复了它,而不是进行替换,而是隐藏它并显示它。这并不理想,因为这种方法可能会导致配置更改出现问题。
      【解决方案5】:

      我也有同样的问题。

      在一个活动中,我有 3 个按钮可以用 transaction.replace(...) 切换片段

      FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.layout_tablet_paneau, mLigneMessageFragment);

      其中一个片段包含一个带有自定义 FragmentPagerAdapter 的 ViewPage。因此,我必须执行 getChildFragmentManager(),以允许嵌套的 Fragments。

      构造函数在这里:

      public LignePagerAdapter(Fragment ligneMessageTabletFragment) {
              super(ligneMessageTabletFragment.getChildFragmentManager());
          }
      

      所以我遇到了同样的错误:这个片段的第一次显示出错了,但是当我显示其他片段并返回这个片段时,我得到了这个异常:

      02-26 11:57:50.798: D/ACRA(776): 等待 Toast + worker 结束。杀死应用程序?真的 02-26 11:57:50.798: E/AndroidRuntime(776): 致命异常: main 02-26 11:57:50.798: E/AndroidRuntime(776): java.lang.IllegalStateException: 没有活动 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1075) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1070) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1861) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.Fragment.performActivityCreated(Fragment.java:1474) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:931) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1088) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1444) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:429) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.os.Handler.handleCallback(Handler.java:587) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.os.Handler.dispatchMessage(Handler.java:92) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.os.Looper.loop(Looper.java:132) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 android.app.ActivityThread.main(ActivityThread.java:4126) 02-26 11:57:50.798: E/AndroidRuntime(776): at java.lang.reflect.Method.invokeNative(Native Method) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 java.lang.reflect.Method.invoke(Method.java:491) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844) 02-26 11:57:50.798: E/AndroidRuntime(776): 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602) 02-26 11:57:50.798: E/AndroidRuntime(776): at dalvik.system.NativeStart.main(Native Method) 02-26 11:57:52.818: I/dalvikvm(776): threadid=4: 对信号 3 做出反应 02-26 11:57:52.818: I/dalvikvm(776): 将堆栈跟踪写入“/data/anr/traces.txt”

      所以我可以重新创建它来解决问题,而不是放置相同的片段实例,但我似乎效率不高。

      transaction.replace(R.id.layout_tablet_paneau, LigneMessageTabletFragment.newInstance());
      

      【讨论】:

        【解决方案6】:

        参考@lopisan 答案:

        我长期使用他的解决方案。

        但我想有更好的方法来做到这一点!

        如果 mChildFragmentManager.mActivity 为 null,则将 mChildFragmentManager 设置为 null。当 performActivityCreated 方法时。

        @Override
        void performActivityCreated(Bundle savedInstanceState) {
            if (getFragmentManagerActivity(mChildFragmentManager) == null) {
                setChildFragmentManager(this, null);
            }
            super.performActivityCreated(savedInstanceState);
        }
        
        
        public static FragmentActivity getFragmentManagerActivity(FragmentManager fragmentManager) {
            FragmentManagerImpl fm = (FragmentManagerImpl) fragmentManager;
            return fm.mActivity;
        }
        
        
        
        private static final Field sChildFragmentManagerField;
        static {
            /**
             * BUG : causing a java.IllegalStateException error, No Activity, only
             * when navigating to Fragment for the SECOND time
             * http://stackoverflow.com /questions/15207305/getting-the-error-java-lang-illegalstateexception-activity-has-been-destroyed
             * http://stackoverflow.com/questions/14929907/causing-a-java-illegalstateexception-error-no-activity-only-when-navigating-to
             */
            Field f = null;
            try {
                f = Fragment.class.getDeclaredField("mChildFragmentManager");
                f.setAccessible(true);
            } catch (NoSuchFieldException e) {
                Log.e(TAG, "Error getting mChildFragmentManager field", e);
            }
            sChildFragmentManagerField = f;
        }
        
        public static void setChildFragmentManager(Fragment fragment, FragmentManager fragmentManager) {
            if (sChildFragmentManagerField != null) {
                try {
                    sChildFragmentManagerField.set(fragment, fragmentManager);
                } catch (Exception e) {
                    Log.e(TAG, "Error setting mChildFragmentManager field", e);
                }
            }
        }
        

        【讨论】:

          【解决方案7】:

          我在使用 BottomNavigationView 开关和替换导致应用程序崩溃的片段时遇到了同样的错误。在正常速度下它工作得非常好,如果用户快速切换它会崩溃说

          IllegalStateException 无活动

          在日志中

          在我之前

          fragmentA = new FragmentA();
          fragmentB = new FragmentB();
          fragmentC = new FragmentC();
          
          mBottomNavigationView.setOnNavigationItemSelectedListener(item -> {
                  switch (item.getItemId()) {
             case R.id.fragmentA:
                   replaceFragment(fragmentA);
             case R.id.fragmentB:
                  repalceFragment(fragmentB);
             case R.id.fragmentC:
                  repalceFragment(fragmentC);
             }
          };
          

          我认为这种片段的预先创建并使用它们进行替换会造成问题,现在我每次都重新创建它。它开始工作正常

          mBottomNavigationView.setOnNavigationItemSelectedListener(item -> {
                  switch (item.getItemId()) {
             case R.id.fragmentA:
                  fragment = new FragmentA();
                   replaceFragment(fragment);
             case R.id.fragmentB:
                  fragment = new FragmentB();
                  repalceFragment(fragment);
             case R.id.fragmentC:
                  fragment = new FragmentC();
                  repalceFragment(fragment);
             }
          };
          
           public void repalceFragment(Fragment fragment) {
              if (!this.isFinishing()) {
                  if (!fragment.isAdded()) {
                      if (!this.mDisplayedFragment.isRemoving()) {
                          getFragmentManager().beginTransaction().replace(R.id.layout_fragment_container, fragment).commit();
                          this.mDisplayedFragment = fragment;
                      }
                  }
              }
          }
          

          希望它对某些人有所帮助,我做了太多的防御性编码,但它解决了我快速切换片段的问题

          【讨论】:

            【解决方案8】:

            请检查此链接的第 32 号:

            Child FragmentManager retained after detaching parent Fragment

            步骤:

            1. Download and copy this class to your project
            2. 使用支持 v4 one 扩展每个 Fragment(最佳实践)
            3. 使用该类而不是 FragmentStatePagerAdapter

            【讨论】:

              猜你喜欢
              • 2023-02-02
              • 1970-01-01
              • 1970-01-01
              • 2018-11-21
              • 2021-04-08
              • 1970-01-01
              • 1970-01-01
              • 2021-02-18
              • 1970-01-01
              相关资源
              最近更新 更多