什么是 Activity?
Activity 是 Android 的四大組件之一,是用戶操作的可視化界面,它為用戶提供了一個完成操作指令的窗口。
當我們創建完 Activity 之后,需要調用 setContentView(view)
方法來完成界面的顯示,以此來為用戶提供交互的入口。在 Android App 中只要能看見的幾乎都要依托于 Activity,所以 Activity 是在開發中使用最頻繁的一種組件。
生命周期
生命周期就是 Activity 從開始到結束所經歷的各個狀態,從一個狀態到另一個狀態的轉變,從無到有再到無,這樣一個過程中所經歷的狀態就叫做生命周期。
Acitivity 本質上有四種狀態:
- 運行:如果一個活動被移到了前臺(活動棧頂部)。
- 暫停:如果一個活動被另一個非全屏的活動所覆蓋(比如一個 Dialog),那么該活動就失去了焦點,它將會暫停(但它仍然保留所有的狀態和成員信息,并且仍然是依附在 WindowsManager 上),在系統內存積極缺乏的時候會將它殺死。
- 停止:如果一個活動被另一個全屏活動完全覆蓋,那么該活動處于停止狀態(狀態和成員信息會保留,但是 Activity 已經不再依附于 WindowManager 了)。同時,在系統缺乏資源的時候會將它殺死(它會比暫停狀態的活動先殺死)。
- 重啟:如果一個活動在處于停止或者暫停的狀態下,系統內存缺乏時會將其結束(finish)或者殺死(kill)。這種非正常情況下,系統在殺死或者結束之前會調用
onSaveInstanceState()
方法來保存信息,同時,當 Activity 被移動到前臺時,重新啟動該Activity并調用onRestoreInstanceState()
方法加載保留的信息,以保持原有的狀態。
在上面的四中常有的狀態之間,還有著其他的生命周期來作為不同狀態之間的過度,用于在不同的狀態之間進行轉換。
正常情況下的生命周期:
-
onCreate():與 onDestroy() 配對,表示 Activity 正在被創建,這是生命周期的第一個方法。
在這個方法中可以做一些初始化的工作(加載布局資源、初始化 Activity 所需要的數據等),耗時的工作在異步線程上完成。
-
onRestart():表示 Activity 正在重新啟動。
一般情況下,在當前 Activity 從不可見重新變為可見的狀態時 onRestart() 就會被調用。這種情形一般是由于用戶的行為所導致的,比如用戶按下 Home 鍵切換到桌面或者打開了一個新的 Activity(這時當前 Activity 會暫停,也就是 onPause() 和 onStop() 被執行),接著用戶有回到了這個 Activity,就會出現這種情況。
-
onStart():與 onStop() 配對,表示 Activity 正在被啟動,并且即將開始。
但是這個時候要注意它與 onResume() 的區別。兩者都表示 Activity 可見,但是 onStart() 時 Activity 還正在加載其他內容,正在向我們展示,用戶還無法看到,即無法交互。
-
onResume():與 onPause() 配對,表示 Activity 已經創建完成,并且可以開始活動了,這個時候用戶已經可以看到界面了,并且即將與用戶交互(完成該周期之后便可以響應用戶的交互事件了)。
-
onPause():與 onResume() 配對,表示 Activity 正在暫停,正常情況下,onStop() 接著就會被調用。
在特殊情況下,如果這個時候用戶快速地再回到當前的 Activity,那么 onResume() 會被調用(極端情況)。一般來說,在這個生命周期狀態下,可以做一些存儲數據、停止動畫的工作,但是不能太耗時,如果是由于啟動新的 Activity 而喚醒的該狀態,那會影響到新 Activity 的顯示,原因是 onPause() 必須執行完,新的 Activity的 onResume() 才會執行。
-
onStop():與 onStart() 配對,表示 Activity 即將停止,可以做一些稍微重量級的回收工作,同樣也不能太耗時(可以比 onPause 稍微好一點)。
-
onDestroy():與 onCreate() 配對,表示 Activity 即將被銷毀,這是 Activity 生命周期的最后一個回調,我們可以做一些回收工作和最終的資源釋放(如 Service、BroadReceiver、Map 等)。
啟動模式
Activity 的啟動模式有4種,分別是 Standard、SingleTop、SingleTask、SingleInstance。可以在 AndroidMainifest.xml
文件中指定每一個 Activity 的啟動模式。
一個 Android 應用一般都會有多個 Activity,系統會通過任務棧來管理這些 Activity,棧是一種后進先出的集合,當前的 Activity 就在棧頂,按返回鍵,棧頂 Activity 就會退出。Activity 啟動模式不同,系統通過任務棧管理 Activity 的方式也會不同,以下將分別介紹。
- Standard
Standard 模式是 Android 的默認啟動模式,你不在配置文件中做任何設置,那么這個 Activity 就是 Standard 模式。這種模式下,Activity 可以有多個實例,每次啟動 Activity,無論任務棧中是否已經有這個 Activity 的實例,系統都會創建一個新的 Activity 實例。
- SingleTop
SingleTop 模式和 Standard 模式非常相似,主要區別就是當一個 SingleTop 模式的 Activity 已經位于任務棧的棧頂,再去啟動它時,不會再創建新的實例。如果不位于棧頂,就會創建新的實例。
- SingleTask
SingleTask 模式的 Activity 在同一個 Task 內只有一個實例。如果 Activity 已經位于棧頂,系統不會創建新的 Activity 實例,和 SingleTop 模式一樣。但 Activity 已經存在但不位于棧頂時,系統就會把該 Activity 移到棧頂,并把它上面的 Activity 出棧。
- SingleInstance
SingleInstance 模式也是單例的,但和 SingleTask 不同,SingleTask 只是任務棧內單例,系統里是可以有多個 SingleTask Activity 實例的,而 SingleInstance Activity 在整個系統里只有一個實例,啟動一個SingleInstance 的 Activity 時,系統會創建一個新的任務棧,并且這個任務棧只有他一個 Activity。
SingleInstance 模式并不常用,如果我們把一個 Activity 設置為 SingleInstance 模式,你會發現它啟動時會慢一些,切換效果不好,影響用戶體驗。它往往用于多個應用之間,例如一個電視 Launcher 里的 Activity,通過遙控器某個鍵在任何情況可以啟動,這個 Activity 就可以設置為 SingleInstance 模式,當在某應用中按鍵啟動這個 Activity,處理完后按返回鍵,就會回到之前啟動它的應用,不影響用戶體驗。
任務與返回棧
一個應用程序當中通常會包含多個 Activity,每個 Activity 都應該設計成可以執行用戶特定的操作,并且能夠啟動其他 Activity。比如,電子郵件應用可能有一個 Activity 顯示新郵件列表。用戶選擇某個郵件時,會打開一個新的 Activity 來查看該郵件。
一個 Activity 甚至可以啟動設備上其他應用中的 Activity。比如,如果當前應用想要發送電子郵件,就可以發送一個 Intent 添加一些數據(如郵箱和郵件內容等)。然后,系統將打開其他應用中已經聲明可以處理該 Intent 的 Activity,如果有多個系統則讓用戶選擇要使用的 Activity。即使這兩個 Activity 可能來自不同的應用,但是 Android 仍會將 Activity 保留在相同的任務中,以維護這種無縫的用戶體驗。
任務是指在執行特定作業時與用戶交互的一系列 Activity。 這些 Activity 按照各自的打開順序排列在堆棧(即返回棧)中。
設備主屏幕是大多數任務的起點。當用戶觸摸應用啟動器中的圖標(或主屏幕上的快捷方式)時,該應用的任務將出現在前臺。 如果應用不存在任務(應用最近未曾使用),則會創建一個新任務,并且該應用的“主” Activity 將作為堆棧中的根 Activity 打開。
當前 Activity 啟動另一個 Activity 時,該新 Activity 會被推送到堆棧頂部,并獲取焦點。前一個 Activity 仍保留在堆棧中,但是處于停止狀態。Activity 停止時,系統會保持其用戶界面的當前狀態。 用戶按「返回」按鈕時,當前 Activity 會從堆棧頂部彈出(Activity 被銷毀),而前一個 Activity 恢復執行(恢復其 UI 的前一狀態)。
堆棧中的 Activity 永遠不會重新排列,僅推入和彈出堆棧:由當前 Activity 啟動時推入堆棧;用戶使用「返回」按鈕退出時彈出堆棧。 因此,返回棧以「后進先出」對象結構運行。 如下圖:
如果用戶繼續按「返回」,堆棧中的相應 Activity 就會彈出,以顯示前一個 Activity,直到用戶返回主屏幕為止(或者,返回任務開始時正在運行的任意 Activity)。 當所有 Activity 均從堆棧中移除后,任務即不復存在。
任務是一個有機整體,當用戶開始新任務或通過「主頁」按鈕轉到主屏幕時,可以移動到「后臺」。 盡管在后臺時,該任務中的所有 Activity 全部停止,但是任務的返回棧仍舊不變,也就是說,當另一個任務發生時,該任務僅僅失去焦點而已,如下圖所示。然后,任務可以返回到「前臺」,用戶就能夠回到離開時的狀態。
例如,假設當前任務(任務 A)的堆棧中有三個 Activity,即當前 Activity 下方還有兩個 Activity。 用戶先按「主頁」按鈕,然后從應用啟動器啟動新應用。 顯示主屏幕時,任務 A 進入后臺。新應用啟動時,系統會使用自己的 Activity 堆棧為該應用啟動一個任務(任務 B)。與該應用交互之后,用戶再次返回主屏幕并選擇最初啟動任務 A 的應用。現在,任務 A 出現在前臺,其堆棧中的所有三個 Activity 保持不變,而位于堆棧頂部的 Activity 則會恢復執行。 此時,用戶還可以通過轉到主屏幕并選擇啟動該任務的應用圖標(或者,通過從「屏幕預覽」擇該應用的任務)切換回任務 B。這就是 Android 系統中的多任務的場景。
無論 Activity 是在新任務中啟動,還是在與啟動 Activity 相同的任務中啟動,用戶按「返回」按鈕始終會轉到前一個 Activity。 但是,如果啟動指定 singleTask
啟動模式的 Activity,則當某后臺任務中存在該 Activity 的實例時,整個任務都會轉移到前臺。此時,返回棧包括上移到堆棧頂部的任務中的所有 Activity,如下圖:
保存與恢復
Activity 為我們提供了兩個回調方法 onSaveInstanceState() 和 onRestoreInstanceState() 用于當 Activity 在不是用戶主動意識關閉的情況下來進行頁面數據的保存和恢復。
那么那些情況下 onSaveInstanceState() 會被調用呢?分別有以下幾種情況:
- 當用戶按下 Home 鍵 App 處于后臺,此時會調用 onSaveInstanceState() 方法。
- 當用戶按下電源鍵時,會調用 onSaveInstanceState() 方法。
- 當 Activity 進行橫豎屏切換的時候也會調用 onSaveInstanceState() 方法。
- 從 AActivity 跳轉到 BActivity 的時候 AActivity 也會調用 onSaveInstanceState() 方法。
雖然以上四種情況會執行 onSaveInstanceState() 方法 但是并不是都會執行 onRestoreInstanceState() 方法,只有第三種情況會調用 onRestoreInstanceState(),因為當 Activity 橫豎屏切換的時候會重新走一遍生命周期,所以 Activity 會被銷毀創建,由此會執行 onRestoreInstanceState() 方法。
也就是說 onSaveInstanceState 和 onRestoreInstanceState 并不是一定成雙出現的,終于當 Activity 真正的被銷毀的時候才會執行 onRestoreInstanceState()。
而其他情況 Activity 只是暫居后臺,并沒有被銷毀,所以系統不會調用 onRestoreInstanceState()。
保存數據:
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {// 保存數據savedInstanceState.putString("data", "這是保存的數據");super.onSaveInstanceState(savedInstanceState);
}
復制代碼
恢復數據:
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 恢復數據if (savedInstanceState != null) {String data = savedInstanceState.getInt("data");}
}@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 也可以在 onRestoreInstanceState() 方法中恢復數據if (savedInstanceState != null) {String data = savedInstanceState.getInt("data");}
}
復制代碼
Intent
Intent 分兩種,顯式 Intent 和隱式 Intent。如果一個 Intent 明確指定了要啟動的組件的完整類名,那么這個 Intent 就是顯式 Intent,否則就是隱式 Intent。
當我們用一個顯式 Intent 去啟動組件時,Android 會根據 Intent 對象所提供的 component name 直接找到要啟動的組件,當我們用一個隱式的 Intent 去啟動組件時,Android 系統就無法直接知道要啟動的組件名稱了。
顯式 Intent
Intent intent = new Intent(this, xxx.class);
startActivity(intent);
復制代碼
隱式 Intent
使用隱式 Intent 之前需要在 AndroidManifest.xml 中對標簽增加設置。
<activity android:name=".ui.activity.IntentActivity"><intent-filter><action android:name="com.jeanboy.action.TEST" /></intent-filter>
</activity>
復制代碼
使用隱式 Intent 跳轉 Activity。
Intent intent = new Intent("com.jeanboy.action.TEST");
startActivity(intent);
復制代碼
Intent Filter
如果 Intent 中的存在 category 那么所有的 category 都必須和 Activity 過濾規則中的 category 相同。才能和這個 Activity 匹配。Intent 中的 category 數量可能少于 Activity 中配置的 category 數量,但是 Intent 中的這 category 必須和 Activity 中配置的 category 相同才能匹配。
<activity android:name=".ui.activity.IntentActivity"><intent-filter><action android:name="com.jeanboy.action.TEST" /><category android:name = "android.intent.category.DEFAULT" /><category android:name="aaa.bb.cc"/></intent-filter>
</activity>
復制代碼
運行以下代碼可以匹配到 IntentActivity:
Intent intent = new Intent("com.jeanboy.action.TEST");
intent.addCategory("aaa.bb.cc");
startActivity(intent);
復制代碼
只通過 category 匹配是無法匹配到 IntentActivity 的,因為 category 屬性是一個執行 Action 的附加信息。
URL Scheme
Android 中的 Scheme 是一種頁面內跳轉協議,是一種非常好的實現機制。通過定義自己的 Scheme 協議,可以非常方便跳轉 App 中的各個頁面。
使用場景:
- 通過小程序,利用 Scheme 協議打開原生 App。
- H5 頁面點擊錨點,根據錨點具體跳轉路徑 App 端跳轉具體的頁面。
- App 端收到服務器端下發的 Push 通知欄消息,根據消息的點擊跳轉路徑跳轉相關頁面。
- App 根據URL跳轉到另外一個 App 指定頁面。
- 通過短信息中的 URL 打開原生 App。
Scheme 路徑的規則:
<scheme> :// <host> : <port> [<path>|<pathPrefix>|<pathPattern>]
設置 Scheme
在 AndroidManifest.xml 中對標簽增加設置 Scheme。
<activityandroid:name=".ui.activity.SchemeActivity"android:screenOrientation="portrait"><!--Android 接收外部跳轉過濾器--><!--要想在別的 App 上能成功調起 App,必須添加 intent 過濾器--><intent-filter><!--協議部分配置,注意需要跟 web 配置相同--><!--協議部分,隨便設置 aa://bb:1024/from?type=jeanboy--><data android:scheme="aa"android:host="bb"android:port="1024"android:path="/from"/><!--下面這幾行也必須得設置--><category android:name="android.intent.category.DEFAULT" /><!--表示 Activity 允許通過網絡瀏覽器啟動,以顯示鏈接方式引用,如圖像或電子郵件--><category android:name="android.intent.category.BROWSABLE" /><action android:name="android.intent.action.VIEW" /></intent-filter>
</activity>
復制代碼
原生調用:
Uri uri = Uri.parse("aa://bb:1024/from?type=jeanboy");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
復制代碼
網頁調用:
<a href="aa://bb:1024/from?type=jeanboy">打開 App</a>
復制代碼
在 SchemeActivity 中可以處理 Scheme 跳轉的參數:
public class SchemeActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);Uri uri = getIntent().getData();if (uri != null) {//獲取指定參數值String type = uri.getQueryParameter("type");Log.e("SchemeActivity", "type:" + type);if(type.equals("jeanboy")){ActivityUtils.startActivity(XXXActivity.class);}else if(type.equals("main")){ActivityUtils.startActivity(MainActivity.class);}}finish();}
}
復制代碼
如何判斷一個 Scheme 是否有效:
PackageManager packageManager = getPackageManager();
Uri uri = Uri.parse("aa://bb:1024/from?type=jeanboy");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isValid = !activities.isEmpty();
if (isValid) {startActivity(intent);
}
復制代碼
startActivityForResult()
如果想在 Activity 中得到新打開 Activity 關閉后返回的數據,需要使用系統提供的 startActivityForResult()
方法打開新的 Activity,新的 Activity 關閉后會向前面的 Activity 傳回數據。
@Override
public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//...int resultCode = 1;startActivityForResult(new Intent(this, OtherActivity.class), resultCode);
}/*** 接收 OtherActivity 返回的數據*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {String result = data.getExtras().getString("result");
}
復制代碼
為了得到傳回的數據,必須在前面的 Activity 中重寫 onActivityResult()
方法。
Intent intent = new Intent();
// 把返回數據存入 Intent
intent.putExtra("result", "我是返回數據");
// 設置返回數據
setResult(RESULT_OK, intent);
// 關閉當前 Activity
finish();
復制代碼
常見面試題
-
啟動一個 Activity 的生命周期?
例如:A 啟動 B,生命周期如下
A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onPause()B: ==> onCreate() B: ==> onStart() B: ==> onResume()A: ==> onStop() 復制代碼
-
下拉通知欄對生命周期的影響?
沒有影響!
-
AlertDialog(對話框)對生命周期的影響?
沒有影響!
-
Toast 對生命周期的影響?
沒有影響!
-
透明主題的 Activity 對生命周期的影響?
A: ==> onCreate() A: ==> onStart() A: ==> onResume() 如果彈出透明 Activity A: ==> onPause() 復制代碼
-
屏幕旋轉對生命周期的影響?
沒有配置 configChanges:
A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onPause() A: ==> onSaveInstanceState() A: ==> onStop() A: ==> onDestroy() 屏幕旋轉后 A: ==> onCreate() A: ==> onStart() A: ==> onRestoreInstanceState() A: ==> onResume() 復制代碼
配置 configChanges 后:
A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onConfigurationChanged() 復制代碼
參考資料
- Android 官方文檔 - Activity 生命周期
- Android 官方文檔 - 任務和返回棧
我的 GitHub
github.com/jeanboydev
我的公眾號
歡迎關注我的公眾號,分享各種技術干貨,各種學習資料,職業發展和行業動態。
技術交流群
歡迎加入技術交流群,來一起交流學習。