Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

换肤引发的血案(所有系统自动重建fragment相关) #84

Open
soapgu opened this issue Oct 29, 2021 · 0 comments
Open

换肤引发的血案(所有系统自动重建fragment相关) #84

soapgu opened this issue Oct 29, 2021 · 0 comments
Labels
problem problem or trouble 安卓 安卓

Comments

@soapgu
Copy link
Owner

soapgu commented Oct 29, 2021

  • 前言

软件改完以后,老规矩先烤机个几天看看有没有啥问题。今天一上班一看竟然退了。
一看日志

1635332400351,2021.10.27 19:00:00.351,ERROR,shgbitgate,----------UncaughtException throw--------- : 
java.lang.RuntimeException: Unable to destroy activity {com.smartvision.webmeeting/com.space365.app.gate.shgbit.MainActivity}: 
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState <br> 	
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4204) <br> 	
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4222) <br> 	
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4496) <br> 	
at android.app.ActivityThread.-wrap19(ActivityThread.java) <br> 	
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1484) <br> 	
at android.os.Handler.dispatchMessage(Handler.java:102) <br> 	
at android.os.Looper.loop(Looper.java:154) <br> 	
at android.app.ActivityThread.main(ActivityThread.java:6121) <br> 	
at java.lang.reflect.Method.invoke(Native Method) <br> 	
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912) <br> 	
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802) 
<br> Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState <br> 	
at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1844) <br> 	
at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1884) <br> 	
at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:329) <br> 	
at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:294) <br> 	
at androidx.fragment.app.DialogFragment.dismissInternal(DialogFragment.java:355) <br> 	
at androidx.fragment.app.DialogFragment.dismiss(DialogFragment.java:307) <br> 	
at com.space365.gb25.fragment.FaceDialogFragment.onDestroy(FaceDialogFragment.java:67) <br> 	
at androidx.fragment.app.Fragment.performDestroy(Fragment.java:3219) <br> 	
at androidx.fragment.app.FragmentStateManager.destroy(FragmentStateManager.java:774) <br> 	
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:350) <br> 	
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112) <br> 	
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1647) <br> 	
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3126) <br> 	
at androidx.fragment.app.FragmentManager.dispatchDestroy(FragmentManager.java:3105) <br> 	
at androidx.fragment.app.Fragment.performDestroy(Fragment.java:3214) <br> 	
at androidx.fragment.app.FragmentStateManager.destroy(FragmentStateManager.java:774) <br> 	
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:350) <br> 	
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112) <br> 	
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1647) <br> 	
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3126) <br> 	
at androidx.fragment.app.FragmentManager.dispatchDestroy(FragmentManager.java:3105) <br> 	
at androidx.fragment.app.Fragment.performDestroy(Fragment.java:3214) <br> 	
at androidx.fragment.app.FragmentStateManager.destroy(FragmentStateManager.java:774) <br> 	
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:350) <br> 	
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112) <br> 	
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1647) <br> 	
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3126) <br> 	
at androidx.fragment.app.FragmentManager.dispatchDestroy(FragmentManager.java:3105) <br> 	
at androidx.fragment.app.Fragment.performDestroy(Fragment.java:3214) <br> 	
at androidx.fragment.app.FragmentStateManager.destroy(FragmentStateManager.java:774) <br> 	
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:350) <br> 	
at androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete(SpecialEffectsController.java:742) <br> 	
at androidx.fragment.app.SpecialEffectsController$Operation.cancel(SpecialEffectsController.java:594) <br> 	
at androidx.fragment.app.SpecialEffectsController.forceCompleteAllOperations(SpecialEffectsController.java:329) <br> 	
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3130) <br> 	
at androidx.fragment.app.FragmentManager.dispatchDestroy(FragmentManager.java:3105) <br> 	
at androidx.fragment.app.FragmentController.dispatchDestroy(FragmentController.java:334) <br> 	
at androidx.fragment.app.FragmentActivity.onDestroy(FragmentActivity.java:330) <br> 	
at androidx.appcompat.app.AppCompatActivity.onDestroy(AppCompatActivity.java:242) <br> 	
at android.app.Activity.performDestroy(Activity.java:6911) <br> 	
at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1153) <br> 	
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4191) <br> 	... 10 more <br> 

  • 问题一:不要在DialogFragment的onDestroy回调里面调用dismiss

问题很快定位
通过堆栈锁定在onDestroy回调中调用了dismiss,分析最后几个调用发现是dismiss会执行commit,也就是保持当前状态,而
onSaveInstanceState已经先执行过了。也就是说DialogFragment的时序执行非法了,比较好理解。

  • 附加问题?为啥以前我们调用的时候确没有发生崩溃,而这次崩溃了那?

这是一开始就会提出的疑问。
查看源代码是这样的结论
平时我们都是调用dismiss以后最终会调用onDestroy回调,而onDestroy再调dismiss最终会执行下面代码

private void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss) {
        if (mDismissed) {
            return;
        }
       //...
    }

也就是说检测到重复调用了,其实dismiss并没有生效

而更换了夜间模式其实就是修改了配置变更

某些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性,以及当用户启用多窗口模式时)。发生这种变化时,Android 会重启正在运行的 Activity(先后调用 onDestroy() 和 onCreate())。重启行为旨在通过利用与新设备配置相匹配的备用资源来自动重新加载您的应用,从而帮助它适应新配置。

重新温故而知新一下前面的知识点

重建包含保存恢复
一共有两种情况会引发

  1. 用户期望 Activity 的界面状态在整个配置变更(例如旋转或切换到多窗口模式)期间保持不变
  2. 当 Activity 因系统限制而被销毁时

我们是属于前者

Fragment作为小号Activity本质是一样的

最后再回顾下ViewModel,它是唯一有特权躲过被周期的对象,这里不详细表述了。同学们可以看图感受下
图片

  • 问题二:could not find Fragment constructor

图片
刚改完,又崩了。
could not find Fragment constructor是什么鬼,我怎么可能会没有构造。
后来领悟了,我没有声明默认构造,系统重建Fragment调用的是默认构造

  • 问题三:不要在重建过程中产生多余的界面

加上默认构造以后,崩溃是没有了。但是再仔细检查日志以后
发现几乎所有的UI(Fragment)都被销毁了两次创建了两次。
经过反复调试检查后发现是原来的代码写得还是考虑不周到

悲剧是这样来的,我在ViewCreated回调里面去写了加载子Fragment的操作。所以这个生命周期就变成了

  1. 父Fragment创建
  2. 子Fragment创建
  3. 变更配置
  4. 子Fragment销毁
  5. 父Fragment销毁
  6. 父Fragment重建(系统)
  7. 子Fragment重建(系统)
  8. 子Fragment创建 (人为错误代码)
  9. 子Fragment销毁(系统的Fragment被顶掉了,因为我用的是replace)
  • 备注:在日志中加入当前Fragment的hashCode,方便配对找出谁是谁

虽然最终程序是正常的,但是产生毫无必要的浪费。

当然viewModel全程没有挪地方,安静地在旁边吃瓜
图片

代码需要修改下

@Override
    public void onViewCreated(@NonNull @NotNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if( savedInstanceState == null ) {
            setFragment(pictureProvider.get(), R.id.contentContainer);
            setFragment(placeProvider.get(), R.id.place);
        }
    }

相似代码还要改一大堆😭

完,最后含泪总结。看似不起眼的基本功还是太重要,很多奇奇怪怪的问题还是基础问题。最近安卓手相对没这么热了,调试反应慢了一点了。

@soapgu soapgu added JAVA This doesn't seem right problem problem or trouble 安卓 安卓 and removed JAVA This doesn't seem right labels Oct 30, 2021
@soapgu soapgu changed the title 换肤引发的血案(所有系统自动关闭fragment相关) 换肤引发的血案(所有系统自动重建fragment相关) Oct 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
problem problem or trouble 安卓 安卓
Projects
None yet
Development

No branches or pull requests

1 participant