仅在第二次导航到 Fragment 时导致 java.IllegalStateException 错误,无活动

Posted

技术标签:

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

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

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

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

这是奇怪的部分:

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

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

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

谢谢你, 大卫

【问题讨论】:

请发布完整的 logcat 输出 究竟什么是“无活动”错误?就像在片段中没有附加到一个活动?导致错误的代码行是什么? 【参考方案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://***.com /questions/15207305/getting-the-error-java-lang-illegalstateexception-activity-has-been-destroyed
     * http://***.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

步骤:

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

【讨论】:

以上是关于仅在第二次导航到 Fragment 时导致 java.IllegalStateException 错误,无活动的主要内容,如果未能解决你的问题,请参考以下文章

UIButton 切换标题。仅在第二次单击后更改

Jquery 仅在第二次点击时验证提交表单

UseState 仅在第二次单击时显示

Objective-C:UIDatePicker UIControlEventValueChanged 仅在第二次选择时触发

jQuery - 更改 iframe src 时仅在第二次单击时触发

IOS模拟器问题应用程序仅在第二次启动时运行..