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(...);
是否被删除。有没有其他方法可以避免这个问题?
在生命周期中,片段事务何时完成以便我可以等待设置寻呼机?
【问题讨论】:
显然,androidViewPager
会将寻呼机内片段的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()
中删除activity
、requireActivity
或这个,保持空白。
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 已经在执行事务。提交后何时初始化寻呼机是安全的?的主要内容,如果未能解决你的问题,请参考以下文章
无法从 android.app.FragmentManager 转换为 android.support.v4.app.FragmentManager