橫豎屏切換 屏幕適配 狀態保存與恢復 [MD]


博文地址

我的GitHub 我的博客 我的微信 我的郵箱
baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

橫豎屏切換適配

Android開發中,大多APP可能根據實際情況直接將APP的界面方向設死了,或豎屏或橫屏。但是,我們還是會遇到橫豎屏切換的功能需求,不管是通過物理重力感應觸發,還是用戶手動觸發。所以,我們有必要去弄清楚Android中橫豎屏切換到底做了什么。

小技巧

  • 可以在開發中選項中修改最小寬度的值(單位是dp),這樣就可以真實模擬其他分辨率的手機(或pad)
  • 可以layout-landlayout-port中放同名的布局文件
    • 對於重啟 activity 模式:當橫豎屏切換時會自動使用不同的布局
    • 對於非重啟 activity 模式:需要在 onConfigurationChanged 中重新 setContentView
  • 對於 RecyclerView,可以在 onConfigurationChanged 中根據橫豎屏狀態設置不同的 LayoutManager 來改變布局樣式

官方文檔:兩個屬性和三個方法

屬性 android:screenOrientation

用來設置 activity 在設備上的顯示方向

android:screenOrientation="portrait"
可選值 含義
landscape(常用) 屏幕方向為橫向(顯示的寬度大於高度)。
portrait(常用) 屏幕方向為縱向(顯示的高度大於寬度)。
sensor(常用) 屏幕方向由設備方向傳感器決定。顯示方向取決於用戶如何手持設備,它會在用戶旋轉設備時發生變化。但在默認情況下,一些設備不會旋轉為所有四種可能的方向。如要支持所有這四種方向,請使用 "fullSensor"。即使用戶鎖定基於傳感器的旋轉,系統仍可使用傳感器。
fullSensor(常用) 屏幕方向由使用 4 種方向中任一方向的設備方向傳感器決定。這與 "sensor" 類似,不同之處在於無論設備在正常情況下使用哪種方向,該值均支持所有 4 種可能的屏幕方向(例如,一些設備正常情況下不使用反向縱向或反向橫向,但其支持這些方向)。API9 新增
unspecified 默認值。由系統選擇方向。在不同設備上,系統使用的政策以及基於政策在特定上下文中所做的選擇可能會有所差異。
behind 與 Activity 棧中緊接其后的 Activity 的方向相同。
reverseLandscape 屏幕方向是與正常橫向方向相反的橫向。API9 新增
reversePortrait 屏幕方向是與正常縱向方向相反的縱向。API9 新增
sensorLandscape 屏幕方向為橫向,但可根據設備傳感器調整為正常或反向的橫向。即使用戶鎖定基於傳感器的旋轉,系統仍可使用傳感器。API9 新增
sensorPortrait 屏幕方向為縱向,但可根據設備傳感器調整為正常或反向的縱向。即使用戶鎖定基於傳感器的旋轉,系統仍可使用傳感器。API9 新增
userLandscape 屏幕方向為橫向,但可根據設備傳感器和用戶首選項調整為正常或反向的橫向。API18 新增
userPortrait 屏幕方向為縱向,但可根據設備傳感器和用戶首選項調整為正常或反向的縱向。API18 新增
nosensor 確定屏幕方向時不考慮物理方向傳感器。系統會忽略傳感器,因此顯示內容不會隨用戶手持設備的方向而旋轉。
user 用戶當前的首選方向。
fullUser 如果用戶鎖定基於傳感器的旋轉,則其行為與 user 相同,否則,其行為與 fullSensor 相同,並且支持所有 4 種可能的屏幕方向。API18 新增
locked 將屏幕方向鎖定為其當前的任意旋轉方向。API18 新增

屬性 android:configChanges

列出 Activity 將自行處理的配置變更集合,不僅僅是屏幕方向,還有語言、地區等等。

android:configChanges="keyboardHidden|screenSize|orientation"

在運行時發生配置變更時,默認情況下會關閉 Activity 並將其重啟,但使用該屬性聲明配置將阻止 Activity 重啟。相反,Activity 會保持運行狀態,並且系統會調用其 onConfigurationChanged() 方法。

請注意:應避免使用該屬性,並且只應在萬不得已的情況下使用。如需了解有關如何正確處理配置變更所致重啟的詳細信息,請閱讀 處理運行時變更

可選值 含義
keyboardHidden(常用) 鍵盤無障礙功能發生變更 — 例如,用戶顯示硬鍵盤。
orientation(常用) 屏幕方向發生變更 — 用戶旋轉設備。請注意:如果應用面向 API13 或更高版本的系統,則還應聲明 screenSize 配置,因為當設備在橫向與縱向之間切換時,該配置也會發生變更。
screenSize(常用) 當前可用屏幕尺寸發生變更。該值表示當前可用尺寸相對於當前縱橫比的變更,當用戶在橫向與縱向之間切換時,它便會發生變更。API13 新增
density 顯示密度發生變更 — 用戶可能已指定不同的顯示比例,或者有不同的顯示現處於活躍狀態。API24 新增
fontScale 字體縮放系數發生變更 — 用戶已選擇新的全局字號。
keyboard 鍵盤類型發生變更 — 例如,用戶插入外置鍵盤。
layoutDirection 布局方向發生變更 — 例如,自從左至右 (LTR) 更改為從右至左 (RTL)。API17 新增
locale 語言區域發生變更 — 用戶已為文本選擇新的顯示語言。
mcc IMSI 移動設備國家/地區代碼 (MCC) 發生變更 — 檢測到 SIM 並更新 MCC。
mnc IMSI 移動設備網絡代碼 (MNC) 發生變更 — 檢測到 SIM 並更新 MNC。
navigation 導航類型(軌跡球/方向鍵)發生變更。(這種情況通常不會發生。)
screenLayout 屏幕布局發生變更 — 不同的顯示現可能處於活躍狀態。
smallestScreenSize 物理屏幕尺寸發生變更。該值表示與方向無關的尺寸變更,因此它只有在實際物理屏幕尺寸發生變更(如切換到外部顯示器)時才會變化。對此配置所作變更對應 smallestWidth 配置的變化。API13 新增
touchscreen 觸摸屏發生變更。(這種情況通常不會發生。)
uiMode 界面模式發生變更 — 用戶已將設備置於桌面或車載基座,或者夜間模式發生變更。API8 新增

所有這些配置變更都可能影響應用所看到的資源值。因此,調用 onConfigurationChanged() 時,通常有必要再次檢索所有資源(包括視圖布局、可繪制對象等),以正確處理變更。

方法 onConfigurationChanged

這幾個方法都是 Activity 中的

public void onConfigurationChanged (Configuration newConfig)
    //Configuration newConfig: The new device configuration. This value cannot be null.

Called by the system when the device configuration changes while your activity is running. Note that this will only be called if you have selected android:configChanges you would like to handle with the R.attr.configChanges attribute in your manifest. If any configuration change occurs that is not selected to be reported by that attribute, then instead of reporting it the system will stop and restart the activity (to have it launched with the new configuration).

At the time that this function has been called, your Resources object will have been updated to return resource values matching the new configuration.

方法 onSaveInstanceState

public void onSaveInstanceState (Bundle outState)
    //Bundle outState: Bundle in which to place your saved state. This value cannot be null.

Called to retrieve per-instance state from an activity before being killed so that the state can be restored in onCreate(Bundle) or onRestoreInstanceState(Bundle) (the Bundle populated by this method will be passed to both).

在 activity 被終止之前調用,以從中取回每個實例的狀態,以便可以在 ... 中恢復狀態(此方法填充的 Bundle 將傳遞給兩者)。

This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state. For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, activity A will have a chance to save the current state of its user interface via this method so that when the user returns to activity A, the state of the user interface can be restored via onCreate(Bundle) or onRestoreInstanceState(Bundle).

這個方法在一個 activity 可能被殺死之前被調用,這樣當它在未來某個時間回來時它可以恢復它的狀態。例如,如果 activityB 在 activityA 之前啟動,並且在某個時刻 activityA 被殺死以回收資源,則 activityA 將有機會通過此方法保存其用戶界面的當前狀態,以便當用戶返回時對於 activityA,用戶界面的狀態可以通過 ... 恢復。

Do not confuse this method with activity lifecycle callbacks such as onPause(), which is always called when the user no longer actively interacts with an activity, or onStop() which is called when activity becomes invisible. One example of when onPause() and onStop() is called and not this method is when a user navigates back from activity B to activity A: there is no need to call onSaveInstanceState(Bundle) on B because that particular instance will never be restored, so the system avoids calling it. An example when onPause() is called and not onSaveInstanceState(Bundle) is when activity B is launched in front of activity A: the system may avoid calling onSaveInstanceState(Bundle) on activity A if it isn't killed during the lifetime of B since the state of the user interface of A will stay intact.

不要將此方法與 activity 生命周期回調混淆,例如,當用戶不再主動與 activity 交互時總是調用 onPause(),或者當 activity 變得不可見時調用 onStop()。調用 onPause() 和 onStop() 而不調用此方法的一個示例是,當用戶從 activityB 導航回 activityA 時:此時無需在 B 上調用 onSaveInstanceState(Bundle) 因為該特定實例永遠不會恢復,所以系統會避免調用它。調用 onPause() 而不是 onSaveInstanceState(Bundle) 的示例是當 activityB 在 activityA 之前啟動時:如果 activityA 在 activityB 的生命周期內沒有被殺死,系統會避免在 activityA 上調用 onSaveInstanceState(Bundle),A 的用戶界面的狀態將保持不變。

The default implementation takes care of most of the UI per-instance state for you by calling View.onSaveInstanceState() on each view in the hierarchy that has an id, and by saving the id of the currently focused view (all of which is restored by the default implementation of onRestoreInstanceState(Bundle)). If you override this method to save additional information not captured by each individual view, you will likely want to call through to the default implementation, otherwise be prepared to save all of the state of each view yourself.

默認實現通過在層次結構中具有 id 的每個 View 上調用 View.onSaveInstanceState() 並保存當前聚焦的 View 的 id 來為您處理大部分 UI 每個實例狀態,所有這些都是通過 onRestoreInstanceState(Bundle) 的默認實現來恢復的。如果您重寫此方法以保存每個單獨 View 未捕獲的其他信息,您可能希望調用默認實現,否則就要准備好自己保存每個視圖的所有狀態。

If called, this method will occur after onStop() for applications targeting platforms starting with Build.VERSION_CODES.P(28). For applications targeting earlier platform versions this method will occur before onStop() and there are no guarantees about whether it will occur before or after onPause().

如果調用,此方法將在 onStop() 之后針對...。對於面向早期平台版本的應用程序,此方法將在 onStop() 之前發生,並且無法保證它是在 onPause() 之前還是之后發生。

方法 onRestoreInstanceState

protected void onRestoreInstanceState (Bundle savedInstanceState)
    //Bundle: the data most recently 最近 supplied in onSaveInstanceState(Bundle). This value cannot be null.

This method is called after onStart() when the activity is being re-initialized from a previously saved state, given here in savedInstanceState. Most implementations will simply use onCreate(Bundle) to restore their state, but it is sometimes convenient to do it here after all of the initialization has been done or to allow subclasses to decide whether to use your default implementation. The default implementation of this method performs a restore of any view state that had previously been frozen by onSaveInstanceState(Bundle).

當 activity 從先前保存的狀態重新初始化時,在 onStart() 之后調用此方法。大多數實現將簡單地使用 onCreate(Bundle) 來恢復它們的狀態,但有時在完成所有初始化后在這里執行它、或允許子類決定是否使用您的默認實現會很方便。此方法的默認實現會執行恢復先前被 onSaveInstanceState(Bundle) 保存的任何視圖狀態。

This method is called between onStart() and onPostCreate(Bundle). This method is called only when recreating an activity; the method isn't invoked if onStart() is called for any other reason.

該方法在 onStart()onPostCreate(Bundle) 之間調用。 此方法僅在重新創建 activity 時調用;如果出於任何其他原因調用 onStart() ,則不會調用該方法。

官方文檔:如何處理 config change

某些設備配置可能會在運行時發生變化,例如屏幕方向、鍵盤可用性,以及當用戶啟用多窗口模式時。發生這種變化時,Android 會重啟正在運行的 Activity(先后調用onDestroyonCreate)。重啟行為旨在通過利用與新設備配置相匹配的備用資源,來自動重新加載您的應用,從而幫助它適應新配置。

然而,重啟應用並恢復大量數據不僅成本高昂,而且會造成糟糕的用戶體驗。在此情況下,您還有兩個選擇:

1、在配置變更期間保留對象,並使用 ViewModel 等方式保存界面數據

允許 Activity 在配置變更時重啟,但是需將有狀態對象傳遞給 Activity 的新實例。

2、阻止重啟 Activity,並在回調中自行處理配置變更

如果您無法使用首選項(onSaveInstanceState()、ViewModel 和持久存儲)來保留界面狀態,則可阻止系統在特定配置變更期間重啟您的 Activity。配置變更時,應用會收到回調,以便您可以根據需要手動更新 Activity。

允許重啟並自行保存界面數據

如果重啟 Activity 需要恢復大量數據、重新建立網絡連接或執行其他密集操作,那么因配置變更而引起的完全重啟可能會給用戶留下應用運行緩慢的體驗。

此外,若使用系統通過 onSaveInstanceState() 回調為您保存的 Bundle,則可能 無法完全恢復 Activity 狀態,因為該類並非用於攜帶大型對象(例如Bitmap),並且其中的數據必須依次在主線程中進行序列化和反序列化,而這可能會消耗大量內存並降低配置變更的速度。

在此情況下,您可通過使用 ViewModel 對象來減輕重新初始化 Activity 的負擔。系統會在配置變更時保留 ViewModel,使其成為保存界面數據的理想場所,讓您無需再次查詢這些數據。

阻止重啟並自行處理配置變更

如果應用在特定配置變更期間無需更新資源,並且因性能限制您需要盡量避免 Activity 重啟,則可聲明 Activity 自行處理配置變更,從而阻止系統重啟 Activity。

注意:自行處理配置變更可能會提高使用備用資源的難度,因為系統不會為您自動應用這些資源。只有在必須避免 Activity 因配置變更而重啟的無奈情況下,您才可考慮使用此方法,並且不建議對大多數應用使用此方法。

如要聲明由 Activity 處理配置變更,請在清單文件中編輯相應的元素,以包含 android:configChanges 屬性,該屬性的值表示要處理的配置。您可以在屬性中聲明多個配置值,方法是用管道 | 字符將其進行分隔。

常用的值:

  • orientation 值可在屏幕方向發生變更時阻止重啟
  • screenSize 值也可在屏幕方向發生變更時阻止重啟,但僅適用於 API13 及以上版本的系統。若想在應用中手動處理配置變更,您必須在 android:configChanges 屬性中聲明 orientationscreenSize
  • keyboardHidden 值可在鍵盤可用性發生變更時阻止重啟。

現在,即便其中某個配置發生變化, Activity 也不會重啟,但 Activity 會接收到對 onConfigurationChanged() 的調用消息。此方法會收到傳遞的 Configuration 對象,從而指定新設備配置。您可以通過讀取 Configuration 中的字段確定新配置,然后通過更新界面所用資源進行適當的更改。調用此方法時,Activity 的 Resources 對象會相應地進行更新,並根據新配置返回資源,以便您在系統不重啟 Activity 的情況下輕松重置界面元素。

Configuration 對象代表所有當前配置,而不僅僅是已變更的配置。多數情況下,您並不在意配置具體發生了哪些變更,而且您可以輕松地重新分配所有資源,為正在處理的配置提供備用資源。例如,由於 Resources 對象現已更新,您便可通過 setImageResource() 重置任何 ImageView 並使用合適的新配置資源。

請謹記:在聲明由 Activity 處理配置變更時,您有責任 resetting any elements for which you provide alternatives(備用資源)。如果您聲明由 Activity 處理方向變更,且需要在橫向和縱向之間切換某些圖像,則您必須在 onConfigurationChanged() 期間為每個元素 re-assign(重新分配) each resource。

如果無需根據這些配置變更更新應用,則您可不必實現 onConfigurationChanged()。在此情況下,all of the resources used before the configuration change are still used,區別在於您無需重啟 Activity。但是,您的應用應始終能在保持先前狀態完好的情況下關閉和重啟,因此您不應該認為,使用此方法即可無需保留正常 Activity 生命周期中的狀態。其原因是一些其他配置變更會強制重啟應用,而且某些事件需由您進行處理,例如以下情況:when the user leaves your application and it gets destroyed before the user returns to it.

橫豎屏切換下的生命周期

不配置 screenSize 場景

下面三種方式 Log是一樣的:橫豎屏切換時會重新調用各個生命周期,且都是執行一次生命周期方法

  • 不配置 android:configChanges
  • 僅配置 android:configChanges="orientation"
  • 僅配置 android:configChanges="orientation|keyboardHidden"
//豎屏切橫屏
onPause
onSaveInstanceState
onStop
onDestroy
onCreate - orientation
onStart
onRestoreInstanceState
onResume
//橫屏切豎屏
onPause
onSaveInstanceState
onStop
onDestroy
onCreate - orientation
onStart
onRestoreInstanceState
onResume

我們經常在其他地方看到,以上3種不同配置下,回調 Activity 生命周期方法次數並不一致,為什么和我們的結論不一樣呢?

其實是 Android 高、低版本的差異而已,查看官方文檔,發現有如下提示:

注意:從 Android 3.2(API13)開始,當設備在縱向和橫向之間切換時, screenSize 也會發生變化。因此,在開發針對 API13 或更高版本系統的應用時,若要避免由於設備方向改變而導致運行時重啟,則除了orientation值以外,您還必須添加screenSize值。但是,如果您的應用是面向(注意,指的是targetSdkVersion) API12 或更低版本的系統,則 Activity 始終會自行處理此配置變更(注意,即便是在 Android 3.2 或更高版本的設備上運行,此配置變更也不會重啟 Activity)。

目前不會還有兼容 API12 或更低版本的應用了,所以這三種配置都已經不適用了。

完整配置場景

android:configChanges="orientation|keyboardHidden|screenSize"
//豎屏切橫屏
onConfigurationChanged

//橫屏切豎屏
onConfigurationChanged

按照這種配置,橫豎屏切換時才不會銷毀 activity,且只調用 onConfigurationChanged方法。

代碼中動態切換橫豎屏

在代碼中切換屏幕的方向主要調用 setRequestedOrientation(int requestedOrientation) 方法,此方法的作用等同於在 AndroidManifest.xml 設置的 android:screenOrientation

boolean isLandscape = getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
Toast.makeText(TestActivity.this, "當前是 " + (isLandscape ? "橫屏" : "豎屏"), Toast.LENGTH_SHORT).show();
setRequestedOrientation(isLandscape ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

注意: 通過 setRequestedOrientation 修改了屏幕方向后,就類似於設置了 android:screenOrientation,效果是一樣的。比如:調用 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) 后,無論屏幕怎么旋轉,都不會切換屏幕方向。如果要恢復為響應橫豎屏隨物理方向傳感器設備變換,那么就需要手動調用類似 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR)代碼進行恢復。

適用場景:如果我們應用是手機和平板都可用的,但是手機上只能是豎屏不可切換,平板上只能是橫屏不可切換,那么就可以在區分設備是手機還是平板后,在BaseActivityonCreate方法中通過setRequestedOrientation設置屏幕方向。

重啟模式下數據的保存與恢復

在重啟 Activity 模式下,橫豎屏切換的時候會導致數據丟失,我們可以通過如下代碼來保證數據不丟失:

public class TestActivity extends Activity {
    private String date;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textView = new TextView(this);
        if (savedInstanceState != null) {
            date = savedInstanceState.getString("date"); //在 onCreate 中也是可以拿到之前保存的數據的
        } else {
            date = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault()).format(new Date());
        }
        textView.setText(date);
        setContentView(textView);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString("date", date); //putInt、putBoolean、putCharSequence、putIntArray、putIntegerArrayList...
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) {
            String date = savedInstanceState.getString("date");
            Toast.makeText(this, date, Toast.LENGTH_SHORT).show();
        }
    }
}

重啟模式下的屏幕適配

重啟模式下,如果大家在資源目錄 res 中添加了 layout-land(橫向布局文件夾) 和 layout-port(豎想布局文件夾),重啟 Activity 模式的橫豎屏切換,系統會自動幫我們顯示正確方向的布局 UI。

非重啟模式下的屏幕適配

非重啟 Activity 模式下橫豎屏切換時,我們的 Activity 不會銷毀重建,數據也不會丟失。所以,如果按照上面的方式,在資源目錄 res 中添加 layout-landlayout-port,會發現並沒有效果,此時應該怎么做呢?

其實也很簡單,只需要在onConfigurationChanged中再掉一次setContentView即可。

public class TestActivity extends Activity {
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.tv);
        mTextView.setText(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault()).format(new Date()));
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            setContentView(R.layout.activity_main);
            TextView textView = findViewById(R.id.tv);
            textView.setText("切換到了豎屏 " + (textView == mTextView)); 
            //注意1:界面上的 TextView 已經不是原先 onCreate 中的 TextView 了,必須重新find一次
            //注意2:由於 activity 沒變,所以原先的數據都還存在,此時可以直接鋪到新的 UI 上去
        } else {
            setContentView(R.layout.activity_main);
            TextView textView = findViewById(R.id.tv);
            textView.setText("切換到了橫屏 " + (textView == mTextView));
        }
        mTextView.setText(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault()).format(new Date())); //沒有效果
    }
}

橫豎屏切換對 Fragment 的影響

測試代碼 Activity

public class TestActivity extends FragmentActivity {
    private static final String TAG = "child";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (getSupportFragmentManager().findFragmentByTag(TAG) == null) {
            Log.d("Activity", " -- onCreate has no child ");
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.add(R.id.root_view, BlankFragment.newInstance("baiqiantao"), TAG);
            transaction.commit();
        } else {
            Log.d("Activity", " -- onCreate has child ");
        }
    }
}

測試代碼 Fragment

public class BlankFragment extends Fragment {
    private static final String ARGUMENT = "argument";
    private String mArgument;

    public static BlankFragment newInstance(String argument) {
        Bundle bundle = new Bundle();
        bundle.putString(ARGUMENT, argument);
        BlankFragment fragment = new BlankFragment();
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("Fragment", "-- onCreate");
        Bundle bundle = getArguments();
        if (bundle != null) {
            mArgument = bundle.getString(ARGUMENT);
        }
    }

    @Override
    public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.i("Fragment", "-- onCreateView");
        TextView textView = new TextView(getActivity());
        textView.setBackgroundColor(Color.RED);
        textView.setText(mArgument);
        return textView;
    }
}

非重建 Activity 模式

橫豎屏切換時,Fragment 和 Activity 都只會調用 onConfigurationChanged 方法,不會走其他生命周期的方法。

Fragment: -- onConfigurationChanged
Activity: -- onConfigurationChanged

重建 Activity 模式

帶有 Fragment 即為 Fragment 的打印,其他則為 Activity 的打印

進入 Activity 的 Log

-- onCreate has no child
Fragment -- newInstance
Fragment -- onAttach
Fragment -- onCreate
Fragment -- onCreateView
Fragment -- onActivityCreated
Fragment -- onStart
-- onStart
-- onResume
Fragment -- onResume

橫豎屏切換的Log

雖然 Fragment 不像 Activity 擁有 onRestoreInstanceState 方法,但是我們可以在 onActivityCreated 中獲取之前保存的數據。

Fragment -- onPause
-- onPause
-- onSaveInstanceState save: name = bqt  //【Activity 保存數據】
Fragment -- onSaveInstanceState save str  //【Fragment 可以在此保存數據】
Fragment -- onStop
-- onStop
Fragment -- onDestroyView
Fragment -- onDestroy
Fragment -- onDetach
-- onDestroy
Fragment -- onAttach
Fragment -- onCreate  //【Fragment 重建了。一定注意,是 new 了一個新的對象,而不是復用舊的對象】
-- onCreate get: name = bqt  //【Activity 重建了且數據恢復了。同樣,也是 new 了一個新的對象】
-- onCreate has child //【自動將新的 Fragment 添加進了新的 Activity】
Fragment -- onCreateView
Fragment -- onActivityCreated get  //【Fragment 可以在此獲取之前保存的數據】
Fragment -- onStart
-- onStart
-- onRestoreInstanceState get: name = bqt
-- onResume
Fragment -- onResume

2021-6-2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM