CodeXiaoMai

CodeXiaoMai的博客


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

FragmentManager is already executing transactions 异常分析

发表于 2019-09-13 更新于 2019-12-04 分类于 Android 阅读次数:
本文字数: 17k

今天是 2019年09月13日,农历八月十五。没错今天是中秋节,而且对我来说,今年的中秋不一样🖕。

故事是这样的 ——

中秋前一天的晚上线上发现了问题,在家改bug到后半夜。中秋当天上午早上又接到线上的问题反馈,于是在家与其他几个小伙伴一起连线排查解决。经过一上午的努力,修改了几个问题,但是有两个问题没有查到原因和解决办法,于是决定下午去公司解决,还好下午狠顺利的将问题复现了,并找到了原因和解决办法。当把问题都回归验证后,已经是快晚上7点钟了。回到家不到 8 点,吃了一大碗大家下午包的给我一个人留得饺子……

扯蛋完毕,现在回到正题。

需求

用户按返回键即将退出当前页面前,需要立即请求接口判断是否显示一个 Dialog,如果展示 Dialog 则不退出页面,否则再退出页面。

模拟代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MainActivity : AppCompatActivity() {

override fun onBackPressed() {
sendRequest().observe(this@MainActivity, Observer {
if (it == true) {
Toast.makeText(this@MainActivity, "弹出Dialog", Toast.LENGTH_SHORT).show()
} else {
super.onBackPressed()
}
})
}

private fun sendRequest(): LiveData<Boolean> {
val result = MutableLiveData<Boolean>()

// 这里假设请求用时 500 ms。
Handler().postDelayed({
result.postValue(false)
}, 500L)

return result
}
}

问题

正常情况下,一个请求几百毫秒内就可以完成,但是如果在弱网环境下,当用户点击返回键后,由于长时间没有反应,此时可能用户着急了,于是按下 Home 键,应用进入后台。一段时间后网络请求成功(返回结果为不显示 Dialog)或请求超时,用户再次返回应用时,此时发生如下异常:

  • Android 8.0(不包含8.0) 以下 ANR。
  • Android 8.0+ 报以下错误,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2019-09-13 21:39:05.780 2106-2106/com.xiaomai E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.xiaomai, PID: 2106
java.lang.RuntimeException: Unable to resume activity {com.xiaomai/com.xiaomai.MainActivity}: java.lang.IllegalStateException: FragmentManager is already executing transactions
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3645)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3685)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1643)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: java.lang.IllegalStateException: FragmentManager is already executing transactions
at android.app.FragmentManagerImpl.ensureExecReady(FragmentManager.java:1987)
at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2043)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:850)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:811)
at android.app.Activity.onBackPressed(Activity.java:2993)
at androidx.fragment.app.FragmentActivity.onBackPressed(FragmentActivity.java:191)
at com.xiaomai.MainActivity.access$onBackPressed$s1136912392(MainActivity.kt:14)
at com.xiaomai.MainActivity$onBackPressed$1.onChanged(MainActivity.kt:28)
at com.xiaomai.MainActivity$onBackPressed$1.onChanged(MainActivity.kt:14)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:144)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:442)
at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:394)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:123)
at androidx.lifecycle.ReportFragment.onStart(ReportFragment.java:83)
at android.app.Fragment.performStart(Fragment.java:2637)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1312)
at android.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1549)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1611)
at android.app.FragmentManagerImpl.dispatchMoveToState(FragmentManager.java:3039)
at android.app.FragmentManagerImpl.dispatchStart(FragmentManager.java:2996)
at android.app.FragmentController.dispatchStart(FragmentController.java:189)
at android.app.Activity.performStart(Activity.java:6998)
at android.app.Activity.performRestart(Activity.java:7066)
at android.app.Activity.performResume(Activity.java:7071)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3620)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3685) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1643) 
at android.os.Handler.dispatchMessage(Handler.java:105) 
at android.os.Looper.loop(Looper.java:164) 
at android.app.ActivityThread.main(ActivityThread.java:6541) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

问题重现

为了模拟弱网环境,方便问题重现,这里把请求时间改为 3s。

1
2
3
Handler().postDelayed({
result.postValue(false)
}, 3000L)

当按下返回键后,立即按 Home 键,等待 3 秒后再次回到应用,问题重现。

原因排查

从堆栈的异常日志可以看到:异常发生的原因与 Fragment 有关,并且对 Fragment 进行了不正当的操作。

看一下代码中哪个地方用到了 Fragment 呢?仔细一行行的看过代码后,发现没有地方使用 Fragment!!! WHAT?????

这是为什么呢?代码中没有任何地方使用 Fragment,错误堆栈中却报了与 Fragment 有关的信息,再仔细看一下堆栈的异常日志,发现有一个叫 ReportFragment 的类。

原来,ReportFragment 是 Android 为了实现 Lifecycles 相关功能而自动添加的。

接下来,调试一下当应用从后台返回时都发生了什么?

1
2
3
4
5
6
7
handleResumeActivity:3685, ActivityThread (android.app)
performResumeActivity:3620, ActivityThread (android.app)
performResume:7071, Activity (android.app)
performRestart:7066, Activity (android.app)
performStart:6991, Activity (android.app)
execPendingActions:402, FragmentController (android.app)
execPendingActions:2043, FragmentManagerImpl (android.app) => 代码分析一

代码分析一(此时的 FragmentManager 为 Activity 中的 FragmentManager):

1
2
3
4
5
6
7
8
public boolean execPendingActions() {
ensureExecReady(true);

boolean didSomething = false;
...省略部分代码

return didSomething;
}

代码分析二(此时 FragmentManager 仍为 Activity 中的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void ensureExecReady(boolean allowStateLoss) {
if (mExecutingActions) {
// 这正是堆栈日志中抛出的异常信息
throw new IllegalStateException("FragmentManager is already executing transactions");
}

if (Looper.myLooper() != mHost.getHandler().getLooper()) {
throw new IllegalStateException("Must be called from main thread of fragment host");
}

... 省略部分代码
mExecutingActions = true;
try {
executePostponedTransaction(null, null);
} finally {
mExecutingActions = false;
}
}

虽然这里找到了异常信息,但是这时并没有抛出异常。

接下来的运行如下:

1
2
3
dispatchStart:189, FragmentController (android.app)
dispatchStart:2996, FragmentManagerImpl (android.app)
dispatchMoveToState:3039, FragmentManagerImpl (android.app) => 代码分析三

代码分析三(此时 FragmentManager 仍为 Activity 中的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void dispatchMoveToState(int state) {
if (mAllowOldReentrantBehavior) {
moveToState(state, false);
} else {
try {
mExecutingActions = true;
// 见代码分析四
moveToState(state, false);
} finally {
mExecutingActions = false;
}
}
execPendingActions();
}

这里要注意的是 mExecutingActions = true;然后就进入了 moveToState 方法。

代码分析四(此时 FragmentManager 仍为 Activity 中的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void moveToState(int newState, boolean always) {
... 省略部分代码
// Now iterate through all active fragments. These will include those that are removed
// and detached.
final int numActive = mActive.size();
for (int i = 0; i < numActive; i++) {
// 此时 mActivie 中只有一个且为 ReportFragment
Fragment f = mActive.valueAt(i);
if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
// 见代码分析五
moveFragmentToExpectedState(f);
...
}
}
... 省略部分代码
}

代码分析五(此时 FragmentManager 为 Activity 中的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void moveFragmentToExpectedState(final Fragment f) {
...
moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false);
...
}

@SuppressWarnings("ReferenceEquality")
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {
...
switch (f.mState) {
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
// 见代码分析六
f.performStart();
dispatchOnFragmentStarted(f, false);
}
...
}
...
}

代码分析六(此时进入 Fragment):

1
2
3
4
5
6
7
8
9
10
11
public class Fragment {

void performStart() {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
// 再次回到代码分析一
mChildFragmentManager.execPendingActions();
}
...
}
}

虽然这里再次进入代码分析一,但这时的 FragmentManager 与前面的 FragmentManager 已经不是同一个了,之前是 Activity 中的而现在是 ReportFragment 中的 childFragmentManager,并且 Activity 的栈帧仍然处在代码分析三的 moveToState() 方法中,mExecutingActions 仍然为 true。

代码分析一(此时的 FragmentManager 为 ReportFragment 中的 childFragmentManager):

1
2
3
4
5
6
7
8
public boolean execPendingActions() {
ensureExecReady(true);

boolean didSomething = false;
...省略部分代码

return didSomething;
}

代码分析二(此时的 FragmentManager 为 ReportFragment 中的 childFragmentManager):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void ensureExecReady(boolean allowStateLoss) {
if (mExecutingActions) {
// 这正是堆栈日志中抛出的异常信息
throw new IllegalStateException("FragmentManager is already executing transactions");
}

if (Looper.myLooper() != mHost.getHandler().getLooper()) {
throw new IllegalStateException("Must be called from main thread of fragment host");
}

... 省略部分代码
mExecutingActions = true;
try {
executePostponedTransaction(null, null);
} finally {
mExecutingActions = false;
}
}

这时仍然没有抛出异常。

接下来的运行仍然如下:

1
2
3
dispatchStart:189, FragmentController (android.app)
dispatchStart:2996, FragmentManagerImpl (android.app)
dispatchMoveToState:3039, FragmentManagerImpl (android.app) => 代码分析三

代码分析三(此时的 FragmentManager 为 ReportFragment 中的 childFragmentManager):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void dispatchMoveToState(int state) {
if (mAllowOldReentrantBehavior) {
moveToState(state, false);
} else {
try {
mExecutingActions = true;
// 见代码分析四
moveToState(state, false);
} finally {
mExecutingActions = false;
}
}
execPendingActions();
}

这里仍然要注意的是 mExecutingActions = true;然后就进入了 moveToState 方法。

代码分析四(此时的 FragmentManager 为 ReportFragment 中的 childFragmentManager):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void moveToState(int newState, boolean always) {
... 省略部分代码
// Now iterate through all active fragments. These will include those that are removed
// and detached.
final int numActive = mActive.size();

// 这时 numActive = 0,所以不会进入 for 循环。
for (int i = 0; i < numActive; i++) {
Fragment f = mActive.valueAt(i);
if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
moveFragmentToExpectedState(f);
...
}
}
... 省略部分代码
}

因为这里不会进入 for 循环,所以方法运行结束后,会进入代码分析三中 finally 代码块中,将 mExecutingActions 赋值为 false;这时方法执行完毕,回到代码分析六,继续向下执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Fragment {

void performStart() {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
mChildFragmentManager.execPendingActions(); // 执行完毕
}
mState = STARTED;
mCalled = false;
// 见代码分析七
onStart();
}
}

代码分析七:

1
2
3
4
5
6
7
8
public class ReportFragment extends Fragment {
@Override
public void onStart() {
super.onStart();
dispatchStart(mProcessListener);
dispatch(Lifecycle.Event.ON_START);
}
}

在 ReportFragment 的 onStart() 方法,会对 OnStart 事件进行分发,之后 LiveData 在 MainActivity 中等到响应:

1
2
3
4
5
6
7
8
9
10
override fun onBackPressed() {
sendRequest().observe(this@MainActivity, Observer {
if (it == true) {
// 因为返回的结果是 true,所以执行此方法。见代码分析八
super.onBackPressed()
} else {
Toast.makeText(this@MainActivity, "弹出Dialog", Toast.LENGTH_SHORT).show()
}
})
}

代码分析八:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FragmentActivity {
public void onBackPressed() {
// 这时的 fragmentManger 又是一个新的 FragmentManger,它属于 FragmentActivity。
FragmentManager fragmentManager = mFragments.getSupportFragmentManager();
final boolean isStateSaved = fragmentManager.isStateSaved();
if (isStateSaved && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
// Older versions will throw an exception from the framework
// FragmentManager.popBackStackImmediate(), so we'll just
// return here. The Activity is likely already on its way out
// since the fragmentManager has already been saved.
return;
}
if (isStateSaved || !fragmentManager.popBackStackImmediate()) {
// 代码分析九
super.onBackPressed();
}
}
}

代码分析九:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Activity {
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}

// 这个 fragmentManager 又变成了 Activity 中的 FragmentManager。
FragmentManager fragmentManager = mFragments.getFragmentManager();

// 见代码分析十
if (fragmentManager.isStateSaved() || !fragmentManager.popBackStackImmediate()) {
finishAfterTransition();
}
}
}

代码分析十(此时的 FragmentManager 为 Activity 中的 FragmentManager):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public boolean popBackStackImmediate() {
checkStateLoss();
return popBackStackImmediate(null, -1, 0);
}

private boolean popBackStackImmediate(String name, int id, int flags) {
// 再次进入代码分析一,此时的 fragmentManager 与第一次分析中的是同一个
execPendingActions();
ensureExecReady(true);

... 省略部分代码
return executePop;
}

我们再次进入到代码分析二中:

1
2
3
4
5
6
private void ensureExecReady(boolean allowStateLoss) {
if (mExecutingActions) {
// 这正是堆栈日志中抛出的异常信息
throw new IllegalStateException("FragmentManager is already executing transactions");
}
}

因为前面在代码分析三中我们强调过,Activity 中的 mExecutingActions 为 true 后,就再也没有被修改为 false,所以这里抛出了异常。

至此,问题的原因已经找到了,是由于 Activity 中的 FragmentManager 的第一次任务还没有执行完毕,其他的操作又导致它需要进行第二次任务,所以发生错误。

解决方案

问题的原因已经找到了,所以当第一次任务没有执行结束时,如果有第二个任务到来,我们可以从两个方面去解决问题:

直接丢弃第二个任务

在页面不可见时,取消对 LiveData 的监听,这样页面重新可见时,就不会接收到变化的通知了,修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class MainActivity : AppCompatActivity() {

private val observer = Observer<Boolean> {
if (it == true) {
super.onBackPressed()
} else {
Toast.makeText(this@MainActivity, "弹出Dialog", Toast.LENGTH_SHORT).show()
}
}

private var result: LiveData<Boolean>? = null

override fun onBackPressed() {
result = sendRequest()
result?.observe(this@MainActivity, observer)
}

override fun onStop() {
super.onStop()
result?.removeObserver(observer)
}

private fun sendRequest(): LiveData<Boolean> {
val result = MutableLiveData<Boolean>()

Handler().postDelayed({
result.postValue(true)
}, 3000L)

return result
}
}

等待第一个任务执行完毕后再执行第二个任务

通过 Handler.post() 方式,将任务滞后完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MainActivity : AppCompatActivity() {

override fun onBackPressed() {
sendRequest().observe(this@MainActivity, Observer {
if (it == true) {
Handler().post {
super.onBackPressed()
}
} else {
Toast.makeText(this@MainActivity, "弹出Dialog", Toast.LENGTH_SHORT).show()
}
})
}

private fun sendRequest(): LiveData<Boolean> {
val result = MutableLiveData<Boolean>()

Handler().postDelayed({
result.postValue(true)
}, 3000L)

return result
}
}
# Fragment
刘海屏适配
Handler消息机制源码解析
  • 文章目录
  • 站点概览
CodeXiaoMai

CodeXiaoMai

CodeXiaoMai的博客
12 日志
6 分类
19 标签
GitHub E-Mail
  1. 1. 需求
  2. 2. 模拟代码
  3. 3. 问题
  4. 4. 问题重现
  5. 5. 原因排查
  6. 6. 解决方案
    1. 6.1. 直接丢弃第二个任务
    2. 6.2. 等待第一个任务执行完毕后再执行第二个任务
© 2019 CodeXiaoMai
|