完全看懂 Android 四大元件之 Activity(上)

萌果愛吃芒果發表於2020-09-24

一、概述

簡單來講,Activity 就是一個視覺化介面,負責承建一個螢幕視窗,防止 UI 元件,供使用者互動。一般來說承建 Activity 有三個步驟:

  1. 承建 Activity 類;
  2. 在 AndroidManifest.xml 中註冊;
  3. 設定佈局檔案(可選)。

二、Activity 的啟動方法

2.1、顯示啟動

明確指定要啟動的 Activity 的 c l a s s \color{red}{class} class 或者 包 名 . a c t i v i t y 類 名 \color{red}{包名.activity類名} .activity,顯示啟動主要有三種方式:

  • 方式一:class 跳轉(最常用)
    Intent intent = new Intent(MainActivity.this, SecondActivity.class);
    startActivity(intent);
    
  • 方式二:包名.類名 跳轉
    Intent intent = new Intent();
    intent.setClassName(MainActivity.this, "com.zjgsu.activitydemo.SecondActivity");
    startActivity(intent);
    
  • 方式三:ComponentName 跳轉
    Intent intent = new Intent();
    intent.setComponent(new ComponentName(MainActivity.this, SecondActivity.class));
    startActivity(intent);
    

2.2、隱式啟動

設定啟動過濾器,通過指定的 a c t i o n \color{red}{action} action a c t i o n 和 d a t a \color{red}{action 和 data} actiondata 屬性,系統會查詢符合條件的 Activity,並啟動它,隱私啟動主要有兩種方式:

  • 方式一:傳入 actionName
    Intent intent = new Intent("abcd.SecondActivity");
    startActivity(intent);
    
  • 方式二:設定 action
    Intent intent = new Intent();
    intent.setAction("abcd.SecondActivity");
    startActivity(intent);
    

注意:如果自己定義的某個 Activity 要通過隱式啟動,在 AndroidManifest.xml 中必須加上 android.intent.category.DEFAULT,否則不起作用。

 <activity android:name=".SecondActivity">
     <intent-filter>
         <action android:name="abcd.SecondActivity"/>
         <category android:name="android.intent.category.DEFAULT"/>
     </intent-filter>
 </activity>

2.2.1、隱式啟動如果兩個 activity 的 actionName是一樣的會怎樣呢?

如下所示,我建立了兩個 Activity,然後這兩個 Activity 的 actionName 我設定成一樣的,然後我通過隱式啟動 Activity,會怎樣呢?

<activity android:name=".SecondActivity"
   android:label="第二個介面">
    <intent-filter>
        <action android:name="abcd.SecondActivity" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

<activity android:name=".ThirdActivity"
    android:label="第三個介面">
    <intent-filter>
        <action android:name="abcd.SecondActivity" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

執行效果如下所示:


隱式啟動Activity
看上面的 gif 動圖我們就可以看出來,這種情況下 Android 會讓我們選擇要執行的 Activity。即當有多個 Action 匹配隱式匹配的條件時,將會彈出選擇框供使用者選擇。

三、Activity 的生命週期

我們就以下面這張 Activity 生命週期圖為例子吧:

完全看懂 Android 四大元件之 Activity(上)


一個 Activity 啟動的時候會依次呼叫 onCreate() --> onStart() --> onResume() --> onPause() --> onStop() --> onDestroy()。當執行到 onResume() 的時候這個 Activity 就變成了可操作狀態。

3.1、單 Activity 生命週期的呼叫順序

  • onCreate()    建立 Activity 時呼叫
  • onStart()    當 Activity 介面變為使用者可見時呼叫
  • onResume()    當 Activity 介面獲取到焦點時呼叫(介面按鈕可點選,文字框可輸入等)
  • onPause()    當 Activity 介面失去焦點時呼叫(介面按鈕不可點選,文字框不可輸入等)
  • onStop()    當 Activity 介面變為使用者不可見時呼叫
  • onDestroy()    當 Activity 被銷燬時呼叫
  • onRestart()    當 Activity 再次啟動時呼叫

3.2、多 Activity 生命週期的呼叫順序

多Activity的生命週期如下圖所示:
多Activity生命週期
上面那張圖的操作流程:開啟 A activity,點選按鈕啟動 B activity,在 B activity 中點選返回鍵。程式碼執行截圖如下:
模擬

四、Activity 的啟動模式

Activity 的啟動模式決定了新生產的 Activity 例項是否重用已存在的 Activity 例項,是否和其他 Activity 例項共用一個 Task。Task 是一個具有棧結構的物件,一個 Task 可以管理多個 Activity,啟動一個應用,也就建立一個與之對應的 Task。

4.1、四種啟動模式

  1. standard
    預設的啟動模式,每次啟用 Activity 時(startActivity),都建立 Activity 例項,並放入任務棧;
  2. singleTop
    每次啟用 Activity 時,判斷該 Activity 例項是不是在棧頂,如果是,不需要建立,否則需要建立 Activity 例項;
  3. singleTask
    如果要啟用的那個 Activity 在任務棧中已經存在了,則不需要建立,只需要把此 Activity 以上的 Activity 例項都出棧,這個時候此 Activity 就到棧頂了,若不存在,就建立 Activity 例項;
  4. singleInstance
    只有一個例項,並且這個例項獨立執行在一個 Task 中,這個 Task 只有這個例項,不允許有別的 Activity 例項存在。

4.2、四種啟動模式程式碼演示

4.2.1、standard

我們先來看下效果:

在這裡插入圖片描述
可以看到,在 SecondActivity 裡面再次啟動 SecondActivity,standard 模式下的啟動方式會再次建立 SecondActivity 例項。使得任務棧中包含了兩個 SecondActivity 例項,所以我們點選返回鍵只是返回了上一個 SecondActivity,再次按返回鍵才回到 MainActivity。

主要程式碼如下所示:

// 啟動模式是 standard,不填預設就是 standard
<activity android:name=".SecondActivity"
	android:launchMode="standard"/>

// SecondActivity
public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        findViewById(R.id.btn_start_self).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SecondActivity.this, SecondActivity.class));
            }
        });
    }
}

4.2.2、singleTop

我們還是用上面的例子來演示,只是把 SecondActivity 的啟動模式改成 singleTop,我們來看下效果:

在這裡插入圖片描述
可以看到,這個時候在 SecondActivity 裡面再次啟動自己,是不會建立 SecondActivity 例項的,應該這個時候 SecondActivity已經在棧頂了,而 singleTop 啟動模式會判斷該 Activity 例項是不是在棧頂,如果是,不需要建立,否則才建立 Activity 例項。

主要程式碼如下所示:

<activity android:name=".SecondActivity"
	android:launchMode="singleTop"/>

問題1:如果這時候我們再建立一個 ThirdActivity,在 MainActivity 啟動 SecondActivity,再在 SecondActivity 裡面啟動 ThirdActivity,然後在 ThirdActivity 裡面啟動 SecondActivity,這時候任務棧中 Activity 的例項順序是怎樣的?

:任務棧中自底向上的 Activity 例項順序為 MainActivity–>SecondActivity–>ThirdActivity–>SecondActivity。

4.2.3、singleTask

我們用上面的問題的例子來演示,只是把 SecondActivity 的啟動模式改成 singleTask,我們來看下效果:

在這裡插入圖片描述
這個時候任務棧中自底向上的 Activity 例項順序為 MainActivity–>SecondActivity。因為 SingleTask 啟動模式如果棧中存在 Activity 例項會把該 Activity 以上的所有 Activity 例項全部出棧。

主要程式碼如下所示:

<activity android:name=".SecondActivity"
	android:launchMode="singleTask"/>

4.2.4、singleInstance

singleInstance 啟動模式和 singleTask 啟動模式有點像,都會保證任務棧中只有同一個 Activity 的例項,但是 singleInstance 會建立一個新的 Task。要想演示 singleInstance,我們需要把每個 Activity 所在的棧的 ID 列印出來。例子同上,只是修改啟動模式為 singleInstance。

列印日誌如下所示:
singleInstance
我們看到 SecondActivity 所在任務棧的 TaskId 是592,而 MainActivity 和 ThirdActivity 所在任務棧的 TaskId 是 591。這就說明 SecondActivity 執行在一個獨立的任務棧裡面,這個任務棧裡面只有 SecondActivity 這一個 Activity 例項。列印 TaskId 的方法如下:

Log.e("activityDemo2TAG", "ThirdActivity所在的task的id為:" + getTaskId());

五、利用 IntentFlag 設定 Activity 的啟動方式

講解這個知識點之前我們先來看一下 Task 和 taskAffinity 這兩個概念、

5.1、Task 基本概念

  • Task 是一個具有棧結構的容器,可以放置多個 Activity 例項;
  • 啟動一個應用,系統會為之建立一個 Task,來放置根 Activity;
  • 一個 Activity 啟動另一個 Activity 時,預設情況下兩個 Activity 是放置在同一個 Task 中的,後者被壓入前者所在的 Task 棧,當使用者按下返回鍵,後者從 Task 中被彈出,前者又顯示在棧頂。

5.2、taskAffinity 基本概念

  • 定義了 Activity 例項想要進入的 Task;
  • 如果一個 Activity 沒有顯示的指明該 Activity 的 taskAffinity 屬性,那麼它的這個屬性就等於 Application 所指明的 taskAffinity,如果 Application 也沒有指明,那麼該 taskAffinity 的值就等於包名。

5.3、IntentFlag 的常用值

IntentFlag 的種類很多,我們這裡就只挑選幾個平時常用的來講解一下。

5.3.1、FLAG_ACTIVITY_NEW_TASK

系統會尋找或建立一個新的 Task 來放置目標 Activity,尋找時依據目標 Activity 的 taskAffinity 屬性進行匹配,如果找到一個 Task 的 taskAffinity 與之相同,就將目標壓入此 Task 中,如果查詢無果,則建立一個新的 Task,並將該 Task 的 taskAffinity 值設定為目標 Activity 的 taskAffinity,將目標 Activity 放置於此 Task 中 。

5.3.1.1、程式碼演示

設定 IntentFlag 的方法:

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

第 一 種 情 況 \color{red}{第一種情況} :以 FLAG_ACTIVITY_NEW_TASK 方式啟動 SecondActivity,但是 SecondActivity 不新增 taskAffinity 屬性,我們來看下效果:
效果
可以看到,我們雖然以 FLAG_ACTIVITY_NEW_TASK 啟動,但是兩個 Activity 所在的 Task 還是同一個,這是為什麼呢?那是因為如果一個 Activity 沒有顯示的指明該 Activity 的 taskAffinity 屬性,那麼它的這個屬性就等於 Application 所指明的 taskAffinity,如果 Application 也沒有指明,那麼該 taskAffinity 的值就等於包名。 也就是說此時 SecondActivity 和 MainActivity 兩個 taskAffinity 的值都是包名,所以 SecondActivity 肯定會被放入 MainActivity 所在的棧中了。

第 二 種 情 況 \color{red}{第二種情況} :以 FLAG_ACTIVITY_NEW_TASK 方式啟動 SecondActivity,並且 SecondActivity 設定 taskAffinity 屬性:

<activity android:name=".SecondActivity" 
	android:taskAffinity="flag.newIntent.test"/>

我們來看下效果:
效果二
可以看到這時候 SecondActivity 和 MainActivity 已經不在同一個棧裡面了。

5.3.2、FLAG_ACTIVITY_SINGLE_TOP

同四種啟動模式中的 singleTop,這裡就不演示了。

5.3.3、FLAG_ACTIVITY_CLEAR_TOP

同四種啟動模式中的 singleTask,這裡也不演示。

5.3.4、FLAG_ACTIVITY_REORDER_TO_FRONT

這個啟動模式的意思是如果棧中已經存在 Activity 例項,會將它拿到棧頂,不會啟動新 Activity,也不會刪除它之上的 Activity 例項。

5.3.4.1、程式碼演示

我們先來看下效果:

在這裡插入圖片描述
我們在 MainActivity 裡面啟動 SecondActivity,然後在 SecondActivity 裡面啟動 ThirdActivity,然後再在 ThirdActivity 裡面通過 FLAG_ACTIVITY_REORDER_TO_FRONT 的方式啟動 SecondActivity,這個時候因為棧中已經有 SecondActivity 例項了,所以會把該例項拿到棧頂,並且不會銷燬 SecondActivity 之上的所有例項,所以此時棧自底向上的 Activity 例項順序是 MainActivity --> ThirdActivity --> SecondActivity。

六、小結

這篇文章主要講了 Activity 的五種啟動方法,包括三種顯示啟動和兩種隱式啟動,又講了單和多 Activity 的生命週期,以及在 AndroidManifest 中通過 lunchMode 設定 Activity 的啟動模式和通過Intent.setFlag() 方法設定 Activity 的啟動模式。

下一篇文章我們將會講解在 Activity 中利用 Intent 傳參利用Bundle傳遞資料複雜資料的傳遞以及啟動系統 Activity的方法

相關文章