第1章 Activity的生命週期和啓動模式
作爲本書的第1章,本章主要介紹Activity相關的一些內容。Activity作爲四大組件之 首,是使用最爲頻繁的一種組件,中文直接翻譯爲“活動",但是筆者認爲這種翻譯有些生硬,如果翻譯成界面就會更好理解。正常情況下,除了 Window、Dialog和Toast,我們能 見到的界面的確只有Activity。Activity是如此重要,以至於本書開篇就不得不講到它。
當然,由於本書的定位爲進階書,所以不會介紹如何啓動Activity這類入門知識,本章的側重點是Activity在使用過程中的一些不容易搞清楚的概念,主要包括生命週期和啓動模式以及IntentFilter的匹配規則分析。其中Activity在異常情況下的生命週期是十分微妙的, 至於Activity的啓動模式和形形色色的Flags更是讓初學者摸不到頭腦,就連隱式啓動 Activity中也有着複雜的Intent匹配過程,不過不用擔心,本章接下來將一一解開這些疑難 問題的神祕面紗。
1.1 Activity的生命週期全面分析
本節將Activity的生命週期分爲兩部分內容,一部分是典型情況下的生命週期,另一部分是異常情況下的生命週期。
所謂典型情況下的生命週期,是指在有用戶參與的情況下,Activity所經過的生命週期的改變;
而異常情況下的生命週期,是指Activity被系統回收 或者 由於當前設備的Configuration發生改變從而導致Activity被銷燬重建,異常情況下的生命週期的關注點和典型情況下略有不同。
1.1.1典型情況下的生命週期分析
在正常情況下,Activity會經歷如下生命週期。
- onCreate:表示Activity正在被創建,這是生命週期的第一個方法。在這個方法中, 我們可以做一些初始化工作,比如調用setContentView去加載界面佈局資源、初始化Activity 所需數據等。
- onRestart:表示Activity正在重新啓動。一般情況下,噹噹前Activity從不可見重新變爲可見狀態時,onRestart 就會被調用。這種情形一般是用戶行爲所導致的,比如用戶按Home鍵切換到桌面或者用戶打開了一個新的 Activity,這時當前的 Activity 就會暫停,也就是 onPause 和 onStop 被執行了,接着用戶又回到了這個 Activity,就會出現這種情況。
- onStart:表示Activity正在被啓動,即將開始,這時 Activity 已經可見了,但是還沒有出現在前臺,還無法和用戶交互。這個時候其實可以理解爲Activity已經顯示出來了,但是我們還看不到。
- onResume:表示Activity已經可見了,並且出現在前臺並開始活動。要注意這個和 onStart 的對比,onStart 和 onResume 都表示Activity已經可見,但是 onStart 的時候 Activity 還在後臺,onResume 的時候 Activity 才顯示到前臺。
- onPause:表示 Activity 正在停止,正常情況下,緊接着 onStop 就會被調用。在特殊情況下,如果這個時候快速地再回到當前Activity,那麼onResume會被調用。筆者的理解是,這種情況屬於極端情況,用戶操作很難重現這一場景。此時可以做一些存儲數據、 停止動畫等工作,但是注意不能太耗時,因爲這會影響到新Activity的顯示,onPause必須先執行完,新Activity的onResume纔會執行。
- onStop:表示Activity即將停止,可以做一些稍微重量級的回收工作,同樣不能太耗時。
- onDestroy:表示Activity即將被銷燬,這是Activity生命週期中的最後一個回調, 在這裏,我們可以做一些回收工作和最終的資源釋放。
正常情況下,Activity的常用生命週期就只有上面7個,圖1-1更詳細地描述了 Activity 各種生命週期的切換過程。
針對圖1-1,這裏再附加一下具體說明,分如下幾種情況。
(1)針對一個特定的 Activity,第一次啓動,回調如下:onCreate -> onStart -> onResume。
(2)當用戶打開新的Activity或者切換到桌面的時候,回調如下:onPause -> onStop 。這裏有一種特殊情況,如果新Activity釆用了透明主題,那麼當前Activity不會回調onStop。
(3)當用戶再次回到原 Activity 時,回調如下:onRestart-> onStart -> onResume。
(4)當用戶按back鍵回退時,回調如下:onPause -> onStop -> onDestroy。
(5)當Activity被系統回收後再次打開,生命週期方法回調過程和(1)一樣,注意只是生命週期方法一樣,不代表所有過程都一樣,這個問題在下一節會詳細說明。
(6)從整個生命週期來說,onCreate 和 onDestroy 是配對的,分別標識着 Activity 的創建和銷燬,並且只可能有一次調用。從 Activity 是否可見來說,onStart 和 onStop 是配對的,隨着用戶的操作或者設備屏幕的點亮和熄滅,這兩個方法可能被調用多次;從 Activity 是否在前臺來說 onResume 和 onPause 是配對的,隨着用戶操作或者設備屏幕的點亮和熄滅, 這兩個方法可能被調用多次。
這裏提出2個問題,不知道大家是否清楚。
問題1:onStart和onResume、onPause和onStop從描述上來看差不多,對我們來說有什麼實質的不同呢?
問題2:假設當前 Activity 爲A,如果這時用戶打開一個新 Activity B,那麼 B 的 onResume 和 A 的 onPause 哪個先執行呢?
先說第一個問題,從實際使用過程來說,onStart 和 onResume、onPause 和 onStop 看起來的確差不多,甚至我們可以只保留其中一對,比如只保留 onStart 和 onStop 。既然如此, 那爲什麼Android系統還要提供看起來重複的接口呢?根據上面的分析,我們知道,這兩個配對的回調分別表示不同的意義,onStart 和 onStop 是從 Activity 是否可見這個角度來回調的,而 onResume 和 onPause 是從Activity是否位於前臺這個角度來回調的,除了這種區別,在實際使用中沒有其他明顯區別。
第二個問題可以從 Android 源碼裏得到解釋。關於 Activity 的工作原理在本書後續章節會進行介紹,這裏我們先大概瞭解即可。從Activity的啓動過程來看,我們來看一下系統源碼。Activity的啓動過程的源碼相當複雜,涉及Instrumentation、ActivityThread和ActivityManagerService(下面簡稱AMS)。這裏不詳細分析這一過程,簡單理解,啓動 Activity 的請求會由 Instrumentation 來處理,然後它通過 Binder 向 AMS 發請求,AMS內部維護着一個 ActivityStack 並負責棧內的 Activity 的狀態同步,AMS 通過 ActivityThread 去同步 Activity 的狀態從而完成生命週期方法的調用。在 ActivityStack 中的 resumeTopActivity-InnerLocked 方法中,有這麼一段代碼:
從上述代碼可以看出,在新 Activity 啓動之前,棧頂的 Activity 需要先 onPause 後,新 Activity 才能啓動。
最終,在 ActivityStackSupervisor 中的 realStartActivityLocked 方法會調用如下代碼。
我們知道,這個 app.thread 的類型是 IApplicationThread,而 IApplicationThread 的具體實現是 ActivityThread 中的 ApplicationThread 。所以,這段代碼實際上調到了 ActivityThread 的中,即 ApplicationThread 的 scheduleLaunchActivity 方法,而 scheduleLaunchActivity 方法最終會完成新 Activity 的 onCreate、onStart、onResume 的調用過程。因此,可以得出結論, 是舊 Activity 先 onPause,然後新Activity 再啓動。
至於 ApplicationThread 的 scheduleLaunchActivity 方法爲什麼會完成新 Activity 的 onCreate、onStart、onResume 的調用過程,請看下面的代碼。scheduleLaunchActivity 最終會調用如下方法,而如下方法的確會完成 onCreate、onStart、onResume 的調用過程。
從上面的分析可以看出,當新啓動一個 Activity 的時候,舊 Activity 的 onPause 會先執行,然後纔會啓動新的 Activity 到底是不是這樣呢? 我們寫個例子驗證一下,如下是2個 Activity 的代碼,在 MainActivity 中單擊按鈕可以跳轉到 Second Activity,同時爲了分析我們的問題,在生命週期方法中打印出了日誌,通過日誌我們就能看出它們的調用順序。
代碼:MainActivity.java
package com.yyh.demo1.activitysample;
import com.ryg.chapter_1.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class MainActivity extends Activity {
public static final String TAG = "MainActivity@@@";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_demo1);
findViewById(R.id.btnTo).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
}
@Override
protected void onPause() {
super.onPause();
Log.i(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG, "onStop");
}
}
代碼:SecondActivity.java
package com.yyh.demo1.activitysample;
import com.ryg.chapter_1.R;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class SecondActivity extends Activity {
private static final String TAG = "SecondActivity@@@";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_demo1);
Log.i(TAG, "onCreate");
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, "onResume");
}
}
我們來看一下log,是不是和我們上面分析的一樣,如圖1-2所示。
通過圖1-2可以發現,舊 Activity 的 onPause 先調用,然後新 Activity 才啓動,這也證實了我們上面的分析過程。也許有人會問,你只是分析了 Android5.0 的源碼,你怎麼知道所有版木的源碼都是相同邏輯呢?關於這個問題,我們的確不大可能把所有版本的源碼都分析一遍,但是作爲Android運行過程的基本機制,隨着版本的更新並不會有大的調整, 因爲Android系統也需要兼容性,不能說在不同版本上同一個運行機制有着截然不同的表現。關於這一點我們需要把握一個度,就是對於Android運行的基本機制在不同 Android 版本上具有延續性。從另一個角度來說,Android官方文檔對 onPause 的解釋有這麼一句:不能在 onPause 中做重量級的操作,因爲必須 onPause 執行完成以後新 Activity 才能 Resume,從這一點也能間接證明我們的結論。通過分析這個問題,我們知道 onPause 和 onStop 都不能執行耗時的操作,尤其是 onPause方法,這也意味着,我們應當儘量在 onStop 中做操作,從而使得新 Activity 儘快顯示出來並切換到前臺。
1.1.2異常情況下的生命週期分析
上一節我們分析了典型情況下 Activity 的生命週期,本節我們接着分析 Activity 在異常情況下的生命週期。我們知道,Activity除了受用戶操作所導致的正常的生命週期方法調度, 還有一些異常情況,比如當資源相關的系統配置發生改變以及系統內存不足時,Activity就可能被殺死。下面我們具體分析這兩種情況。
1.情況1:資源相關的系統配置發生改變導致Activity被殺死並重新創建
理解這個問題,我們首先要對系統的資源加載機制有一定了解,這裏不詳細分析系統 的資源加載機制,只是簡單說明一下。拿最簡單的圖片來說,當我們把一張圖片放在 drawable目錄後,就可以通過Resources去獲取這張圖片。同時爲了兼容不同的設備,我們 可能還需要在其他一些目錄放置不同的圖片,比如drawable-mdpi、drawable-hdpi、 drawable-land等。這樣,當應用程序啓動時,系統就會根據當前設備的情況去加載合適的