横竖屏切换

Posted by OOFTF Blog on June 17, 2021

结论

  1. 默认情况下横竖屏切换时,Activity 会销毁并重新创建
    • 如果想保留某个对象,可以重写onRetainCustomNonConfigurationInstance返回要保存的对象,然后再新的Activity中通过getLastNonConfigurationInstance方法获取到这个对象,
    • 也可以通过 ViewModel 来保存要保留的对象,因为 ViewModel 会通过 onRetainNonConfigurationInstance 机制保留对象
  2. 添加 android:configChanges=”keyboardHidden orientation screenSize” 横竖屏切换只会调用 Activity.onConfigurationChanged 方法
  3. 横屏状态下,旋转 180 不会调用 Activity.onConfigurationChanged,可是采用 activity.window.decorView.addOnLayoutChangeListener 方式监听

    默认情况下横竖屏切换的起点是

    ActivityThread.handleRelaunchActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    @Override
    public void handleRelaunchActivity(ActivityClientRecord tmp,
         PendingTransactionActions pendingActions) {
     Configuration changedConfig = null;
     int configChanges = 0;
     synchronized (mResourcesManager) {
         if (mPendingConfiguration != null) {
             changedConfig = mPendingConfiguration;
             mPendingConfiguration = null;
         }
     }
     if (changedConfig != null) {
         handleConfigurationChanged(changedConfig, null);// 最终可能会调用 activity.onConfigurationChanged(configToReport); 默认情况下 Configuration.diffPublicOnly 计算没有修改,所以不会触发  onConfigurationChanged
     }
     ActivityClientRecord r = mActivities.get(tmp.token);
     r.activity.mConfigChangeFlags |= configChanges;
     r.mPreserveWindow = tmp.mPreserveWindow;
     r.activity.mChangingConfigurations = true;
    
     handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
             pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
    }
    

    ActivityThread.handleRelaunchActivityInner

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
     /**
     * 从代码可以了解到内部执行了 performPauseActivity callActivityOnStop handleDestroyActivity 等方法,所以 Activity 会走一遍销毁的生命周期;
     * 最后会调用 handleLaunchActivity 方法重新创建一个Activity,走一遍启动 Activity 的生命周期 
     */
      private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
             PendingTransactionActions pendingActions, boolean startsNotResumed,
             Configuration overrideConfig, String reason) {
         final Intent customIntent = r.activity.mIntent;
         if (!r.paused) {
             performPauseActivity(r, false, reason, null /* pendingActions */);
         }
         if (!r.stopped) {
             callActivityOnStop(r, true /* saveState */, reason);
         }
    
         handleDestroyActivity(r.token, false, configChanges, true, reason);
    
         r.startsNotResumed = startsNotResumed;
         r.overrideConfig = overrideConfig;
    
         handleLaunchActivity(r, pendingActions, customIntent);
     }
    

    ActivityThread.handleDestroyActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     @Override
     public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
             boolean getNonConfigInstance, String reason) {
         // destroy activity        
         ActivityClientRecord r = performDestroyActivity(token, finishing,
                 configChanges, getNonConfigInstance, reason);
                   WindowManager wm = r.activity.getWindowManager();
         wm.removeViewImmediate(v);// 这个地方最终会调用 Activity.onDetachedFromWindow
         ActivityTaskManager.getService().activityDestroyed(token); 
     }
    

    ActivityThread.performDestroyActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
             int configChanges, boolean getNonConfigInstance, String reason) {
         ActivityClientRecord r = mActivities.get(token);
         Class<? extends Activity> activityClass = null;
          r.activity.mFinished = true;
          // 如果是横竖屏切换 会调用 retainNonConfigurationInstances 获取需要保留的数据
             if (getNonConfigInstance) {
                  r.lastNonConfigurationInstances
                             = r.activity.retainNonConfigurationInstances();
             }
             mInstrumentation.callActivityOnDestroy(r.activity); // 调用 Acitvity.OnDestroy
             r.setState(ON_DESTROY);
         synchronized (mResourcesManager) {
             mActivities.remove(token);
         }
         return r;
     }
    

Activity 方法调用堆栈

竖屏切换成横屏

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
⇢ onPause[]
⇠ onPause[0ms]="void"
⇢ onStop[]
⇠ onStop[0ms]="void"
⇢ onSaveInstanceState[outState="Bundle[{}]"]
⇠ onSaveInstanceState[0ms]="void"
⇢ onRetainCustomNonConfigurationInstance[]
⇠ onRetainCustomNonConfigurationInstance[0ms]="null"
⇢ isChangingConfigurations[]
⇠ isChangingConfigurations[0ms]="true"
⇢ onDestroy[]
⇠ onDestroy[0ms]="void"
⇢ onDetachedFromWindow[]
⇠ onDetachedFromWindow[0ms]="void"
⇢ <init>[]
⇠ <init>[1ms]="void"
⇢ onCreate[savedInstanceState="Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@b8c12cc, 2131230768=androidx.appcompat.widget.Toolbar$SavedState@e265915, 2131230770=android.view.AbsSavedState$1@b8c12cc, 2131230776=android.view.AbsSavedState$1@b8c12cc, 2131230839=android.view.AbsSavedState$1@b8c12cc, 2131230977=android.view.AbsSavedState$1@b8c12cc}}], androidx.lifecycle.BundlableSavedStateRegistry.key=Bundle[{}], android:lastAutofillId=1073741823, android:fragments=android.app.FragmentManagerState@b6842a}]"]
⇢ getLastNonConfigurationInstance[]
⇠ getLastNonConfigurationInstance[0ms]="androidx.activity.ComponentActivity$NonConfigurationInstances@310b8f6"
⇠ onCreate[39ms]="void"
⇢ onStart[]
⇠ onStart[0ms]="void"
⇢ onRestoreInstanceState[savedInstanceState="Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@b8c12cc, 2131230768=androidx.appcompat.widget.Toolbar$SavedState@e265915, 2131230770=android.view.AbsSavedState$1@b8c12cc, 2131230776=android.view.AbsSavedState$1@b8c12cc, 2131230839=android.view.AbsSavedState$1@b8c12cc, 2131230977=android.view.AbsSavedState$1@b8c12cc}}], androidx.lifecycle.BundlableSavedStateRegistry.key=Bundle[{}], android:lastAutofillId=1073741823, android:fragments=android.app.FragmentManagerState@b6842a}]"]
⇠ onRestoreInstanceState[0ms]="void"
⇢ onResume[]
⇠ onResume[2ms]="void"
⇢ onAttachedToWindow[]
⇠ onAttachedToWindow[0ms]="void"
⇢ onWindowFocusChanged[hasFocus="true"]
⇠ onWindowFocusChanged[0ms]="void"

横屏切换成竖屏

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
⇢ onPause[]
⇠ onPause[1ms]="void"
⇢ onStop[]
⇠ onStop[0ms]="void"
⇢ onSaveInstanceState[outState="Bundle[{}]"]
⇠ onSaveInstanceState[0ms]="void"
⇢ onRetainCustomNonConfigurationInstance[]
⇠ onRetainCustomNonConfigurationInstance[0ms]="null"
⇢ isChangingConfigurations[]
⇢ onDetachedFromWindow[]
⇠ onDetachedFromWindow[0ms]="void"
⇢ <init>[]
⇠ <init>[0ms]="void"
⇢ onCreate[savedInstanceState="Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@b8c12cc, 2131230768=androidx.appcompat.widget.Toolbar$SavedState@776b8c, 2131230770=android.view.AbsSavedState$1@b8c12cc, 2131230776=android.view.AbsSavedState$1@b8c12cc, 2131230839=android.view.AbsSavedState$1@b8c12cc, 2131230977=android.view.AbsSavedState$1@b8c12cc}}], androidx.lifecycle.BundlableSavedStateRegistry.key=Bundle[{}], android:lastAutofillId=1073741823, android:fragments=android.app.FragmentManagerState@fe5a2d5}]"]
⇢ getLastNonConfigurationInstance[]
⇠ getLastNonConfigurationInstance[0ms]="androidx.activity.ComponentActivity$NonConfigurationInstances@76a9751"
⇠ onCreate[34ms]="void"
⇢ onStart[]
⇠ onStart[1ms]="void"
⇢ onRestoreInstanceState[savedInstanceState="Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@b8c12cc, 2131230768=androidx.appcompat.widget.Toolbar$SavedState@776b8c, 2131230770=android.view.AbsSavedState$1@b8c12cc, 2131230776=android.view.AbsSavedState$1@b8c12cc, 2131230839=android.view.AbsSavedState$1@b8c12cc, 2131230977=android.view.AbsSavedState$1@b8c12cc}}], androidx.lifecycle.BundlableSavedStateRegistry.key=Bundle[{}], android:lastAutofillId=1073741823, android:fragments=android.app.FragmentManagerState@fe5a2d5}]"]
⇠ onRestoreInstanceState[1ms]="void"
⇢ onResume[]
⇠ onResume[1ms]="void"
⇢ onAttachedToWindow[]
⇠ onAttachedToWindow[0ms]="void"
⇢ onWindowFocusChanged[hasFocus="true"]  // 只要焦点从Activity中失去或者得到就会被调用,所以会被调用多次
⇠ onWindowFocusChanged[0ms]="void"

横竖屏切换不重新创建 Activity

Android 4.0 之前的版本

1
<activity android:configChanges="keyboardHidden|orientation"/>

Android 4.0 之后的版本

1
<activity android:configChanges="keyboardHidden|orientation|screenSize"/>

设置好如上参数时,横竖屏切换只会调用 Activity.onConfigurationChanged 方法

调用堆栈

1
2
3
4
ActivityThread.handleActivityConfigurationChanged
    ActivityThread.performConfigurationChangedForActivity
        ActivityThread.performActivityConfigurationChanged
            Activity.onConfigurationChanged

横屏变竖屏

1
2
 ⇢ onConfigurationChanged[newConfig="{1.0 310mcc260mnc [zh_CN_#Hans,en_US] ldltr sw360dp w672dp h336dp 480dpi nrml long land finger qwerty/v/v dpad/v winConfig={ mBounds=Rect(0, 0 - 2160, 1080) mAppBounds=Rect(0, 0 - 2016, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_90} s.5}"]
 ⇠ onConfigurationChanged[2ms]="void"

竖屏变横屏

1
2
 ⇢ onConfigurationChanged[newConfig="{1.0 310mcc260mnc [zh_CN_#Hans,en_US] ldltr sw360dp w360dp h648dp 480dpi nrml long port finger qwerty/v/v dpad/v winConfig={ mBounds=Rect(0, 0 - 1080, 2160) mAppBounds=Rect(0, 0 - 1080, 2016) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.7}"]
⇠ onConfigurationChanged[0ms]="void"