Activity的生命週期/啓動模式/過濾規則

Activity的生命週期/啓動模式/過濾規則

1.1 Activity 的生命週期

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

  • onCreate:該階段可以做一些初始化工作:加載佈局資源(setContentView)、初始化所需數據等。

  • onRestart:Activity重新啓動

  • onStart:Activity正在啓動,即將開始。Activity已經顯示了,但是我們還看不到,不能交互。

  • onResume:Activity可見,能交互。

  • onPause:Activity正在停止。可以做一些不太耗時的操作:存儲數據、停止動畫等。(特殊情況下快速切換會從onPause->onResume,幾乎不能重現。)

  • onStop:Activity即將停止。可以做一些稍微重量級的回收,但不能太耗時。

  • onDestroy:Activity即將銷燬。做回收工作和最終的資源釋放。

    Activity生命週期

  • Activity生命週期說明

    • 特定Activity第一次啓動:onCreate->onStart->onResume。
    • 用戶打開新Activity或返回桌面:onPause->onStop。若新Activity採用透明主題,當前Activity不會回調onStop。
    • 用戶再次回到原Activity時:onRestart->onStart->onResume。
    • 用戶back鍵回退:onPause->onStop->onDestroy。
  • (onStart和onStop)與(onResume和onPause)的區別

    • onStart和onStop是從Activity是否可見這個角度來回調的。
    • onResume和onPause是從Activity是都位於前臺(交互與否)這個角度來回調的。
  • 當前Activity爲A,新Activity爲B;發生的生命週期調用順序

    • A:onPause->B:onCreate->B:onStart->B:onResume->A:onStop
    • 所以不能在onPause裏做耗時操作。應使新Activity儘快到前臺。

1.1.2 異常情況下的生命週期分析

  • 注意:1.1.2部分在新版本(測試API>28)時會有一定差異,差異參考1.1.3部分補充內容。

  • 1.資源相關的系統配置發生改變導致Activity被殺死並重新創建:(手機橫豎屏切換)

    異常情況下Activity重建

    • onSaveInstanceState調用是在onStop之前,與onPause無時序關係。Activity週期正常的情況下不會調用該回調。onRestoreInstanceState調用是在onStart之後。
    • Activity重建是,系統會調用onRestoreInstanceState,將Activity銷燬時onSaveInstanceState方法保存的Bundle對象作爲參數同時傳遞給onRestoreInstanceState和onCreate方法。(可通過onRestoreInstanceState和onCreate方法判斷Activity是否被重建。)
  • 2.內存不足導致低優先級的Activity被殺死

    • 優先級:前臺Activity>可見的非前臺Activity>後臺Activity。

    • 當系統內存不足時,系統會按照優先級從低到高殺死進程,後續通過onSaveInstanceState和onRestoreInstanceState來存儲和恢復數據。如果進程中無四大組件會很快被系統殺死,所以後臺程序不適合脫離四大組件獨自運行在後臺,最好的方式是放在Service中,從而保證進程有一定的優先級,不會輕易被系統殺死。

    • 如果不希望Activity在屏幕橫豎切換是被重建,可以指定configChange屬性:

      <!--可以使用'|'符號連接-->
      android:configChanges="orientation|screenSize"
      

      注意:不會被重建不是被銷燬;是不在通過保存狀態->銷燬->恢復狀態這一流程;上述例子可能會導致不會根據屏幕橫豎屏切換加載適合的資源文件(可能橫屏豎時採用的是不同資源,hdpi<->xhdpi)。不重建情況下,系統會調用onConfigurationChanged方法。

    • configChanges項目及含義

      項目 含義
      mcc SIM卡唯一標識IMSI ( 國際移動用戶識別碼)中的國家代碼,由三位數字組成,中國爲460。
      mnc SIM卡唯-標識IMSI (國際移動用戶識別碼)中的運營商代碼,由兩位數字組成,中國移動TD系統爲00,中國聯通爲01,中國電信爲03。
      locale 設備的本地位置發生了改變,一般指切換了系統語言
      keyboard 鍵盤類型發生了改變,比如用戶使用了外插鍵盤
      touchscreen 觸摸屏發生了改變。
      keyboardHidden 鍵盤的可訪問性發生了改變,比如用戶調出了鍵盤。
      navigation 系統導航方式發生了改變,比如採用了軌跡球導航。
      screenLayout 屏幕布局發生了改變,很可能是用戶激活了另外一個顯示設備。
      fontScale 系統字體縮放比例發生了改變,比如用戶選擇了一個新字號。
      uiMode 用戶界面模式發生了改變,比如是否開啓了夜間模式。
      orientation 屏幕方向發生了改變,比如旋轉了手機屏幕。
      screenSize 當屏幕的尺寸信息發生了改變,當旋轉設備屏幕時,屏幕尺寸會發生變化。當API大於13時會導致Activity重啓。
      smallestScreenSize 設備的物理屏幕尺寸發生改變,這個項目和屏幕的方向沒關係,僅僅表示在實際的物理屏幕的尺寸改變的時候發生,比如用戶切換到了外部的顯示設備。當API大於13時會導致Activity重啓。
      layoutDirection 當佈局方向發生變化。

      常用localeorientationkeyboardHidden。注意screenSize和smallestScreenSize,這兩個項目與編譯有關,與運行環境無關,當API大於13時會導致Activity重啓,所以在例子中也要連接上它。

1.1.3 關於新版情況下的onSaveInstanceState補充

  • 新版本(測試環境API>28)onSaveInstanceState被調用的情況分析

    • 首先onSaveInstanceState的調用的時間有所改變:改爲onStop之後調用。(舊版本在onStop之前調用)

    • onSaveInstanceState的觸發情況有所改變:在Activity有可能被恢復的情況下調用

    • 情況一:Activity A啓動Activity B,那麼A會在調用onStop後調用onSaveInstanceState;之後銷燬B返回到A時:

      • 如果A因內存不足銷燬了就會調用onRestoreInstanceState重建A。
      • 如果A沒有被銷燬就不調用onRestoreInstanceState。
    • 情況二:Activity A啓動Activity B,之後銷燬B返回到A時,B調用onStop後不會調用onSaveInstanceState,這是因爲銷燬B的行爲表示B不會被恢復。

    • 情況三:對情況一的補充。如果Activity A啓動Activity B,如果A仍在頁面上有顯示(透明模式的情況),那麼A不會調用onSaveInstanceState。

  • 新版舊版onRestoreInstanceState對比

    • 1:在 資源相關的系統配置發生改變導致Activity被殺死並重新創建 情況下並無明顯差異。調用週期依然爲:onResume->onPause->onStop->onSaveInstanceState->onDestroy->onCreate->onStart->onRestoreInstanceState->onResume。這個不同於Activity A啓動Activity B的生命週期,A調用onPause後,B開始onCreate,B在onResume後,A開始onStop。

    • 2:在 內存不足導致低優先級的Activity被殺死 情況下有所差異。

      • 新版本調用onSaveInstanceState的根據是Activity有沒有被恢復的可能,如果有可能被恢復就會在調用onStop後直接調用onSaveInstanceState。並且,在A被恢復時,會跟據A是否被銷燬選擇性調用onRestoreInstanceState。
      • 舊版本是在Activity在內存不足情況下,因優先級低要被銷燬時,調用onSaveInstanceState。

1.2 啓動模式

1.2.1 四種啓動模式

  • 1.standard:標準模式。該模式下,誰啓動新Activity,那麼新Activity就運行在啓動它的那個Activity所在的棧中。

    • 可能在低版本情況下可能會出現(Android9以上不會):

      使用ApplicationContext啓動standard模式的Activity時會報錯;因爲非Activity類型的Context並無任務棧,需要添加FLAG_ACTIVITY_NEW_TASK標記位。

  • 2.singleTop:棧頂複用模式。會調用onNewIntent回調,通過該回調可以獲得再請求當前Activity的信息。

  • 3.singleTask:棧內複用模式。

    • 系統會先尋找Activity A想要的任務棧,如果棧不存在,創建棧->創建實例A->壓入棧。
    • 如果棧存在,在棧中尋找是否存在A的實例,不存在,創建實例A->壓入棧;存在,將A調到棧頂並調用onNewIntent回調。
    • singleTask具有clearTop效果,會將棧內所有在A上的實例出棧。
    • 棧頂爲A,再啓動A時會經歷onPause->onNewIntent->onResume。
  • 4.singleInstance:單實例模式。

1.2.2 TaskAffinity

  • TaskAffinity:任務相關性,標識一個Activity所需要的任務棧的名字。

  • 默認情況下,所有Activity所需的任務棧的名字爲應用的包名。

  • 我們可以爲每個Activity都單獨指定TaskAffinity屬性,要與包名不一樣。TaskAffinity屬性主要和singleTask啓動模式或者allowTaskReparenting屬性配對使用,其他情況無意義。任務棧分爲前臺任務棧和後臺任務棧,後臺任務棧中的Activity位於暫停狀態,用戶可以通過切換將後臺任務棧再次調到前臺。

  • 當TaskAffinity和singleTask啓動模式配對使用時,它是具有該模式的Activity的目前任務棧的名字,待啓動的Activity會運行在名字和TaskAffinity相同的任務棧中。

  • 當TaskAffinity和allowTaskReparenting結合使用的時候,比如應用A啓動應用B的某個Activity後,如果這個Activity的allowTaskReparenting屬性爲true的話,那麼當應用B被啓動後,此Activity會直接從應用A的任務棧轉移到應用B的任務棧中。

1.2.3 指定啓動模式

  • 方式一:在Menifest文件中指定,android:launchMode="singleTask"
  • 方式二:在Intent中設置標誌位,intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  • 優先級上,方式二>方式一。限定範圍有所不同:方式一無法設定FLAG_ACTIVITY_CLEAR_TOP標識;方式二無法指定singleInstance模式。

1.2.4 Activity的標識

  • FLAG_ACTIVITY_NEW_TASK:當啓動該標識的Activity A時,先查找是否存在A想要的任務棧(TaskAffinity判斷),如果有,將這個棧移動到前臺,並保持棧中的Activity原有順序,然後創建實例A->壓入棧;如果沒有,創建棧->創建實例A->壓入棧。
  • FLAG_ACTIVITY_SINGLE_TOP:指定singleTop啓動模式。
  • FLAG_ACTIVITY_CLEAR_TOP:具有此標記的Activity啓動時,同一個任務棧中所有位於它上面的Activity都要出棧。通常要配合FLAG_ACTIVITY_NEW_TASK使用。如果被啓動的Activity採用standard模式啓動,那麼它連同它之上的Activity都要出棧,系統會創建新的Activity實例並放入棧頂。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有這個標記的Activity不會出現在歷史Activity的列表中,如果我們不希望用戶通過歷史列表回到我們的Activity時使用。等同於Menifest指定:android:excluedeFromRecents="true"

1.3 IntentFilter 的過濾規則(intent-filter)

  • 啓動Activity時隱式調用需要Intent匹配目標組件的IntentFilter中的過濾信息,過濾信息有actioncategorydata。每種過濾信息可包含多個,當action、category、data完全匹配時才啓動Activity。
  • 一個Activity可以有多個intent-filter,匹配任意一個即可啓動Activity。
  • 補充:原則上一個Activity不應該既是隱式啓動又是顯式啓動,但兩種啓動方式並存時,以顯式啓動爲主。

1.3.1action的匹配規則

  • action是一個字符串,系統預定義了一部分,也可自定義。action區分大小寫。
  • 一個過濾規則中可以有多個action,只要Intent中的action能夠匹配過濾規則中的任何一個即視爲匹配成功。
  • 通過intent.setAction("String")方法設置Intent的action。
  • Intent中必須有action。

1.3.2 category的匹配規則

  • category是一個字符串,系統預定義了一部分,也可自定義。category區分大小寫。
  • Intent中如果有category,那麼Intent中的所有category都必須和過濾規則中的某個或某幾個category匹配。
  • Intent中如果沒有category,視爲通過category匹配。系統調用startAcivity或startActivityForResult時會默認爲Intent加上"android.intent.category.DEFAULT"這個category,而我們設置intent-filter時必須指定"android.intent.category.DEFAULT"這個category。
  • 通過intent.addCategory("String")方法設置Intent的category。

1.3.3 data的匹配規則

  • 與action類似,如果過濾規則中定義了data,那麼Intent中必須也要定義可匹配的data。

  • data在過濾規則中存在兩種寫法:

    • 寫法一:

      <intent-filter ...>
           <android:scheme="file" android:host="www.baidu.com" />
      	...
      </intent-filter>
      
    • 寫法二:

      <intent-filter ...>
           <android:scheme="file" />
           <android:host="www.baidu.com" />
      	...
      </intent-filter>
      
    • 補充:scheme默認值爲contentfile

  • 通過intent.setDataAndType(Uri.parse("URI"),"mimeType")方法設置Intent的data。

    • 注意intent.setData(Uri.parse("URI"))intent.setType("mimeType")方法會互相覆蓋(源碼中會將另一個屬性設爲null),如果要爲Intent指定完整的data,要調用intent.setDataAndType(Uri.parse("URI"),"mimeType")方法。
  • 補充:data結構:

    • data語法:

      <data android:scheme="string"
            android:host="string"
            android:port="string"
            android:path="string"
            android:pathPattern="string"
            android:pathPrefix="string"
            android:mimeType="string" />
      
    • data由兩部分組成,mimeType和URI。

    • mimeType指媒體類型,比如image/jpegaudio/mpeg4-genericvideo/*等。

    • URI:<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

    • Scheme:URI的模式,比如httpfilecontent等。如果URI沒有指定scheme,那麼URI無效。

    • Host:URI的主機名,比如www.baidu.com。如果URI沒有指定host,那麼URI無效。

    • Port:URI的端口號,比如80。URI指定scheme和host時,port纔有意義。

    • Path、pathPattern、pathPrefix:路徑信息。path表示完整的路徑信息;pathPattern表示完整的路徑信息,但是可以包含通配符*;pathPrefix表示路徑的前綴信息。

  • Intent-filter的匹配規則對於Service和BroadcastReceiver相同,不過對Service的建議是儘量使用顯示方式啓動服務。

1.3.4 判斷隱式啓動Activity是否匹配成功

  • 方法一PackageManagerresolveActivity方法:

    /**
      * 1.MATCH_DEFAULT_ONLY標識位表示匹配在intent-filter中聲明瞭<category android:name="android.intent.category.DEFAULT" />的Activity。
      * 2.如果不使用該標識位,會返回不含DEFAULT的那些Activity,這些Activity不支持隱式啓動,從而導致startActivity或startActivityForResult方法失敗。
    */
    if(null!=getPackageManager().resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY)){
       try {
           startActivity(intent);
       }catch (ActivityNotFoundException e){
           e.printStackTrace();
       }
    }
    
    • 補充:PackageManager還提供queryIntentActivities方法,該方法會返回所有匹配成功的Activity的信息;而resolveActivityf方法返回最佳匹配成功的Activity的信息。
  • 方法二IntentresolveActivity方法:

    if(null!=intent.resolveActivity(getPackageManager())){
       try {
           startActivity(intent);
       }catch (ActivityNotFoundException e){
           e.printStackTrace();
       }
    }
    
  • 對於Service和BroadcastReceiver,PackageManager提供類似的方法去獲取成功匹配的組件信息。

1.3.5 其他

  • 有一類重要的action和category:

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

    二者共同表明這是一個入口Activity(二者缺一不可),並且會出現在系統的應用列表中。

1.4 參考資料

  • Android 開發藝術探索
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章