FragmentManager 已经在执行事务。提交后何时初始化寻呼机是安全的?

Posted

技术标签:

【中文标题】FragmentManager 已经在执行事务。提交后何时初始化寻呼机是安全的?【英文标题】:FragmentManager is already executing transactions. When is it safe to initialise pager after commit? 【发布时间】:2016-12-07 22:04:25 【问题描述】:

我有一个托管两个片段的活动。该活动在加载对象时开始显示加载程序。然后通过 newInstance 方法将加载的对象作为参数传递给两个片段,并附加这些片段。

final FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.container1, Fragment1.newInstance(loadedObject));
trans.replace(R.id.container2, Fragment2.newInstance(loadedObject));
trans.commit();

第二个片段包含一个 android.support.v4.view.ViewPager 和选项卡。 onResume 我们像下面这样初始化它

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(adapter.getCount()); //the count is always < 4
tabLayout.setupWithViewPager(viewPager);

问题是android然后抛出

java.lang.IllegalStateException: FragmentManager 已经在执行 交易

有了这个堆栈跟踪:(为了简洁起见,我从包名中取出了android.support

v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:1620) 在 v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:637) 在 v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:143) 在 v4.view.ViewPager.populate(ViewPager.java:1235) 在 v4.view.ViewPager.populate(ViewPager.java:1083) 在 v4.view.ViewPager.setOffscreenPageLimit(ViewPager.java:847)

数据显示setOffscreenPageLimit(...); 是否被删除。有没有其他方法可以避免这个问题?

在生命周期中,片段事务何时完成以便我可以等待设置寻呼机?

【问题讨论】:

显然,android ViewPager 会将寻呼机内片段的onCreateView 内的NullPointerException 提升为IllegalStateException : FragmentManager is already executing transactions ...以防万一有人路过并需要知道 【参考方案1】:

只需在Fragment 中使用childFragmentManger() 来代替viewpager

mPagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager());
mPager.setAdapter(mPagerAdapter);

【讨论】:

老兄!你救了我的一天!! :D 非常感谢!我使用的是嵌套片段,而您的“getChildFragmentManager”技巧就像魅力一样! ^_^ 这适用于“返回”(通过返回按钮)到活动/片段组合也崩溃的情况。好电话。 简单而出色的解决方案,您节省了我的时间,谢谢。 谢谢!!当我使用 Apply Changes 和 Restart Activity 作为运行按钮时,我的应用程序崩溃了。 如果您使用嵌套片段并重用相同的片段管理器,这是正确的解决方案。【参考方案2】:

我在快速替换 2 个片段并使用 executePendingTransactions() 时遇到了这个异常。不调用这个也不例外。

我的情况是什么? 我打开一个片段 A 并在其onResume() 中(在某种条件下)我要求活动用片段 B 替换片段。此时发生异常。

我的解决方案是使用Handler.post(runnable),它将查询放在线程队列的末尾,而不是立即运行它。这样,我们可以确保在任何先前的交易完成后执行新的交易。

所以我的解决方案很简单:

Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(new Runnable()

    @Override
    public void run()
    
        openFragmentB(position);
    
);

【讨论】:

简单有效。我虽然使用过 postDelayed 。在我的用例中它甚至更好,因为初始化阶段正在进行很多事情 这行得通,但我想知道这如何在内部解决问题? @David 它会将任务发布到主线程的队列中,而不是试图强行打开片段。所以队列会一一处理fragment commit。 感谢有效的解决方案,Handler() 已弃用,您可以改用Handler(Looper.getMainLooper()) 你是对的,我更新了答案。【参考方案3】:

如果您的目标是 sdk 24 及更高版本,您可以使用:

FragmentTransaction.commitNow()

而不是commit()

如果您针对的是旧版本,请尝试调用:

FragmentManager.executePendingTransactions()

拨打commit()之后

【讨论】:

FragmentManager.executePendingTransactions() 也会导致异常,因为在其代码中调用了 execPendingActions() --> ensureExecReady(true) --> if (mExecutingActions) throw new IllegalStateException( "FragmentManager 已经在执行事务"); 这对我不起作用。这两种方法都显示了崩溃:java.lang.IllegalStateException: FragmentManager 已经在执行事务 commitNow 以及 addToBackStack 的使用也失败了:( 任何使用它的人都应该知道它是一种阻塞方法,在 UI 线程中运行它会使您的应用程序在转换时冻结。 M.Paunov 和 Hitesh Sahu 的答案对于生产就绪型应用程序来说是可靠的。【参考方案4】:

我有类似的问题,

A mainAcitivity 添加fragmentA。

然后fragmentA回调mainactivity用fragmentB替换自己。

用fragmentB替换并提交事务时,MainActivity抛出异常fragmentmanager已经在执行事务

问题实际上来自fragmentB。

我在片段 B 中有一个 TabHost,需要 getfragmentmanager() 来添加标签片段。

将片段B中的getfragmentmanager()替换为getchildfragmentmanager()解决问题。

【讨论】:

【参考方案5】:

我有同样的问题。但是,我使用的 FragmentStateAdapter 构造函数只获取 FragmentActivity:

package androidx.viewpager2.adapter;

public FragmentStateAdapter(
    @NonNull FragmentActivity fragmentActivity
) 
    this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());

即使它从fragmentActivity 实例中获取生命周期,它也根本不起作用。

然后,深入研究FragmentStateAdapter 的代码,我看到有另一个构造函数可以传递生命周期实例。

package androidx.viewpager2.adapter;

public FragmentStateAdapter(
    @NonNull FragmentManager fragmentManager,
    @NonNull Lifecycle lifecycle
) 
    mFragmentManager = fragmentManager;
    mLifecycle = lifecycle;
    super.setHasStableIds(true);

所以,我更改了我的 PageAdapter 构造函数以接收 FragmentManager 作为 childFragmentManager 和来自 viewLifecycleOwner 的 LifeCycle。并将这些参数传递给FragmentStateAdapter 构造函数:

class MyPagerAdapter(
    fragmentManager: FragmentManager,
    lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) 
  //...

然后,当调用我的 PagerAdapter 构造函数时,我传递了 FragmentManager 和 我从viewLifecycleOwner 获得生命周期:

val myPagerAdapter = MyPagerAdapter(
    fragmentManager = childFragmentManager,
    lifecycle = viewLifecycleOwner.lifecycle
)

[更新] MyPagerAdapter 是从 Fragment 设置的。

【讨论】:

有完全相同的问题(大约在同一时间)。永远不会想到阅读其他答案。谢谢你:) viewLifecycleOwner 在 Ativity 中未找到。任何想法 @PankajKumar 在调用 viewLifecycleOwner 时,基本上是从 androidx.fragment.app.Fragment 调用 getViewLifecycleOwner() 方法。在这种情况下(我不确定 - 但值得一试)您可以尝试致电 supportFragmentManager | getSupportFragmentManager() 获取 FragmentManager 并调用生命周期 |从活动中获取生命周期()。 像魅力一样工作,谢谢!【参考方案6】:

ViewPager2

这是 ViewPager 时代的一个相当古老的答案,并帮助许多人解决了遇到此问题的问题。以防万一您遇到ViewPager2 并面临类似问题,

我们知道当我们在片段中有ViewPager2 并且FragmentStateAdapter 类有一个带有Fragment 参数的构造函数时会发生这种情况,因此您可以使用它来创建您的适配器。

喜欢,

class ScreenSlidePagerAdapter(fragment: Fragment): FragmentStateAdapter(fragment)

那么,

val pagerAdapter = ScreenSlidePagerAdapter(this)

【讨论】:

【参考方案7】:

遇到了一个与问题无关的类似错误,但在使用 firebase 时它可以帮助某人。 从.addSnapshotListener() 中删除activityrequireActivity 或这个,保持空白。

 videosListener = videosCollectionRef
            .addSnapshotListener()  snapshot, exception ->

                if (exception != null) 
                    Log.e("Exception", "Could not retrieve documents: $exception")
                

                if (snapshot != null) 
                    parseData(snapshot)
                
            

【讨论】:

谢谢,这很有帮助,但现在我只能打开一次片段,如果我回到它,它就会崩溃。 @AbhishekAN 我需要查看更多代码以帮助您获得更多帮助。希望你解决你的问题伙伴。【参考方案8】:

如果有人使用 Robolectric >3.6 并且至少通过 4.0.2 和 ViewPager,即使使用正确的代码,您也可能会看到这一点。

this github issue有相关信息跟踪Robolectric的问题。

在我写这个答案时问题没有解决,唯一已知的解决方法是使用 @Config(sdk=27) 这似乎对某些人有用但对我不起作用,或者使用 ViewPagerShadow 实现在您的测试包中使用 github 上引用的相当长的代码解决方法(如果这样更好,我可以在此处包含它,但这可能不够相关作为答案?)并使用 @Config(shadows=ShadowViewPager.class)

【讨论】:

【参考方案9】:

我使用/扩展了我的适配器 FragmentStatePagerAdapter 而不是 FragmentPagerAdapter 这解决了我的问题。

如果片段是动态的并且在运行时经常更改,请使用FragmentStatePagerAdapter

如果片段是静态的并且在运行时不经常更改,请使用FragmentPagerAdapter

从这篇文章中得到启发, https://medium.com/inloopx/adventures-with-fragmentstatepageradapter-4f56a643f8e0

【讨论】:

【参考方案10】:

我有同样的问题。通过 add 方法从一个片段导航到另一个片段。问题是我在onViewCreadted 方法中添加了片段转换。然后我尝试在onResume 方法中移动过渡。这也无济于事。所以我在简历后半秒添加了过渡。之后一切都很好。

这是一个小小的迷恋,影响了我不到一半的用户。不过,这很烦人。

【讨论】:

【参考方案11】:

只是不要打电话 FragmentTransaction#commit() 来自在另一个事务进程中已经在同一个FragmentManager 中的片段

例如:

活动:

 override fun onCreate(savedInstanceState: Bundle?) 
     val fragment = MyFragment()
     setFragment(fragment)
    


    fun setFragment(fragment: Fragment)
        supportFragmentManager.beginTransaction()
            .replace(...,fragment, ...)
            .commit()    
     

我的片段:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
        super.onViewCreated(view, savedInstanceState)
/*
*  here error will occurs, because the fragment MyFragment is in current transaction
*/
activity?.setFragment(AnotherFragment())//error

解决方案:

这样做:

活动:

 override fun onCreate(savedInstanceState: Bundle?) 
    
     setFragment(MyFragment())
    ...
    setFragment(AnotherFragment())
    


    fun setFragment(fragment: Fragment)
        supportFragmentManager.beginTransaction()
            .replace(...,fragment, ...)
            .commit()    
     

【讨论】:

以上是关于FragmentManager 已经在执行事务。提交后何时初始化寻呼机是安全的?的主要内容,如果未能解决你的问题,请参考以下文章

spring_05事务管理

无法从 android.app.FragmentManager 转换为 android.support.v4.app.FragmentManager

事务控制语言(TCL)

深入分布式事务

事务的四个基本特性

通过 findFragmentByTag 从 FragmentManager 获取 DialogFragment