一. Activity生命周期
? ? 1.1?返回棧知識點
二. Activity狀態
? ? 2.1?啟動狀態
? ? 2.2?運行狀態
? ? 2.3?暫停狀態
? ? 2.4?停止狀態
? ? 2.5?銷毀狀態
三. Activity生存期
? ? 3.1?回調方法
? ? 3.2?生存期
四. 體驗Activity的生命周期
五. Activity被回收辦法
引言:
掌握Activity的生命周期對Android開發來說非常重要,當我們深入理解Activity的生命周期之后,就可以寫出更加連貫流暢的理序,并在如何合理管理應用資源方面發揮得游刃有余。我們的應用程序也將會擁有更好的用戶體驗。
一.Activity生命周期
1.1返回棧:
我們知道,Android里的Activity可以層疊的,我們每啟動一個新的Activity,就會覆蓋在原來的Activity之上,然后點擊Back按鍵,會銷毀最上面的Activity,下面的一個Activity就會顯示出來。
其實Android是使用任務(task)來管理Activity的,一個任務就是一組存放在棧里的Activity的集合,這個棧也被稱作返回棧(back stack)。棧是一種后進先出的數據結構,在默認情況下,每當我們啟動了一個新的Activity,它就會在返回棧中入棧,并處于棧頂的位置。而每當我們按下Back鍵或調用finish()方法去銷毀一個Activity時,處于棧頂的Activity就會出棧,前一個入棧的Activity就會重新處于棧頂的位置。
系統總是會顯示處于棧頂的Activity給用戶。
圖1:返回棧流程圖
1.2 Activity狀態
Activity有主要的四個狀態,以及一個非常迅速的啟動狀態,下面我們來講解他們的功能:
1.2.1啟動狀態(Starting):
啟動狀態就是內個非常迅速的狀態,在Activity啟動時會自動跳轉到下一個狀態,這也就是為什么很多的Activity介紹里只寫了四個狀態而沒有介紹這個狀態了。
1.2.2運行狀態(Running):
當一個Activity位于返回棧的棧頂時,Activity就處于運行狀態。系統最不愿意回收的就是處于運行狀態的Activity,因為這會帶來非常差的運行體驗。
1.2.3暫停狀態(Paused):
當一個Activity不再處于棧頂的位置,但仍然可見時,Activity就進入了暫停狀態。你可能會覺得,既然Activity已經不在棧頂了,怎么會可見呢?這是因為并不是每一個Activity都會占滿整個屏幕,比如對話框式的Activity只會占用中間屏幕的部分區域。處于暫停狀態的Activity仍然是完全存活的,系統也不愿意回收這種Activity(因為它還是可見的,回收可見的東西都會在用戶體驗方面有不好的影響),只有在內存極低的情況下,系統才會去考慮回收這種Activity。
1.2.4停止狀態(Stopped):
當一個Activity 不再處于棧頂位置,并且完全不可見的時候,就進人了停止狀態。系統仍然會為這種Activity保存相應的狀態和成員變量,但是這并不是完全可靠的,當其他地方需要內存時,處于停止狀態的Activity有可能會被系統回收。
1.2.5銷毀狀態(Destroyed):
一個Activity 從返回棧中移除后就變成了銷毀狀態。系統最傾向于回收處于這種狀態的Activity以保證手機的內存充足。
1.3生存期
1.3.1回調方法
在Activity類中定義了7個回調方法,其覆蓋了Activity生命周期的每一個環節,下面我們來一個一個看這 些方法:
1.onCreate()——創建
這個方法我們已經看到過很多次了,在每個Activity 中都重寫了這個方法,它會在Activity第一次被創建的時候調用。我們應該在這個方法中完成Activity的初始化操作,比如加載布局、綁定事件等。
2.onStart()——啟動
這個方法在Activity由不可見變為可見的時候調用
3.onResume()——恢復
這個方法在Activity準備好和用戶進行交互的時候調用。此時的Activity一定位于返回棧的棧頂,并且處于運行狀態。
4.onPause()——停頓
這個方法在系統準備去啟動或者恢復另一個Activity的時候調用。我們通常會在這個方法中將一些消耗CPU的資源釋放掉,以及保存一些關鍵數據,但這個方法的執行速度一定要快,不然會影響到新的棧頂Activity的使用。
5.onStop()——暫停
這個方法在Activity完全不可見的時候調用。它和onPause()方法的主要區別在于,如果啟動的新Activity是一個對話框式的Activity,那么onPause()方法會得到執行,而onstop()方法并不會執行。
6.onDestory()——銷毀
這個方法在Activity被銷毀之前調用,之后Activity的狀態將變為銷毀狀態。
7.onRestart——重啟
這個方法在Activity由停止狀態變為運行狀態之前調用,也就是Activity被重新啟動了。
1.3.2生存期
1.3.2.1完整生存期
完整生存期:Activity在onCreate()方法和onDestroy()方法之間所經歷的就是完整生存期。一般情況下, 一個Activity會在onCreate()方法中完成各種初始化操作,而在onDestroy()方法中完成釋放內存的操 作。
1.3.2.2可見生存期
Activity在onStart()方法和onStop()方法之間所經歷的就是可見生存期。在可見生存期內,Activity對于用戶總是可見的,即便有可能無法和用戶進行交互。我們可以通過這兩個方法合理地管理那些對用戶可見的資源。比如在onStart()方法中,對資源進行加載,而onStop()方法中對資源進行釋放,從而保證處于停止狀態的Activity不會占用過多內存。
1.3.2.3前臺生存期
Activity在onResume()方法和onPause()方法之間所經歷的就是前臺生存期。在前臺生存期內, Actvity總是處于運行狀態,此時的 Activity 是可以和用戶進行交互的,我們平時看到和接觸最多的就是這個狀態下的Activity。
圖3.Activity生命周期
1.4體驗Activity的生命周期
了解了Activity的生命周期之后,我們來寫一個實例,通過實例來詳細了解。
首先,我們新建一個EmptyActivity,名為NormalActivity,其布局起名為normal_layout;使用相同的方法創建DialogActivity,布局起名為dialog_layout。
接下來,我們先編輯normal_layout.xml文件。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_ parent"
android:layout_height="match_ parent">
<Textview
android:Layout_width="match_parent"
android:layout_height="wrap_content
android:text="This is a normal activity"
/>
</LinearLayout>
在這里我們簡單的定義了一個TextView,顯示了一行文字"This is a normal activity"。
接著我們編輯dialog_layout.xml文件,代碼如下:
<LinearLayout xmlns:android-"http://schemas.android.com/apk/res/android
android:orientation"vertical"
android: Layout_width="match_parent"
android: layout_height="match_parent">
<Textview
android:layout_width="match_parent"
android: layout_height="wrap_content"
android:text="This is a dialog activity"
/>
</LinearLayout>
接下來我們修改AndroidManifest.xml的標簽配件。
這里注冊了兩個Activity,此時我們為什么使用了一個android:theme的屬性?這里是給前面的Activity指定主題,這里的"@style/Animation.Design.BottomSheetDialog"則毫無疑問是讓DialogActivity使用對話框的主題。現在,我們可以修改activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? ? ? ?android:orientation="vertical"
? ? ? ? ?android:layout_width="match_parent"
? ? ? ? ?android:layout_height="wrap_content">
? ? ? <Button
? ? ? ? ? ? ?android:layout_width="match_parent"
? ? ? ? ? ? ?android:layout_height="wrap_content"
? ? ? ? ? ? ?android:id="@+id/startNormalActivity"
? ? ? ? ? ? ?android:text="Start NormalActivity"/>
? ? ? ? ?<Button
? ? ? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? ? ? android:id="@+id/startDialogActivity"
? ? ? ? ? ? ? ? android:text="Start DialogActivity"/>
</LinearLayout>
這里我們添加兩個按鈕,一個用于啟動NormalActivity,一個用于啟動DialogActivity,修改代碼:
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
? ? ? super.onCreate(savedInstanceState);
? ? ? Log.d(TAG, "onCreate");
? ? ? setContentView(R.layout.activity_main);
? ? ? findViewById(R.id.startNormalActivity).setOnClickListener(new
View.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(View v) {
? ? ? ? ? ? ? ? ? ?Intent intent = new Intent(MainActivity.this,
NormalActivity.class);
? ? ? ? ? ? ? ? ? ? ?startActivity(intent);
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ?});
? ? ? ? ? ? ?findViewById(R.id.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");
? }
}
現在,我們啟動程序,并看一下logcat的效果。
圖4:Main界面圖
此時我們不對界面做任何操作,看log cat運行過程:
圖5:MainLogCat打印日志
這里其實我們就會發現,在MainActivity第一次被創建時會依次執行onCreate,onStart和onResume方法。
然后點擊第一個按鈕NormalActivity,打開normal_layout界面:
圖6:NormalActivity界面
此時的Logcat打印日志:
圖7:NormalActivity的LogCat打印日志
由于此時NormalActivity已經把MainActivity完全遮擋住,所以onPause和onStop都會執行。然后按下Back按鍵返回MainActivity。
這時候我們返回原來的界面,再看log cat打印日志:
圖8:返回MainActivity時的打印日志
由于之前的MainActivity已經進入了停止狀態,所以onRestart方法會得到執行,之后會依次執行
onRestart和onResume方法,此時onCreate方法不會執行,因為MainActivity并沒有重新創建。
點擊第二個按鈕,啟動DialogActivity。
此時的logcat:
圖9:打開DialogActivity界面
可以看到,只有onPause的方法得到了執行,onStop沒有得到執行,這是因為DialogActivity并沒有完全遮擋住MainActivity,此時MainActivity只是進入暫停狀態,并沒有進入停止狀態。
最后在MainActivity按下Back按鍵,退出程序,打印程序:
圖10:結束界面
1.5Activity被回收辦法
前面講過,當一個Activity進入了停止狀態,是有可能被系統回收的。我們可以假設一種情況:有一個ActivityA,我們在這個基礎上啟動了ActivityB,這時候A任務就陷入停止狀態,這個時候由于系統的內存不足,系統將ActivityA回收掉了,然后用戶按下Back按鍵返回ActivityA,正常情況下還是會顯示ActivityA,這是并不會執行onRestart方法,而是會執行onCreate方法,因為A在這種情況下被重新創建一次。
但是偶爾我們還是回遇見一種情況,如果在A中可能會存在臨時數據和狀態,比如說A里有一個文本輸入框,我們輸入了一段數據;拿上面內個程序舉例,此時我們打開了NormalActivity,這時MainActivity由于內存不足被回收掉,此時我們如果點擊Back返回;此時如果內存不足的話,剛剛輸入的文字就都沒了,因為MainActivity被重建了。
這種情況是非常影響用戶體驗的,這時Activity里有一種回調方法我們就可以使用了——onSaveInstanceState(),這個方法可以保證在Activity被回收之前一定會被調用。
omSaveInstanceState()方法攜帶一個Bundle類的參數,Bundle提供一系列的方法用于保存數據,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型數據,以此類推。每個保存方法需傳入兩個參數,第一個參數是鍵,用于后面的Bundle中取值,第二個參數是真正要保存到內容。
@Override
protected void onSaveInstanceState(Bundle outState) {
? ? ??super.onSaveInstanceState(outState);
? ? ? String tempData = "Something you just typed";
? ? ? outState.putString("data_key", tempData);
}
數據已經保存下來了,那么我們在哪里恢復呢?我們一直使用的onCreate方法其實也有一個Bundle類型的參數。這個參數在一般情況下都是null。但是如果在Activity被系統回收之前,我們通過 onSaveInstanceState方法保存數據,這個參數就會帶有之前的保存的數據,我們只需要通過相應的取值方法將數據去除,修改代碼如下:
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MyActivity extends AppCompatActivity {
? ? ? private static final String TAG = "MyActivity"; // 確保你有一個合適的TAG?
? ? ? super.onCreate(savedInstanceState);
? ? ? Log.d(TAG, "onCreate");
? ? ? setContentView(R.layout.activity_main);
? ? ? if (savedInstanceState != null) {
? ? ? ? ? ??String tempData = savedInstanceState.getString("data_key");
? ? ? ? ? ? ?Log.d(TAG, "tempData is " + tempData);
? ? ? ? ?}
? ? ?}??
}
取出值之后再做相應的恢復操作就可以了。
我們會發現在使用Bundle保存和取出數據的時候和使用Intent傳遞 也有類似的方法。這里Intent還可以結合Bundle一起用于傳遞數據。
首先我們可以把傳遞的數據都保存在Bundle對象中,然后再將Bundle對象存放在Intent里,到了目標的Activity之后,先從Intent中取出Bundle,再從Bundle中一一取出數據。
注:另外,當手機的屏幕旋轉的時候,Activity也會有一個重建的過程,所以這時候,數據也可能發生丟失的情況。
?
?