天天看點

Android 四大元件 之 Activity二、 Activity 啟動模式三、IntentFilter 比對規則

子曰:溫故而知新,可以為師矣。 《論語》-- 孔子

一、

Activity

生命周期

先來放一張最經典的圖

Android 四大元件 之 Activity二、 Activity 啟動模式三、IntentFilter 比對規則

1.1 典型情況下生命周期分析

  • onCreate

    Activity

    生命周期的第一步。此方法中,常見操作有

    setContView()

    加載布局資源;業務需求需要的資料做初始化操作,例如

    setText()

    setImage()

    等等。
  • onRestart()

    :表示某一個背景

    Activity

    重新切換到了前台,即從背景不可見狀态變成前台可見狀态。
  • onStart()

    Activity

    正在被啟動,背景可見,但并未出現在前台,即相對使用者不可見,使用者無法與此

    Activity

    界面互動。
  • onResume()

    Activity

    可見且出現在前台,使用者可互動。
  • onPause()

    Activity

    正在停止,正常情況下,緊接着就會調用

    onStop()

    方法。此方法中,可以做一些存儲資料,停止動畫等操作,但是不能太耗時。
  • onStop()

    Activity

    處于停滞狀态,可以做一些稍微重量級的操作,但是也不能太耗時。
  • onDestroy()

    Activity

    即将被銷毀,可以做一些回收工作和最終資源的釋放。

不同場景下的生命周期

1) 第一次啟動某一個

Activity

:

onCreate --> onStart --> onResume
           

2)

Activity

A 跳轉到

Activity

B(不是透明的

Activity

) :

onPause(Activity A)--> onCreate(Activity B)--> onStart(Activity B)--> onStop(Activity A)--> onResume(B)
           

3)

Activity

A 跳轉到

Activity

B(透明的

Activity

onPause(Activity A)--> onCreate(Activity B)--> onStart(Activity B)--> onResume(B)
           

4)背景

Activity

進入 前台(背景

Activity

并未因為系統記憶體不足被殺死):

onRestart --> onStart --> onResume
           

5)背景

Activity

因為系統記憶體不足被殺死,重新啟動該

Activity

:

onCreate --> onStart --> onResume
           

6)鎖屏,開屏 :

鎖屏  onPause --> onStop 
開屏  onStart --> onResume
           

7)使用者點選

back

鍵 :

onPause --> onStop --> onDestroy
           
上面列舉了一些常見的場景下生命周期的變化,在此做一個小結:
  • 從整個生命周期來看,

    onCreate

    onDestroy

    是配對的,分别辨別着

    Activity

    的建立和銷毀,并且隻可能有一次調用;
  • Activity

    是否可見,

    onStart

    onStop

    是配對的,随着使用者操作或者裝置點亮熄滅,可能被調用多次;
  • Activity

    是否在前台來說,

    onResume

    onPause

    是配對的,随着使用者操作或者裝置點亮熄滅,可能被調用多次。

1.2 異常情況下生命周期分析:

1)資源相關的系統配置發生改變導緻

Activity

被殺死并重新建立(常見舉例 :

橫豎屏切換

)。

// onPause 和  onSaveInstanceState 的執行順序,誰都可能在前
 // 官方建議在 onRestoreInstanceState 方法進行資料恢複。
 onPause --> onSaveInstanceState --> onStop --> onDestroy --> onCreate --> onRestoreInstanceState
           

2)資源記憶體不足導緻優先級低的

Activity

被殺死。

  • 前台

    Activity

    :正在和使用者互動的

    Activity

    ,優先級最高。
  • 可見但非前台

    Activity

    :比如

    Activity

    中彈出了一個對話框,導緻

    Activity

    可見但是位于背景無法和使用者直接互動。
  • 背景

    Activity

    :已經被暫停的

    Activity

    ,比如執行了 onStop,優先級最低。

1.3

ConfigChanges

如果不想要在系統配置發生改變時,重新建立

Activity

,可以給

Activity

指定

configChanges

屬性。

下面列舉幾個常見的

configChangs

的屬性:

項目 含義
locale 裝置的本地位置發生了改變,一般指切換了語言系統
keyboardHidden 鍵盤的可通路性發生了改變,比如使用者調用出了鍵盤
orientation 螢幕方向發生了改變,比如旋轉手機螢幕。
screenSize 當螢幕尺寸資訊發生了變化,這個選項比較特殊,它和編譯選項有關,當編譯選項中的 minSdkVersion 和 targetSdkVersion 均低于13時,此選項不會導緻 Activity 重新開機,否則會導緻 Activity 重新開機

具體做法,在

AndroidMinfest.xml

檔案中,給不想要因為系統配置發生改變而導緻

Activity

重建的

Activity

添加如下代碼:

// 如果 MiniSdkVersion 和 TargetSdkVersion 屬性大于等于13的情況下,需要同時設定 screenSize 和 orientation
android:configChanges="screenSize|orientation"
           

二、 Activity 啟動模式

在預設情況下,當我們多次啟動同一個 Activity 時,系統會建立多個執行個體,并把它們一一放入任務棧中,那麼多次啟動同一個 Activity,系統就會重複建立多個執行個體,這是一個很傻的行為,是以提供了啟動模式來修改系統的預設行為。
目前有4種啟動模式:

standard

singleTop

singTask

singleInstance

2.1 standard 模式

  • 标準模式,也是系統預設模式。

舉例說明:

  1. 建立

    BaseActivity

    基類。
  2. MainActivity

    繼承基類,

    StandardActivity

    類繼承基類,同時設定

    lanchmode

    standard

  3. MainActivity

    跳轉到

    StandardActivity

    ,在

    StandardActivity

    類中,點選三次按鈕,做跳轉自身操作。
  4. 檢視

    Log

    日志。

BaseActivity

代碼如下:

public class BaseActivity extends AppCompatActivity {

    public static final String TAG = "TAG";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.i(TAG, "===============onCreate()方法==");

        Log.i(TAG, "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());

        dumpTaskAffinity();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.i("TAG", "===============onNewIntent()方法==");
        Log.i("TAG", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
        dumpTaskAffinity();
    }

    private void dumpTaskAffinity() {
        try {
            ActivityInfo info = this.getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Log.e(TAG, "taskAffinity:" + info.taskAffinity);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}
           

Log

日志輸出 :

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 28 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:183570408
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:78785677
E/TAG: taskAffinity:com.example.activitylanuchmode
           

總結:

  1. StandardActivity

    做跳轉自身三次操作,日志顯示了三個不同的

    hasCode

    的值, 推出結論: 每次啟動一個

    Activity

    都會重新建立一個新的執行個體,不管這個執行個體是否存在。
  2. 日志輸出了三次

    onCreate

    方法,推出結論:被建立的執行個體的生命周期符合典型情況下的生命周期,它的

    onCreate

    onStart

    onResume

    都會調用。
  3. MainActivity

    StandardAcitivty

    taskAffinity

    值一樣,推出結論: 在這種模式下,誰啟動了這個

    Activity

    ,那麼這個

    Activity

    就運作在啟動它的那個

    Activity

    所在的棧中。

2.2 singleTop

  • 棧頂複用模式。

通俗易懂的說一下此模式:

目前棧内情況為 ABCD 四個

Activity

,A 位于棧底,D 位于棧頂,假設需要再次啟動 D,如果 D 的啟動模式是

singleTop

,那麼棧内的情況還是 ABCD,如果 D 的啟動模式

standard

,那麼棧内的情況是 ABCDD。

舉例說明:

  1. 建立

    SingleTopActivity

    lanchmode

    singleTop

    ),

    OtherActivity

    lanchmode

    standard

    )。
  2. MainActivity

    跳轉到

    SingleTopActivity

    ,

    SingleTopActivity

    跳轉到

    OhterActivity

    跳轉到

    SingleTopActivity

    SingleTopActivity

    自身跳轉三次。
  3. 檢視

    Log

    日志。

Log 日志輸出:

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 29 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTopActivity TaskId: 29 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTopActivity TaskId: 29 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
           

總結:

  1. 第一次建立

    SingleTopActivity

    時,回調了

    onCreate

    方法,

    taskId

    值一樣,推出結論: 目前棧中不存在該

    Activity

    的執行個體時,其行為同

    standard

    啟動模式。
  2. OhterTopActivity

    傳回到

    SingleTopActivity

    時,從日志看出

    onCreate

    方法 被調用,且

    hasCode

    值 與

    SingleTopActivity

    第一次建立時 hasCode 值不一樣,推出結論: 目前棧中已有該

    Activity

    的執行個體但是該執行個體不在棧頂時,其行為和

    standard

    啟動模式一樣,依然會建立一個新的執行個體。
  3. SingleTopActivity

    做跳轉自身三次操作的 hasCode 值都一樣,推出結論: 目前棧中已有該

    Activity

    的執行個體并且該執行個體位于棧頂時,不會建立執行個體,而是複用棧頂的執行個體,并且會将

    Intent

    對象傳入,回調

    onNewIntent

    方法。

2.3 singleTask

  • 棧内複用模式。

在講解此模式前,先說一下什麼是

Activity

的任務棧 ?

前文提及過

taskAffinity

這個參數,可以翻譯為

任務相關性

。 這個參數辨別了一個

Activity

所需要的任務棧的名字。

預設情況下,所有的

Activity

所需要的任務棧的名字為應用的包名。

當然,我們也可以為每一個

Activity

單獨指定該屬性。

設定了相同

taskAffinity

屬性的

Activity

在同一個任務棧中。如果你為某一個

Activity

taskAffinity

屬性設定為空字元,那麼表示該

Activity

不屬于任何task。

standard

singleTop

啟動模式都是在原任務棧中建立

Activity

執行個體,不會啟動新的Task,即使你指定了

taskAffinity

屬性。

通俗易懂的說一下此模式:

當一個具有

singleTask

模式的

Activity

請求啟動後,例如

Activity A

,系統首先會尋找是否存在 A 想要的任務棧,如果不存在,就重新建立一個任務棧。然後建立 A 的執行個體,把 A 放入棧中。如果存在 A 所需的任務棧,這時要看 A 是否在棧中有執行個體存在,如果有執行個體存在,系統會把A調到棧頂并調用

onNewIntent()

方法,如果執行個體不存在,就建立 A 的執行個體并把 A 壓入棧中。

舉例說明:

  1. 目前任務棧 S1 的情況是 ABC,這個時候

    Activity D

    singleTask

    模式請求啟動,所需要的任務棧為 S2,由于 S2 和 D 的執行個體都不存在,是以系統會先建立任務棧 S2,然後在建立

    Activity D

    的執行個體并将其入棧 S2。
  2. 假設

    Activity D

    所需要的任務棧為 S1,其它如1所示,那麼由于 S1 已經存在,那麼系統會直接建立 D 的執行個體并将其壓入棧 S1。
  3. 如果 D 需要的任務棧是 S1,且目前 S1 的情況是 ADBC,A棧底,C棧頂,根據棧内複用原則,此時 D 不會被重新建立,系統會把D切換到棧頂并調用

    onNewIntent

    方法,同時由于

    singleTask

    預設具有

    clearTop

    方法,會導緻棧内所有的在 D 上面的

    Activity

    全部出棧。

代碼驗證:

  1. 建立

    SingleTaskActivity

    ,啟動模式為

    singleTask

    ,同時建立

    OtherTaskActivity

  2. 做以下操作:

    MainActivity

    跳轉到

    SingleTaskActivity

    SingleTaskActivity

    跳轉到

    OtherTaskActivity

    OtherTaskActivity

    回跳到

    SingleTaskActivity

    SingleTaskActivity

    點選跳轉自身按鈕兩次。
  3. 檢視

    Log

    日志。

Log

日志輸出:

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 31 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTaskActivity TaskId: 31 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
           

上面的操作都是在未給

SingleTaskActivity

指定

taskAffinity

值的情況下,根據日志:

OtherActivity

跳回

SingleTaskActivity

SingleTaskActivity

并未建立,複用了該執行個體,由

hasCode

值可看出,且調用了

onNewIntent

方法。同時,

OhterActivity

出棧了,可以通過 指令

adb shell dumpsys activity

,在顯示的内容中檢視

Running activities

這一塊顯示區域。

下面,我們給

SingleTaskActivity

指定一下

taskAffinity

的值。注意,這個屬性的值時字元串,且中間必須包含分隔符

.

<activity
            android:name=".SingleTaskActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.activitylanuchmode.singletask"
            />
           

重複之前的操作,再來看一下日志:

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 33 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTaskActivity TaskId: 34 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask

I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
           

從日志中可以看到差別就是

taskAffinity

的值不同了,說明

SingleTaskActivity

所屬的任務棧不同于

MainActivity

了。

總結

  • singleTask

    模式的

    Activity

    啟動時,首先根據

    taskAffinity

    屬性查找目前是否存在一個對應名字的任務棧。
  • 若不存在該屬性的任務棧,則會建立一個新的Task,并建立新的

    Activity

    執行個體入棧到新建立的

    Task

    中去。
  • 若存在該屬性的任務棧,則查找該任務棧中是否存在該

    Activity

    執行個體。
  • 若存在該執行個體,則将它上面的所有

    Activity

    執行個體都出棧,然後回調啟動的

    Activity

    執行個體的

    onNewIntent

    方法
  • 若不存在該執行個體,則建立

    Activity

    入棧。

2.4 singleInstance

  • 單執行個體模式,加強版

    singleTask

簡單說明一下此模式

具備

singleTask

模式的所有特性,還加強了一點,就是具有此模式的 Activity 隻能單獨地位于一個任務棧中。

舉例說明

比如

Activity A

singleInstance

模式,那當 A 啟動後,系統會為它建立一個新的任務棧,然後 A 獨自在這個任務棧中,由于棧内複用特性,後續的請求都不會建立新的

Activity

,除非這個獨特的任務棧被系統銷毀了。

2.5

Flags

标記位

Activity

的标記位有很多,這裡說一些常用的。

  • FLAG_ACTIVITY_NEW_TASK

    : 為 Activity 指定 “singleTask” 啟動模式,其效果和在xml中指定該啟動模式相同。
  • FLAG_ACTIVITY_SINGLE_TOP

    : 為 Activity 指定 “singleTop” 啟動模式, 其效果和在xml中指定該啟動模式相同。
  • FLAG_ACTIVITY_CLEAR_TOP

    : 啟動有此辨別的

    Activity

    ,在同一個任務棧中,所有位于它上面的

    Activity

    都要出棧。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

    : 有此辨別的

    Activity

    不會出現在曆史

    Activity

    清單中,等同于在XML中指定

    Activity

    的屬性

    android:excludeFromRecents="true"

至于

Activity

flag

如何使用呢?

前面介紹了四種啟動模式,我們給

Activity

指定啟動模式都是在

AndroidMenifest.xml

檔案中設定的,其實還有一種方法,通過在

Intent

中設定,舉例設定

singleTask

,代碼如下:

Intent intent = new Intent();
intent.setClass(this,SingleTaskActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
           

三、

IntentFilter

比對規則

隐式調用需要

Intent

能夠比對目标元件的

IntentFilter

中所設定的過濾資訊,是以我們需要對

IntentFilter

的過濾資訊有所了解。

IntentFilter

的過濾資訊有

action

category

data

幾個要點說明:

  • 一個

    Intent

    隻要能比對任意一組

    intent-filter

    即可成功啟動對應的

    Activity

  • 一個過濾清單中的

    action

    category

    data

    可以有多個。例如下面:
<activity android:name=".MainActivity"
    android:configChanges="screenSize|orientation">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>
           
  • 一個

    Activity

    可以有多個

    intent-filter

    (過濾清單)。例如下面:
<activity android:name=".MainActivity"
    android:configChanges="screenSize|orientation" >
        
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

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

好了,大概的說了一下

IntentFilter

的幾個小的注意點,下面來具體說一說那三個過濾資訊。

3.1

Action

的比對規則

  • action

    是一個字元串。
  • action

    區分大小寫。
  • Intent

    中必須存在一個

    action

  • Intent

    中的

    action

    intent-filter

    中的 任意一個

    action

    的字元串值完全相同,則

    action

    比對成功。

3.2

category

的比對規則

  • category

    是一個字元串。
  • Intnet

    中的

    category

    必須和

    intent-filter

    中的

    category

    相同。
  • 如果

    Intent

    沒有

    category

    ,那麼為了

    activity

    能夠被隐式接收,就需要在

    intent-filter

    中添加

    "android.intent.category.DEFAULT"

3.3 `data 的比對規則

首先看一下

data

的結構:

<data android:scheme="String"
      android:host="String"
      android:path="String"
      android:pathPattern="String"
      android:pathPrefix="String"
      android:mimeType="String"
           

data

由兩個部分組成:

mimeType

URL

mimeType

: 媒體類型,例如

img

jpeg

等。

URL

:

<Scheme>://<host>:<pot>/[<path>|<pathPrefix>|<pathPattern>]

// 解釋說明:
Scheme: URL 模式。例如 http、file、content
Host: URL 主機名。例如 www.baidu.com
Port: URL 端口号。例如 80
Path、pathPrefix、pathPattern: 表示路徑資訊。

           

data

的比對規則與

action

的比對規則類似。

  • Intent

    中必須有一個

    data

  • Intent

    中的

    data

    必須完全比對

    intent-filter

    中的

    data

舉例說明

<intent-fileter
   <data android:mimeType="image/*"/>
   ...
</intent-filter>
           

上述規則制定了媒體類型為所有類型的圖檔,沒有指定

URL

,但是

URL

有預設值:

content

file

如何比對上述過濾資訊,如下:

intent.setDataAndType(Uri.parse("file://abc","image/png"));
           

有一類

action

category

比較重要:

<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
           

如上的二者缺一不可,表明這是一個入口

Activity

,并且會出現在系統的應用清單中。

好了,關于

IntentFilter

的過濾規則就說完了,下面給出一個完整的舉例說明:

過濾規則:

<activity android:name=".DemoActivity"
   android:configChanges="screenSize|orientation">
    <intent-filter>
        <action android:name="com.example.intentfilter.demo"/>
        <category android:name="android.intent.category.demo"/>
        <data android:mimtType="text/plain">
    </intent-filter>
</activity>
           

比對:

Intent intent = new Intent("com.example.intentfilter.demo");
intent.addCategory("com.example.category.demo");
intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
startActivity(intent);
           

寫在文末

紙上得來終覺淺,絕知此事要躬行。 《冬夜讀書示子聿》-- 陸遊

至此,

Activity

的基礎知識點算是梳理了一遍,各位看官食用愉快。

碼字不易,如果本篇文章對您哪怕有一點點幫助,請不要吝啬您的點贊,我将持續帶來更多優質文章。