Activity的生命周期和啟動模式

Activity的生命周期和啟動模式

1.1 Activity的生命周期全面分析

將 Activity 的生命周期分為兩部分內(nèi)容

  • 典型情況下的生命周期分析
    • 只在有用戶參與的情況下榆鼠,Activity 所經(jīng)歷的生命周期的改變靶病。
  • 異常情況下的生命周期分析
    • Activity 被系統(tǒng)回收 或 由于當(dāng)前設(shè)備的 configuration 發(fā)生改變從而導(dǎo)致 Activity 被銷毀重建先誉。

1.1.1 典型情況下的生命周期分析

幾種情況:

  • 當(dāng)用戶打開新的 Activity 或者 切換到桌面 的時候朽们,回調(diào)如下 onPause =》 onStop
    • 如果新 Activity 采用了透明主題,那么 當(dāng)前 Activity 不會回調(diào) onStop (因為當(dāng)前 Activity 仍然可見嘛)
  • 當(dāng)用戶按下 back 鍵回退時绎橘,回調(diào)如下: onPause =》 onStop =》 onDestroy (銷毀了)
  • 從整個生命周期來看胁孙,onCreate 與 onDestroy 是配對的,分別標(biāo)識著 Activity 的創(chuàng)建和銷毀,只可能有一次調(diào)用浊洞。
  • 從Activity 是否可見來說牵敷,onStart 和 onStop 是配對的,隨著用戶的操作或者設(shè)備屏幕的點亮和熄滅法希,這兩個方法可能被調(diào)用多次枷餐;
  • 從Activity 是否在前臺來說,onResume 和 onPause 是配對的苫亦。隨著用戶的操作或者設(shè)備屏幕的點亮和熄滅毛肋,這兩個方法可能被調(diào)用多次;

onStart 和 onStop 屋剑,onResume 和 onPause 描述上看起來差不多润匙,對于開發(fā)人員而言有何區(qū)別呢?

  • 這兩個配對的回調(diào)分別表示不同的意義唉匾,
    • onStart 和 onStop 是從Activity 是否可見這個角度來回調(diào)孕讳。
    • onResume 和 onPause 是從Activity 是否位于前臺這個角度來回調(diào)。

新 Activity 啟動之前巍膘,要等棧頂?shù)?Activity 執(zhí)行完onPause 后厂财,新Activity 才能==啟動==。(onCreate )

03-26 10:08:59.983 9030-9030/com.android.rdc.myapplication I/MainActivity: onCreate: 
03-26 10:09:00.163 9030-9030/com.android.rdc.myapplication I/MainActivity: onStart: 
03-26 10:09:00.163 9030-9030/com.android.rdc.myapplication I/MainActivity: onResume: 
03-26 10:09:04.387 9030-9030/com.android.rdc.myapplication I/MainActivity: onPause: 
03-26 10:09:04.397 9030-9030/com.android.rdc.myapplication I/SecondActivity: onCreate: 
03-26 10:09:04.427 9030-9030/com.android.rdc.myapplication I/SecondActivity: onStart: 
03-26 10:09:04.427 9030-9030/com.android.rdc.myapplication I/SecondActivity: onResume: 
03-26 10:09:04.838 9030-9030/com.android.rdc.myapplication I/MainActivity: onStop: 
03-26 10:09:08.872 9030-9030/com.android.rdc.myapplication I/SecondActivity: finish: 
03-26 10:09:08.892 9030-9030/com.android.rdc.myapplication I/SecondActivity: onPause: 
03-26 10:09:08.912 9030-9030/com.android.rdc.myapplication I/MainActivity: onRestart: 
03-26 10:09:08.912 9030-9030/com.android.rdc.myapplication I/MainActivity: onStart: 
03-26 10:09:08.922 9030-9030/com.android.rdc.myapplication I/MainActivity: onResume: 
03-26 10:09:09.303 9030-9030/com.android.rdc.myapplication I/SecondActivity: onStop: 
03-26 10:09:09.303 9030-9030/com.android.rdc.myapplication I/SecondActivity: onDestroy: 

另外峡懈,如果調(diào)用了 finsh方法璃饱,會先調(diào)用 onPause 而不是 直接調(diào) onDestroy

onPause 和 onStop 都不能執(zhí)行耗時操作,尤其是 onPause 肪康,這這意味著荚恶,我們應(yīng)當(dāng)盡量在 onStop 方法中做操作,從而讓 新Activity 盡快顯示出來并切換到前臺磷支。

1.1.2 異常情況下的生命周期分析 / 8

情況1. 資源相關(guān)的系統(tǒng)配置發(fā)生改變導(dǎo)致Activcity 被殺死并重新創(chuàng)建

Activity 在異常情況下終止谒撼,系統(tǒng)會 調(diào)用 onSaveInstance 來保存當(dāng)前 Activity 的狀態(tài)。
這個方法的調(diào)用是在 onStop 之前雾狈,但是==和 onPause 沒有既定的時序關(guān)系==嗤栓。

注意:onSaveInstance 只會在 Activity 被異常終止的情況下被回調(diào),正常情況系統(tǒng)不會回調(diào)這個方法箍邮。

當(dāng) Activity 被重新創(chuàng)建之后, 系統(tǒng)會調(diào)用 onRestoreInstanceState 叨叙,并把 Activity 的銷毀時 onSaveInstance 方法所保存的Bundle 對象作為參數(shù)同時傳遞給 onCreate 方法 和 onRestoreInstance 方法锭弊。

可以在 onRestoreInstance 方法 和 onCreate 方法中判斷 Activity 是否被重建了,以此進(jìn)行數(shù)據(jù)恢復(fù)擂错。

在 onSaveInstance 和 onRestoreInstance 方法中味滞,系統(tǒng)自動幫我們做了一定的恢復(fù)工作。

  • 系統(tǒng)默認(rèn)為我們保存當(dāng)前 Activity 的視圖結(jié)構(gòu),并且在 Activity 重啟之后為我們恢復(fù)這些數(shù)據(jù)剑鞍,比如 ListView 滾動的位置 等昨凡。

關(guān)于保存和恢復(fù)View層次結(jié)構(gòu),系統(tǒng)的工作流程是醬紫的:

  • 首先 Activity 被意外終止時蚁署,Activity 會調(diào)用 onSaveInstance 去保存數(shù)據(jù)便脊,然后 Activity 會 委托Window 去保存數(shù)據(jù), Window 委托 它上面的頂級容器去保存數(shù)據(jù)光戈。頂級容器是一個 ViewGroup哪痰,一般來說它很可能是 DecorView 。最后頂層容器再去一一通知它的子元素來保存數(shù)據(jù)久妆,這樣這個數(shù)據(jù)的保存過程就完成了晌杰。

onCreate 方法 和 onRestoreInstance 方法的區(qū)別

  • onRestoreInstance 一旦被調(diào)用,其參數(shù)一定是有值的筷弦,不需要判空
    • 官方建議使用 onRestoreInstance 去恢復(fù)數(shù)據(jù)肋演。
  • onCreate 方法則不一定,因為如果是正常啟動的話烂琴,其參數(shù) Bundle onSaveInstance 可能為 null爹殊。

通過 onSaveInstanceState 方法保存狀態(tài)的不足之處

依靠系統(tǒng)通過onSaveInstanceState() 回調(diào)為你保存的 Bundle,可能無法完全恢復(fù) Activity 狀態(tài)监右,因為它并非設(shè)計用于攜帶大型對象(例如位圖)边灭,而且其中的數(shù)據(jù)必須先序列化,再進(jìn)行反序列化健盒,這可能會消耗大量內(nèi)存并使得配置變更速度緩慢绒瘦。

通過保留 Fragment 來減輕重新初始化 Activity 的負(fù)擔(dān)

當(dāng) Android 系統(tǒng)因配置變更而關(guān)閉 Activity 時,不會銷毀你已標(biāo)記為要保留的 Activity 的Fragment扣癣。 你可以將此類Fragment添加到 Activity 以保留有狀態(tài)的對象惰帽。

注:通過調(diào)用 Fragment#setRetainInstance(true)將 Fragment 標(biāo)記為要保留。在模擬器上邊測試父虑,即使沒有 調(diào)用setRetainInstance(true)也仍然會保存啊该酗。

  1. 擴(kuò)展 Fragment 類并聲明對有狀態(tài)對象的引用。
  2. 在創(chuàng)建片段后調(diào)用 setRetainInstance(boolean)士嚎。
  3. 使用 FragmentManager 將片段添加到 Activity呜魄。
  4. 重啟 Activity 后,使用 FragmentManager 檢索片段莱衩。

onSaveInstance 的最終數(shù)據(jù)保存到哪里爵嗅?內(nèi)存里面嗎?

保存在 ActivityClientRecord 的 Bundle 類型字段 里面笨蚁,而 ActivityThread 中含有一個 final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();用于存放 一組ActivityClientRecord

android.os.Bundle 是什么

class Bundle extends BaseBundle implements Cloneable, Parcelable

A mapping from String keys to various Parcelable values.

從字符串鍵到各種Parcelable值的映射睹晒。

ActivityClientRecord 又是在哪里創(chuàng)建的呢趟庄?
android.app.ActivityThread#startActivityNow
public final Activity startActivityNow(Activity parent, String id,
    Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
    Activity.NonConfigurationInstances lastNonConfigurationInstances) {
    ActivityClientRecord r = new ActivityClientRecord();
    //...
    return performLaunchActivity(r, null);
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    //……
        mActivities.put(r.token, r);//添加到 ArrayMap 中
    //……
    return activity;
}

Activity 異常銷毀的情況下,F(xiàn)ragmentActivity 的 onSaveInstanceState 方法的默認(rèn)實現(xiàn)會保存其中所有的 Fragment 的狀態(tài)

然后在 android.support.v4.app.FragmentActivity#onCreate 方法中恢復(fù) Fragment 保存的狀態(tài)伪很。

android.support.v4.app.FragmentManagerImpl#saveAllState

“關(guān)于保存和恢復(fù)View層次結(jié)構(gòu)戚啥,系統(tǒng)的工作流程是這樣的:首先Activity被意外終止時,Activity會調(diào)用onSaveInstanceState去保存數(shù)據(jù)锉试,然后Activity會委托Window去保存數(shù)據(jù)猫十,接著Window再委托它上面的頂級容器去保存數(shù)據(jù)(這里的說法不恰當(dāng),應(yīng)該是 Windows 里面的頂級容器才對吧)键痛。頂層容器是一個ViewGroup炫彩,一般來說它很可能是DecorView。最后頂層容器再去一一通知它的子元素來保存數(shù)據(jù)絮短,這樣整個數(shù)據(jù)保存過程就完成了江兢。可以發(fā)現(xiàn)丁频,這是一種典型的委托思想杉允,上層委托下層、父容器委托子元素去處理一件事情席里,這種思想在Android中有很多應(yīng)用叔磷,比如View的繪制過程、事件分發(fā)等都是采用類似的思想奖磁。至于數(shù)據(jù)恢復(fù)過程也是類似的改基,”

Activity 異常銷毀—> 委托 window 保存數(shù)據(jù)—>委托頂級容器 逐個通知子元素保存數(shù)據(jù),

子元素通過重寫 onSaveInstanceState 來保存狀態(tài)的時候通常都會調(diào)用 super.onSaveInstanceState咖为,

狀態(tài)保存的起點實際上是 android.app.Activity#performSaveInstanceState(android.os.Bundle)

//The hook for {@link ActivityThread} to save the state of this activity.
final void performSaveInstanceState(Bundle outState) {
    onSaveInstanceState(outState);
    saveManagedDialogs(outState);
    mActivityTransitionState.saveState(outState);
    storeHasCurrentPermissionRequest(outState);
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}

實際上是保存在 android.app.ActivityThread.ActivityClientRecord 里面的秕狰。這個東西 android.app.ActivityThread.ActivityClientRecord#state

調(diào)用源頭——ActivityThread

保存狀態(tài)的調(diào)用流程
—>Activity.onSaveInstanceState(MainActivity.java:50)
—>android.app.Activity.performSaveInstanceState(Activity.java:1496)
—>android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1386)
—>android.app.ActivityThread.callCallActivityOnSaveInstanceState(ActivityThread.java:4721)
—> android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4672)
—> android.app.ActivityThread.-wrap18(Unknown Source:0)
—> android.app.ActivityThread$H.handleMessage(ActivityThread.java:1595)
—> android.os.Handler.dispatchMessage(Handler.java:106)
—> android.os.Looper.loop(Looper.java:164)
—> android.app.ActivityThread.main(ActivityThread.java:6494)
—> java.lang.reflect.Method.invoke(Native Method)
—> com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
—>at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
恢復(fù)保存的狀態(tài)的調(diào)用流程
—> Activity#onRestoreInstanceState(MainActivity.java:61)
—> android.app.Activity.performRestoreInstanceState(Activity.java:1057)
—> android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1260)
—> android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2751)
—> android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
—> android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4699)
—> android.app.ActivityThread.-wrap18(Unknown Source:0)
—> android.app.ActivityThread$H.handleMessage(ActivityThread.java:1595)
—> android.os.Handler.dispatchMessage(Handler.java:106)
—> android.os.Looper.loop(Looper.java:164)
—> android.app.ActivityThread.main(ActivityThread.java:6494)
—> java.lang.reflect.Method.invoke(Native Method)
—> com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

系統(tǒng)內(nèi)存不足的時候,系統(tǒng)會按照上述優(yōu)先級去殺死目標(biāo) Activity 所在的進(jìn)程躁染。

后臺工作放到哪里比較合適鸣哀?比較好的方法是將后臺工作放入Service中從而保證進(jìn)程有一定的優(yōu)先級,這樣就不會輕易地被系統(tǒng)殺死吞彤。

當(dāng)配置發(fā)生改變的時候我衬,有沒有辦法不重新創(chuàng)建?

有的饰恕。android:configChanges="orientation"

不重新創(chuàng)建的話挠羔,前提是不銷毀呀。

Activity#onConfigurationChanged

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    Log.e(TAG, " " + newConfig);
}

請謹(jǐn)記:在聲明由 Activity 處理配置變更時埋嵌,你需要負(fù)責(zé)重置要為其提供備用資源的所有元素褥赊。 如果你聲明由 Activity 處理方向變更,而且有些圖像應(yīng)該在橫向和縱向之間切換莉恼,則必須在 onConfigurationChanged() 期間將每個資源重新分配給每個元素拌喉。

如果無需基于這些配置變更更新應(yīng)用,則可不用實現(xiàn) onConfigurationChanged()俐银。在這種情況下尿背,仍將使用在配置變更之前用到的所有資源,只是你無需重啟 Activity捶惜。 但是田藐,應(yīng)用應(yīng)該始終能夠在保持之前狀態(tài)完好的情況下關(guān)閉和重啟,因此你不得試圖通過此方法來逃避在正常 Activity 生命周期期間保持你的應(yīng)用狀態(tài)吱七。 這不僅僅是因為還存在其他一些無法禁止重啟應(yīng)用的配置變更汽久,還因為有些事件必須由你處理,例如用戶離開應(yīng)用踊餐,而在用戶返回應(yīng)用之前該應(yīng)用已被銷毀景醇。

情況2. 資源內(nèi)存不足導(dǎo)致低優(yōu)先級的Activity 被殺死

Activity 按照優(yōu)先級從高到低,可以分為如下三種:

  1. 前臺 Activity —— 正在和用戶交互的Activity
  2. 可見但非前臺 Activity ——比如彈出了一個對話框
  3. 后臺 Activity —— 已經(jīng)被暫停的 Activity吝岭,比如 執(zhí)行了 onStop

一個進(jìn)程中沒有四大組件在執(zhí)行三痰,那么這個進(jìn)程將很快被系統(tǒng)殺死。

當(dāng)系統(tǒng)配置發(fā)生改變之后窜管,Activity 會重新被創(chuàng)建散劫。

有沒有辦法在配置改變時不重新創(chuàng)建呢?

有的幕帆,系統(tǒng)配置中有很多內(nèi)容获搏,如果當(dāng)某項內(nèi)容改變之后,我們不想系統(tǒng)重新創(chuàng)建 Activity ,可以給 Activity 指定 configChanges 屬性失乾。

config 項目有很多常熙,常用的只有 locale 、 orientation 仗扬、keyboardHidden 這三個選項

注意: screenSize 和 smallestScreenSize 症概,這兩個行為比較特殊,他們的行為和 編譯選項有關(guān)早芭,但和運(yùn)行的環(huán)境無關(guān)彼城。

“mcc“ 移動國家號碼切平,由三位數(shù)字組成急侥,每個國家都有自己獨(dú)立的MCC,可以識別手機(jī)用戶所屬國家纺非。
“mnc“ 移動網(wǎng)號语盈,在一個國家或者地區(qū)中舱馅,用于區(qū)分手機(jī)用戶的服務(wù)商。
“l(fā)ocale“ 所在地區(qū)發(fā)生變化刀荒。
“touchscreen“ 觸摸屏已經(jīng)改變代嗤。(這不應(yīng)該常發(fā)生棘钞。)
“keyboard“ 鍵盤模式發(fā)生變化,例如:用戶接入外部鍵盤輸入干毅。
“keyboardHidden“ 用戶打開手機(jī)硬件鍵盤 
“navigation“ 導(dǎo)航型發(fā)生了變化宜猜。(這不應(yīng)該常發(fā)生。)
“orientation“ 設(shè)備旋轉(zhuǎn)硝逢,橫向顯示和豎向顯示模式切換姨拥。 //經(jīng)常發(fā)生
“fontScale“ 全局字體大小縮放發(fā)生改變

自從Android 3.2(API 13),在設(shè)置Activity的android:configChanges="orientation|keyboardHidden"后渠鸽,還是一樣會重新調(diào)用各個生命周期的叫乌。因為screen size也開始跟著設(shè)備的橫豎切換而改變。所以徽缚,在AndroidManifest.xml里設(shè)置的MiniSdkVersion和 TargetSdkVersion屬性大于等于13的情況下憨奸,如果你想阻止程序在運(yùn)行時重新加載Activity,除了設(shè)置"orientation"猎拨,你還必須設(shè)置"ScreenSize"膀藐。

注意:

  1. 模擬器上跟真機(jī)驗證結(jié)果可能會不一樣。
  2. 不同版本的系統(tǒng)驗證結(jié)果也可能不一樣红省。

PS:在 API27 的模擬器上試了额各,只加orientation 沒有加 ScreenSize時,即使切換設(shè)備的橫豎方向也不會觸發(fā)重建吧恃。

1.2 Activity的啟動模式 / 16

為什么Activity需要啟動啟動模式虾啦?

  • 為了滿足不同的需求。給予更多的拓展性

默認(rèn)的啟動方式有點傻

四種啟動模式:standard痕寓、singleTop傲醉、singeTask、singleInstance

1.2.1 Activity的LaunchMode / 16

為什么 Android 設(shè)計了多種啟動模式呻率?

為了適應(yīng)更多的場景硬毕。

如何給 Activity 指定啟動模式呢?

  1. 通過 AndroidManifest 為Activity 指定啟動模式
  2. 在 Intent 中設(shè)置標(biāo)志位來為 Activity 指定啟動模式

兩種指定方式的區(qū)別:

  1. 優(yōu)先級上礼仗,intent 設(shè)置標(biāo)志位的方式要高于在 manifest 文件中為 Activity 指定啟動模式
  2. 在限定范圍上有所不同吐咳,
    • 比如,第一種方式無法直接為 Activity 指定 FLAG_ACTIVITY_CLEAR_TOP 標(biāo)識
    • 第二種方式無法為 Activity 指定 singleInstance 模式

也就是說 如果 Activity A 啟動 Activity B元践,則 Activity B 可以在其清單文件中定義它應(yīng)該如何與當(dāng)前任務(wù)關(guān)聯(lián)(如果可能)韭脊,并且 Activity A 還可以請求 Activity B 應(yīng)該如何與當(dāng)前任務(wù)關(guān)聯(lián)。如果這兩個 Activity 均定義 Activity B 應(yīng)該如何與任務(wù)關(guān)聯(lián)单旁,則 Activity A 的請求(如 Intent 中所定義)優(yōu)先級要高于 Activity B 的請求(如其清單文件中所定義)沪羔。

:某些適用于清單文件的啟動模式不可用作 Intent 標(biāo)志,同樣象浑,某些可用作 Intent 標(biāo)志的啟動模式無法在清單文件中定義蔫饰。

1. standard

標(biāo)準(zhǔn)模式:(系統(tǒng)默認(rèn))琅豆。每次啟動一個 Activity 都會新建一個新的實例。

誰啟動了這個 Activity死嗦,那么這個 Act 就運(yùn)行在啟動它的那個 Activity 所在的那個棧中趋距。
當(dāng)我們用 ApplicationContext 去啟動 standard 模式的 Activity 時會報錯。

解決這個問題的方法是越除, 為帶啟動的Activity 指定 FLAG_ACTIVITY_NEW_TASK 標(biāo)志位,這樣啟動時外盯,就會為它新建一個任務(wù)棧摘盆。這時的 activity 其實是以 singleTask 模式啟動的

在5.0和8.0的模擬器、8.0的真機(jī)(一加5t)中饱苟,使用 ApplicationContext 去啟動standard模式的 Activity 也沒有crash孩擂。

任務(wù)和返回棧

2. singleTop

棧頂復(fù)用。如果新 Activity 已經(jīng)位于 任務(wù)棧的棧頂箱熬,那么此Activity不會被重建类垦。同時它的 onNewIntent 方法會被回調(diào)。

  • 通過 onNewIntent 方法的參數(shù) 我們可以取出當(dāng)前請求的信息城须。

3. singleTask

棧內(nèi)復(fù)用模式蚤认。一種單實例模式。只要 Activity 在一個棧中存在糕伐,就會復(fù)用這個 Activity 砰琢,同時它的 onNewIntent 方法會被回調(diào)。

  • singleTask 默認(rèn)具有 clearTop 效果良瞧,會導(dǎo)致棧內(nèi) 所有在目標(biāo) Activity 之上的 Activity 全部出棧

我們假設(shè)目前有2個任務(wù)棧陪汽,前臺任務(wù)棧的情況為AB,而后臺任務(wù)棧的情況為CD褥蚯,這里假設(shè)CD的啟動模式均為singleTask≈吭現(xiàn)在請求啟動D,那么整個后臺任務(wù)棧都會被加入到前臺任務(wù)棧中赞庶,這個時候整個后退列表變成了ABCD训挡。當(dāng)用戶按back鍵的時候,列表中的Activity會一一出棧尘执。

如果是啟動 C舍哄,那么后退列表就是 ABC。

也就是說誊锭,啟動 singleTask 模式的 Activity 會把它下面的所有 Activity 也帶到當(dāng)前的棧中表悬。

4. singleInstance

單實例模式:一種加強(qiáng)版的 singleTask 模式,具有此種模式的 Activity 只能單獨(dú)地位于一個任務(wù)棧中丧靡。

singleTask 中提到某個 Activity 所需的任務(wù)棧蟆沫,

何謂Activity 所需任務(wù)棧籽暇? 任務(wù)相關(guān)性

從一個參數(shù)說起:TaskAffinity (任務(wù)相關(guān)性)。該參數(shù)標(biāo)識了一個 Activity 所需要的任務(wù)棧的名字饭庞,默認(rèn)情況下戒悠,所有 Activity 所需的任務(wù)棧的名字為應(yīng)用的包名。

  • 如果要給 Activity 設(shè)定不同的任務(wù)棧舟山,可把該屬性值改為與包名不同的值绸狐。

TaskAffinity 主要與 singleTask 啟動模式 、 allowTaskReparenting 屬性配對使用累盗,其他情況下無意義寒矿。

每個Activity都有TaskAffinity屬性,這個屬性指出了它希望進(jìn)入的Task若债。如果一個Activity沒有顯式的指明該Activity的TaskAffinity符相,那么它的這個屬性就等于Application指明的TaskAffinity,如果Application也沒有指明蠢琳,那么該TaskAffinity的值就等于包名啊终。而Task也有自己的affinity屬性,它的值等于它的根Activity的TaskAffinity的值傲须。

如果加載某個Activity的intent蓝牲,F(xiàn)lag被設(shè)置成FLAG_ACTIVITY_NEW_TASK時,它會首先檢查是否存在與自己taskAffinity相同的Task躏碳,如果存在搞旭,那么它會直接宿主到該Task中,如果不存在則重新創(chuàng)建Task菇绵。

注:taskAffinity 的值至少要有一個 「.」肄渗。否則應(yīng)用會因為解析出錯而無法安裝。

1.2.2 Activity的 Flags / 27

Activity 的 flag 有很多 咬最,這里主要分析一些比較常用的==標(biāo)記位==翎嫡。

標(biāo)記位的作用

  • 設(shè)定待啟動的 Activity 的啟動模式
    • FLAG_ACTIVITY_NEW_TASK 《===》 singleTask 啟動模式
    • FLAG_ACTIVITY_SINGLE_TOP 《===》 singleTop 啟動模式
    • FLAG_ACTIVITY_CLEAR_TOP 通常與 FLAG_ACTIVITY_NEW_TASK 結(jié)合使用。一起使用時永乌,通過這些標(biāo)志惑申,可以找到其他任務(wù)中的現(xiàn)有 Activity,并將其放入可從中響應(yīng) Intent 的位置翅雏。
  • 影響 Activity 的運(yùn)行狀態(tài)
    • FLAG_ACTIVITY_CLEAR_TOP 具有此標(biāo)志位的 Activity圈驼,當(dāng)它啟動時,在同一個任務(wù)棧內(nèi)所有位于其上的 Activity 都需要出棧望几。 一般 需要和 FLAG_ACTIVITY_NEW_TASK 配合使用
    • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 具有這個標(biāo)記的 Activity 不會出現(xiàn)在歷史 Activity 列表中绩脆,
      • 等價于在 XML 中指定 Activity 的屬性 android:excludeFromRecents = "true"
        大部分情況下,并不需要指定標(biāo)記位,對于標(biāo)記位只要理解即可靴迫。
        使用時要注意 有些標(biāo)記位是系統(tǒng)內(nèi)部使用的惕味,應(yīng)用程序不需要手動設(shè)置這些標(biāo)記位(以防止出現(xiàn)問題)

1.3 IntentFilter的匹配規(guī)則 / 28

“顯式調(diào)用需要明確地指定被啟動對象的組件信息,包括包名和類名玉锌,而隱式調(diào)用則不需要明確指定組件信息名挥。原則上一個Intent不應(yīng)該既是顯式調(diào)用又是隱式調(diào)用,如果二者共存的話以顯式調(diào)用為主主守≠骶螅”

一個過濾列表中的 action、category参淫、data可以有多個

IntentFilter 中的過濾信息有

  • action蹋艺、
  • category、
  • data

只有一個Intent同時匹配action類別黄刚、category類別、data類別才算完全匹配民效,只有完全匹配才能成功啟動目標(biāo)Activity憔维。一個Activity 可以有多組 intent-filter,一個 Intent 只要匹配任何一組 intent-filter 即可成功啟動對應(yīng)的 Activity畏邢,

<activity
    android:name=".MainActivity"
    android:configChanges="locale|orientation">
    <intent-filter>
        <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"/>
        <action android:name="android.intent.action.SYNC"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

1. action 的匹配規(guī)則

如果過濾列表中有指定 action 的話业扒,要求 Intent 中的 action 存在且必須和過濾規(guī)則中的其中一個 action 相同。

  • ==注意: action 區(qū)分大小寫==舒萎。

2. category 的匹配規(guī)則

intent 中的如果含有 category 程储,那么所有的 category 必須和過濾規(guī)則中的所有 category 相同。 即 「一一對應(yīng)臂寝,==完全匹配==」章鲤。

為什么不設(shè)置 category 也能匹配呢?

  • 原因:系統(tǒng)在 startActivity 或者 startActivityForResult 的時候咆贬,會默認(rèn)為 Intent 加上"android.intent.category.DEFAULT"這個 category败徊。
  • :要使我們的 activity 能夠接受隱式調(diào)用,就必須在 intent-filter 中指定 android.intent.category.DEFAULT" 這個 category掏缎。(不加的話因為系統(tǒng)默認(rèn)給Intent添加了這樣的一個 category 會導(dǎo)致不匹配)

3. data的匹配規(guī)則

data的匹配規(guī)則與 action 的匹配規(guī)則相類似皱蹦。匹配其中的一組即可

data的語法如下

<intent-filter>
    <data
        android:scheme="string"
        android:host="string"
        android:port="string"
        android:path="string"
        android:pathPattern="string"
        android:pathPrefix="string"
        android:mimeType="text/plain"/>
</intent-filter>

data 由 兩部分組成,mimeType 和 URI眷蜈。

  • mimeType 指媒體類型沪哺,比如:image/jpeg
  • URI 統(tǒng)一資源標(biāo)識符

URI 的結(jié)構(gòu):
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

栗子:

content://com.example.project:200/folder/subfolder/etc
https://www.baidu.com:80/search/info

每個數(shù)據(jù)的含義:

  • Scheme: URI 的模式, eg: http酌儒、file辜妓、content。
    • 默認(rèn)值為 content 和 file
  • Host: URI 主機(jī)名, eg: www.baidu.com
  • Port: URI 中的端口號嫌拣,
  • path pathPattern 和 pathPrefix:這三個參數(shù)表述路徑信息柔袁,
    • path 表示完整的路徑信息
    • pathPattern 也表示完整的路徑信息,但是它里面可以包含通配符 「* 」异逐,「 *」表示 0 個 或者 多個任意字符捶索。
      • 由于 正則表達(dá)式 的規(guī)范, 如果想表示真實的字符串灰瞻, 「* 」 要寫成「\* 」, 「\ 」要寫成「\\ 」腥例。
    • pathPrefix 表示路徑的前綴信息

如果要在代碼中為 Intent指定完整的 data必須調(diào)用 setDataAndType 方法酝润, setData 和 setType 方法會互相清除對方的值燎竖。

data 的特殊寫法

可以在一個 data 標(biāo)簽中寫出多個 scheme host port,也可以在不同行中分別寫要销。

寫法一
<data
    android:host="www.baidu.com"
    android:mimeType="text/plain"
    android:scheme="http"/>
    
寫法二    
<data android:mimeType="video/mpeg"/>
<data android:scheme="http"/>
<data android:host="www.baidu,com"/>

開發(fā)中的 Tip

對于 Service 建議是构回,盡量使用顯式調(diào)用方式來啟動服務(wù)

通過 隱式啟動一個 Activity 的時候,可以做一下判斷疏咐,看是都存在 Activity 能夠響應(yīng)我們的隱式 Intent纤掸。
判斷方法有兩種:

  1. PackageManager 的 resolveActivity 方法
    • PackageManager 還提供了 queryIntentActivities 方法。
    • resolveActivity 的區(qū)別: 不是返回最佳匹配的 Activity浑塞,而是返回所有成功匹配的 Activity 信息借跪。
  2. Intent 的 resolveActivity 方法

resolveActivity 找不到對應(yīng)的 Activity 會返回 null。

public abstract List<ResolveInfo> queryIntentActivities(Intent intent, @ResolveInfoFlags int flags);
public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags);

注意: 第二個參數(shù)酌壕,要使用 MATCH_DEFAULT_ONLY 這個標(biāo)記位掏愁,如果沒有,會把那些==沒有在 intent-filter 中聲明 <category android:name="android.intent.category.DEFAULT"/> 的 Activity 也匹配出來==卵牍,從而導(dǎo)致 startActivity 失敗

  • 含義是:僅匹配 那些在 intent-filter 中聲明 了 <category android:name="android.intent.category.DEFAULT"/> 這個 category 的 Activity果港。

在 action 和 category 中,有一類 action 和 category 比較重要

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

這兩者的共同作用是用來標(biāo)明這是一個入口 Activity 并且會出現(xiàn)在 系統(tǒng)的應(yīng)用列表中辽慕,少了任何一項都沒有實際意義京腥,也無法出現(xiàn)在系統(tǒng)列表中,也就是二者缺一不可溅蛉。

如果一個應(yīng)用中有n個 Activity 都在 manifest 文件中注明了上述的 intent-filter 那么應(yīng)用列表會出現(xiàn)n個 該App 的圖標(biāo)公浪。點擊各個圖標(biāo)分別進(jìn)入以相應(yīng) Activity 為主頁的界面。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末船侧,一起剝皮案震驚了整個濱河市欠气,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镜撩,老刑警劉巖预柒,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件队塘,死亡現(xiàn)場離奇詭異,居然都是意外死亡宜鸯,警方通過查閱死者的電腦和手機(jī)憔古,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淋袖,“玉大人鸿市,你說我怎么就攤上這事〖赐耄” “怎么了焰情?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剥懒。 經(jīng)常有香客問我内舟,道長,這世上最難降的妖魔是什么初橘? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任验游,我火速辦了婚禮,結(jié)果婚禮上保檐,老公的妹妹穿的比我還像新娘批狱。我一直安慰自己,他們只是感情好展东,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炒俱,像睡著了一般盐肃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上权悟,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天砸王,我揣著相機(jī)與錄音,去河邊找鬼峦阁。 笑死谦铃,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的榔昔。 我是一名探鬼主播驹闰,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼撒会!你這毒婦竟也來了嘹朗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤诵肛,失蹤者是張志新(化名)和其女友劉穎屹培,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褪秀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年蓄诽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媒吗。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡仑氛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝴猪,到底是詐尸還是另有隱情调衰,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布自阱,位于F島的核電站嚎莉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沛豌。R本人自食惡果不足惜趋箩,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望加派。 院中可真熱鬧叫确,春花似錦、人聲如沸芍锦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娄琉。三九已至次乓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間孽水,已是汗流浹背票腰。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留女气,地道東北人杏慰。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像炼鞠,于是被迫代替她去往敵國和親缘滥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容