活動狀態
每個活動在其生命週期中最多可能會有 4 種狀態:
1、運行狀態
當一個活動位於返回棧的棧頂時,這時活動就處於運行狀態。系統最不願意回收的就是處於運行狀態的活動,因爲這會帶來非常差的用戶體驗。
2、暫停狀態
當一個活動不再處於棧頂位置,但仍然可見時,這時活動就進入了暫停狀態。你可能會覺得既然活動已經不在棧頂了,還怎麼會可見呢?
這是因爲並不是每一個活動都會佔滿整個屏幕,比如對話框形式的活動只會佔用屏幕中間的部分區域。處於暫停狀態的活動仍然是完全存活着的,系統也不願意去回收這種活動(因爲它還是可見的,回收可見的東西都會在用戶體驗方面有不好的影響,)只有在內存極低的情況下,系統纔會去考慮回收這種活動。
3、停止狀態
當一個活動不再處於棧頂位置,並且完全不可見的狀態,就進入了停止狀態。系統仍然會爲這種活動保持相應的狀態和成員變量,但是這並不是完全可靠的,當其他地方需要內存時,處於停止狀態的活動有可能會被系統回收。
4、銷燬狀態
當一個活動從返回棧種移除後就變成了銷燬狀態。系統會最傾向於回收處於這種狀態的活動,從而保證手機的內存充足。
回調方法
Activity 類中定義了 7 個回調方法,覆蓋了 Activity 生命週期的每一個環節:
onCreate()
這個方法你已經看到過很多次了,每個活動中我們都重寫了這個方法,它會在活動第一次被創建的時候調用。你應該在這個方法中完成活動的初始化操作,比如加載佈局、綁定事件等。
onStart()
這個方法在活動由不可見變爲可見的時候調用。
onResume()
這個方法在活動準備好和用戶進行交互的時候調用。此時的活動一定位於返回棧的棧頂,並且處於運行狀態。
onPause()
這個方法在系統準備去啓動或者恢復另一個活動的時候調用。我們通常會在這個方法中將一些消耗 CPU 的資源釋放掉,以及保存一些關鍵數據,但這個方法的執行速度一定要快,不然會影響到新的棧頂活動的使用。
onStop()
這個方法在活動完全不可見的時候調用。它和 onPause() 方法的主要區別在於,如果啓動的新活動是一個對話框式的活動,那麼 onPause() 方法會得到執行,而 onStop() 方法並不會執行。
onDestroy()
這個方法在活動被銷燬之後調用,之後活動的狀態將變爲銷燬狀態。
onRestart()
這個方法在活動由停止狀態變爲運行狀態之前調用,也就是活動被重新啓動了。
生存期
以上 7 個方法中除了 onRestart() 方法,其他都是兩兩對應的,從而可以將活動分爲 3 種生存期。
完整生存期:活動在 onCreate() 方法和 onDestroy() 方法之間所經歷的,就是完整生存期。一般情況下,一個活動會在 onCreate() 方法中完成各種初始化操作,而在 onDestroy() 方法種完成釋放內存的操作。
可見生存期():活動在 onStart() 方法和 onStop() 方法之間所經歷的,就是可見生存期。在可見生存期內,活動對於用戶總是可見的,即便有可能無法和用戶進行交互。我們可以通過這兩個方法,合理地管理那些對用戶可見的資源。比如在 onStart() 方法中對資源進行加載,而在 onStop() 方法中對資源進行釋放,從而保證處於停止狀態的活動不會佔用過多內存。
前臺生存期:活動在 onResume() 方法和 onPause() 方法之間所經歷的,就是前臺生存期。在前臺生存期內,活動總是處於運行狀態的,此時的活動是可以和用戶進行交互的,我們平時看到和接觸最多的就是這個狀態下的活動。
我們看下官方給出的 Activity 生命週期的示意圖:
實戰演練
Code
我們先定義一個:NormalActivity
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NormalActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is a normal activity" />
</android.support.constraint.ConstraintLayout>
再定義一個:DialogActivity
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DialogActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is a dialog activity" />
</android.support.constraint.ConstraintLayout>
爲了讓 DialogActivity 使用對話框式主題,我們在 AndroidManifest.xml 中做如下設置:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.marco.activitylifecycletest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".NormalActivity" />
<activity android:name=".DialogActivity"
android:theme="@style/Theme.AppCompat.Dialog"> // Look here
</activity>
</application>
</manifest>
接下來修改 activity_main.xml,重新定製主活動的佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/start_normal_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start NormalActivity" />
<Button
android:id="@+id/start_dialog_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start DialogActivity" />
</LinearLayout>
修改 MainActivity :
package com.example.marco.activitylifecycletest;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
private Button startNormalActivity = null;
private Button startDialogActivity = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startNormalActivity = findViewById(R.id.start_normal_activity);
startDialogActivity = findViewById(R.id.start_dialog_activity);
startNormalActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, NormalActivity.class);
startActivity(intent);
}
});
startDialogActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, DialogActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}
}
Result
(1)當 MainActivity 第一次被創建時,如下方法被執行:
2018-10-18 04:31:29.071 2526-2526/? D/MainActivity: onCreate
2018-10-18 04:31:29.077 2526-2526/? D/MainActivity: onStart
2018-10-18 04:31:29.083 2526-2526/? D/MainActivity: onResume
(2)點擊 Start NormalActivity 按鈕:
2018-10-18 04:33:02.159 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onPause
2018-10-18 04:33:02.745 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onStop
因爲 NormalActivity 已經把 MainActivity 完全遮擋住,因此 onPause() 和 onStop() 方法都會得到執行。
(3)點擊 Back 鍵返回 MainActivity:
2018-10-18 04:35:00.010 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onRestart
2018-10-18 04:35:00.012 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onStart
2018-10-18 04:35:00.014 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onResume
由於之前 MainActivity 已經進入了停止狀態,所以 onRestart() 方法會得到執行,之後又會執行 onStart() 和 onResume() 方法。注意,此時 onCreate() 方法不會執行,因爲 MainActivity 並沒有重新創建。
(4)點擊 Start DialogActivity 按鈕:
2018-10-18 04:43:38.006 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onPause
通過 Log 可以看到,只有 onPause() 方法得到了執行,onStop() 方法並沒有執行,這是因爲 DialogActivity 並沒有完全遮擋住 MainActivity,此時 MainActivity 只是進入了暫停狀態,並沒有進入停止狀態。
(5)點擊 Back 鍵返回 MainActivity:
2018-10-18 04:50:12.222 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onResume
按下 Back 鍵返回 MainActivity 也應該只有 onResume() 方法會得到執行。
(6)在 MainActivity 界面按下 Back 鍵退出程序:
2018-10-18 04:51:47.673 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onPause
2018-10-18 04:51:48.013 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onStop
2018-10-18 04:51:48.015 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onDestroy
依次會執行 onPause()、onStop() 和 onDestroy 方法,最終銷燬 MainActivity。
疑問
我們在之前分析 Activity 的生命週期的時候曾經提到過:如果一個活動進入了 onStop (停止)狀態,是有可能被系統回收的!
場景
比如我們看以下的場景:
應用中有一個活動 A ,用戶在活動 A 的基礎上啓動了活動 B ,活動 A 就進入了停止的狀態,這個時候由於系統內存不足,將活動 A 回收掉了,然後用戶按下 Back 鍵返回活動 A ,會出現什麼情況呢?
其實還是會正常顯示活動 A 的,但是此時並不會執行 onRestart() 方法了,而是會執行活動 A 的 onCreate() 方法,因爲活動 A 在這種情況下會被重新創建一次。
可能這並不會影響正常的功能,但是存在一個特殊情況:如果活動 A 中存在臨時數據和狀態(比如 A 中有一個文本輸入框,我們輸入了一些文字,然後啓動了 B 活動,如果 A 被 kill了,在重新回到 A 後,A 活動重新創建,那麼數據都丟失了),此時會嚴重影響用戶體驗,該怎麼辦?
策略
其實官方文檔給出瞭解決方案,Activity 中提供了一個 onSaveInstanceState() 回調方法,這個方法可以保證在活動被回收之前一定會被調用,因此我們可以通過這個方法來解決活動被回收時臨時數據得不到保存的問題。
onSaveInstanceState() 方法會攜帶一個 Bundle 類型的參數,Bundle 提供了一系列的方法用於保存數據,比如可以使用 putString() 方法保存字符串,使用 putInt() 方法保存整型數據,依次類推。
每個保存方法需要傳入兩個參數,第一個參數是鍵,用於後面從 Bundle 中取值,第二個參數是真正要保存的內容。
Code
我們現在對上面的代碼進行修改,在 MainActivity 中添加如下代碼將臨時數據進行保存:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}
Ok,數據保存好了,那我們應該在哪邊進行恢復?
不知道你有沒有發現,在 onCreate() 方法中有一個 Bundle 類型的參數。這個參數一般情況下是 null ,但是如果在活動被系統回收之前有通過 onSaveInstanceState() 方法來保存數據的話,這個參數就會帶有之前所保存的全部數據,我們只需要再通過相應的取值方法將數據取出即可。
修改 MainActivity 的 onCreate() 方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate");
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
... ...
}
通過上面的方法取出值之後再做相應的恢復操作就可以了,比如說將文本內容重新賦值到文本輸入框上即可。