Android四大組件之Activity全解析

1. 簡介

       本篇不針對於新手,而是對於Activity中一些常識或者問題進行總結。Activity是Android四大組件之一,爲用戶提供與系統交互的界面,每個應用都有一個或者多個Acticity,這樣會有各類各樣的細節問題須要查找,我將本人接觸到的知識點彙總到此篇文章。android

2. 生命週期

       Activity生命週期的回調主要有onCreate()、onRestart()、onStart()、onResume()、onPause()、onStop()、onDestory()這幾個方法,Activity的生命週期類別主要分爲三種,以下。面試

  • 整個生命週期,Activity完整生命週期發生在onCreate()和onDestroy()之間。在onCreate()中執行一些全局性的設置(例如設置佈局文件,初始化View等等),在onDestroy()中釋放全部資源
  • 可見生命週期,Activity可見生命週期發生在onStart()和onStop()之間,在這段時間內,用戶能夠在屏幕上看見Activity並與之交互。在整個生命週期,Activity對用戶可見和隱藏兩種狀態可能交替出現,系統就會屢次調用onStart()和onStop()方法。
  • 前臺生命週期,Activity的前臺生命週期發生在onResume()和onPause()之間,在這段時間內,Activity位於屏幕上其餘Activiy以前,並且獲取屏幕的焦點。Activity可能頻繁的轉入或轉出前臺,例如當設備休眠或者彈出對話框時,系統會調用onPause()方法。由於此狀態可能常常發生變化,因此在這兩個方法中建議作一些輕量級操做。

Activity生命週期圖以下:segmentfault

圖-1 Activity生命週期

3. 啓動與關閉

3.1 啓動

被啓動的Activity必需要在AndroidManifest.xml文件中聲明,不然會拋出異常。框架

  • 正常啓動一個Activity的代碼以下:ide

    // 顯示啓動
    Intent intent = new Intent(this, MyActivity.class);
    // 設置傳遞的數據
    intent.put(KEY_NAME, value);
    startActivity(intent);
    
    // 隱式啓動
    Intent intent = new Intent(ACTION_NAME);
    // 設置其餘匹配規則
    ...
    // 設置傳遞的數據,bundle數據集
    intent.putExtras(bundle);
    startActivity(intent);
  • 啓動一個Activity並獲取其執行結果。佈局

    Intent intent = new Intent(this, MyActivity.class);
    startActivityForResult(intent, REQUEST_CODE);
須要當前Activity重寫onActivityResult()方法以獲取結果。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // 若是結果碼是OK,並且請求碼和咱們設置的請求碼相同
    if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE)
    {
        // 其餘操做
    }        
}

3.2 關閉

  • finish()結束當前的Activity
  • finishActivity(int requestCode)結束當前Activity使用startActivityForResult()方法啓動的子Activity

4. 啓動其餘Activity時候生命週期協調

       當一個Activity A去啓動一個新的Activity B時候,A和B的生命週期並非依次進行,也就是說它們的生命週期會有所重疊。在建立B的時候,A不會徹底中止,更確切的說,啓動B的過程與A中止的過程會有重疊。因此A和B生命週期回調的順序就很重要了,回調順序以下。ui

  • Activity A的onPause()方法執行
  • Activity B的onCreate()、onStart()、onResume()方法依次執行。onResume()方法執行後,Activity B獲取屏幕焦點
  • Activity A的onStop()方法執行

       在知道了從一個Activity到另外一個Activity轉變時候生命週期的順序,平時研發時候就須要注意了。例如,當必須在第一個Activity中止以前存儲數據,以便下一個Activity可以使用,應該在onPause()方法中儲存而不是onStop()方法中。this

5. 狀態保存

       用戶與頁面交互過程當中,會出現應用先後臺切換或者進入其餘頁面等等的狀況,也就是Activity調用暫停(onPause)或者中止(onStop)可是未調用(onDestroy),此時Activity仍然在內存中,其有關狀態和成員信息處於活躍狀態,用戶在Activity中所做的任何更改都會獲得保留,這樣一來,當Activity返回前臺繼續執行時候,這些更改信息依然存在,頁面可以繼續顯示。spa

       可是一旦系統須要內存而將某個Activity銷燬時,當再次回到這個Activity,系統須要重建這個Activity,可是用戶並不知道系統銷燬了這個Activity須要重建,他們但願返回頁面時候頁面仍是保存以前的狀態。這種狀況下,須要咱們手動將一些信息給保存起來,能夠實現Activity中的另外一個回調方法onSaveInstanceState(),保存Activity狀態的一些重要信息。系統會向該方法傳遞一個Bundle,而後咱們能夠向這個Bundle裏面儲存一些重要信息。當系統重建Activity時候,系統會將這個Bundle同時傳遞給onCreate()onRestoreInstanceState()方法,咱們能夠在這兩個方法中恢復以前場景。翻譯

看一下狀態保存的介紹圖。

圖-2 Activity狀態保存

面試時候可能會問到onSaveInstanceState()調用時機,看一下官方源代碼的註釋。

Do not confuse this method with activity lifecycle callbacks such as {@link #onPause}, which is always called when an activity is being placed in the background or on its way to destruction, or {@link #onStop} which is called before destruction. One example of when {@link #onPause} and {@link #onStop} is called and not this method is when a user navigates back from activity B to activity A。

不要把onSaveInstanceState()這個方法和Activity生命週期的幾個方法混淆了,這個方法只有在Activity切換到後臺或者即將被銷燬時候被調用。有一個例子是若是從Activity B返回到Activity A,這個方法是不會被調用的。

       也許很難理解這段註釋的意思,我我的理解是,若是一個Activity失去了屏幕焦點後,失去屏幕焦點通常是指onPause()onStop()方法被調用,onSaveInstanceState()方法就會被調用,有一種特殊狀況是從一個Activity B返回到上一個Activity A,這個方法並不會被調用。

我的總結了一下,大致有如下幾種狀況會調用onSaveInstanceState()

  • 按下Home將程序切換到後臺
  • 關閉屏幕
  • Activity A啓動一個新的Activity B,會回調A中onSaveInstanceState()方法
  • 屏幕橫豎屏方向切換
  • 長按Home或者菜單鍵進入程序列表頁面

爲何平時並無實現onSaveInstanceState()onRestoreInstanceState()方法,可是有些時候,Activity中的UI狀態依然獲得了保存,是爲何?

       在Android中,Activity類的onSaveInstanceState()方法默認實現會調用佈局中每一個View的onSaveInstanceState()方法去保存其自己的狀態信息,Android框架中幾乎每一個控件都會實現這個方法。咱們只須要爲想要保存其狀態的每一個控件提供一個惟一的ID(在xml中設置 android:id屬性),若是控件沒有 ID,則系統沒法保存其狀態。

       咱們能夠經過將View的android:saveEnabled屬性設置爲false或經過調用View的setSaveEnabled()方法顯式阻止佈局內的視圖保存其狀態,一般不須要設置這些屬性,但若是想以不一樣方式恢復Activity UI的狀態,能夠這樣作。

注:因爲沒法保證系統調用onSaveInstanceState()的時機,咱們只用它來保存Activity的瞬間狀態,不要用它來儲存持久性數據,上面提到過,建議在onPause()中儲存持久性數據。

6. 啓動模式

敲黑板敲黑板,劃重點來了,同窗們快拿出筆和紙快作筆記。

       一個應用通常包含不少Activity,它們按照各自打開的順序排列在返回棧(Back Stack)中,這些Activity統稱爲Task。大多數Task的起點是用戶在屏幕中點擊應用圖標啓動應用,該應用的Task出如今前臺,若是該應用沒有Task,也就是最近未被打開,則會新建一個Task,而且會將該應用的MainActivity加入返回棧中,做爲返回棧中的根Activity。

       一般狀況下,當前一個Activity啓動一個新的Activity時候,新的Activity會被加入返回棧中,並處於棧頂,獲取屏幕焦點,而前一個Activity仍保留在返回棧中,處於中止(onStop)狀態。 Activity中止時,如上所說,系統會保存其頁面狀態。當用戶返回時候,當前處於棧頂的Activity會從返回棧中彈出,並被銷燬(onDestroy),恢復前一個Activity的狀態。返回棧中的Activity永遠不會從新排列,遵循先進後出的原則。

圖-3 Activity出入返回棧

上述講的只是標準的Activity與返回棧的關係,在Android中Activity有四種啓動模式,分別是standardsingleTopsingleTasksingleInstance

6.1 設置啓動模式

咱們能夠經過在AndroidManifest.xml配置Activity的啓動模式。

<activity
    android:name=".aidldemo.BindingActivity"
    android:launchMode="standard"
    ... />

或者在代碼中向Intent添加相應標誌。

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

注:第二種方法設置啓動模式的優先級高於第一種,若是二者都存在,以第二種爲準。

6.2 standard(默認模式)

默認的啓動模式,新啓動的Activity放入返回棧棧頂,遵循先進後出原則,同一個Activity能夠被實例化屢次。

6.3 singleTop

  • 若是當前返回棧的頂部不存在該Activity,則新建該Activity並放入棧頂;
  • 若是當前返回棧的頂部已存在Activity的一個實例,則系統會經過調用該實例的onNewIntent()方法向其傳送Intent,不建立該Activity的新實例。

6.4 singleTask

  • 若是該Activity須要的返回棧是A,可是當前系統中不存在A返回棧,系統會新建返回棧A,而後再建立該Activity實例將其壓入返回棧中。
  • 若是該Activity須要的返回棧存在,並且返回棧中沒有該Activity,則新建Activity並放入Task棧頂
  • 若是該Activity須要的返回棧存在,並且返回棧中有該Activity

    • 若是該Activity在棧頂,調用其onNewIntent()方法傳入Intent
    • 若是該Activity不在棧頂,彈出其上面的全部Activity,讓該Activity置於棧頂,並調用其onNewIntent()方法傳入Intent

       默認狀況下,全部Activity所須要的返回棧名稱爲應用的包名,咱們能夠在AndroidManifest.xml中經過設置Activity的android:taskAffinity屬性來指定該Activity須要的返回棧名稱,這個名稱不能和應用包名相同,不然至關於沒有指定。taskAffinity翻譯過來是返回棧親和性,我我的理解這個屬性是指定與返回棧親和度或者優先級,並非每次都會新建返回棧。注意通常android:taskAffinity屬性和singleTask一塊兒使用纔有意義,會新建返回棧,若是隻是指定了android:taskAffinity屬性可是依然是singleTopstandard模式,新啓動的Activity依然會在原來的返回棧中。

6.5 singleInstance

       系統建立一個新的Task並建立Activity的新實例置於新Task返回棧中,可是系統不會將任何其餘Activity的實例放入這個新建的Task中。該Activity始終是其Task惟一僅有的成員,由此Activity啓動的任何Activity,若是沒有指定返回棧名稱,則新啓動的Activity放入默認的返回棧;若是指定了返回棧名稱,則將新啓動的Activity放入指定的返回棧中。

6.6 返回棧順序調用圖(我的理解,你們可跳過)

Android中返回棧分爲前臺返回棧和後臺返回棧,前臺返回棧是指返回棧棧頂的Activity正在和用戶進行交互。
上面說了幾種啓動模式,下面看一下幾種啓動模式混合時候返回棧調度狀況,我我的的理解和官方有些不一樣,這個你們能夠跳過,去看官方的介紹。

我的理解一個應用建立的默認返回棧爲基準,按返回鍵時候,根據返回棧建立順序依次清空返回棧,當默認返回棧清空時候,應用也就關閉了,可是有些後臺返回棧中的Activity並不會當即銷燬。

下面列出幾種特殊狀況的返回棧書序調用圖。

  • 狀況一

    Activity A和Activity B爲默認啓動模式,未設置taskAffinity屬性。
    Activity C啓動模式是singleTask,設置了taskAffinity屬性。
    啓動順序是`Activity A -> Activity B -> Activity C`
    看一下返回棧調用狀況:

圖-4 返回棧圖1

  • 狀況二 Activity A和Activity B爲默認啓動模式,A未設置taskAffinity屬性,B設置taskAffinity屬性爲默認返回棧。 Activity C和Activity D啓動模式是singleTask,設置了相同的taskAffinity屬性。 啓動順序是Activity A -> Activity C -> Activity D -> Activity B 返回棧調用狀況以下圖:

圖-5 返回棧圖2

官方圖,這裏注意一下,官方圖中在StartActivity Y後,Y與X所在返回棧和1與2所在的返回棧是不一樣的,他們並不在同一個返回棧:

圖-6 返回棧圖3

  • 狀況三 Activity A和Activity C爲默認啓動模式,未設置taskAffinity屬性 Activity B啓動模式是singleInstance 啓動順序是Activity A -> Activity B -> Activity C 返回棧調用狀況以下圖:

圖-7 返回棧圖

注:按返回鍵和啓動Activity從返回棧A到返回棧B結果是不一樣的,按返回鍵時候,會首先彈出返回棧A中的Activity,等到返回棧沒有Activity時候,纔會進入另外一個返回棧,這個時候返回棧A已經沒有Activity了。

6.7 XML添加屬性和Intent添加標籤設置啓動模式對比

上面講到的四種啓動模式都是在Androidmanifest.xml中設置啓動模式,也說起了用Intent添加flags來設置啓動模式。下面針對兩種方法作一下對比。

  • FLAG_ACTIVITY_SINGLE_TOP,這個標記的做用是爲Activity指定singleTop啓動模式,與在XML設置啓動模式相同
  • FLAG_ACTIVITY_NEW_TASK,經CClusXX指教,這個標記只是在設置了taskAffinity屬性下有意義,若是被標記的Activity須要de 返回棧不存在,則新建返回棧,而後新建Activity;若是被標記Activity須要的返回棧存在,則將返回棧帶回前臺,並不會建立新的Activity或者調用onNewIntent。
  • FLAG_ACTIVITY_CLEAR_TOP,用這個標記啓動的Activity,當它啓動是,在同一個任務戰中全部位於它上面的Activity都要出棧。這個標記通常會和singleTask啓動模式一塊兒出現,在這種狀況下,被啓動的Activity的實力若是已經存在,那麼系統就會調用它的onNewIntent。若是被啓動的Activity採用standard啓動模式,那麼連同它和它之上的Activity都要出棧,系統會建立新的Activity實力並放入棧頂。singleTask啓動模式默認就具備此標記的效果。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,用這個標記啓動的Activity不會出如今近期任務中,也就是在Android任務列表中隱藏了該Activity,和在XML設置android:excludeFromRecents="true"相同,這個屬性須要配合singleInstance啓動模式纔有用

能夠看到XML設置沒有相似FLAG_ACTIVITY_CLEAR_TOP標記這種效果的啓動模式,而標記中沒有singleInstance這種啓動模式的標記。

注:平時若是咱們使用ApplicationContext.startActivity去啓動一個standard啓動模式的Activity時候,會報錯以下,這是由於standard模式的Activity會默認進入啓動它的Activity的返回棧中,可是因爲非Activity類型的Context(如ApplicationContext)並無所謂的返回棧,因此拋出這個異常。解決這個問題的方法就是在Intent中添加FLAG_ACTIVITY_NEW_TASK的標記,這個時候啓動實際上是以singleTask模式啓動的

ERROR/AndroidRuntime(5066): 
Caused by: 
android.util.AndroidRuntimeException: 
Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

6.8 taskAffinity和allowTaskReparenting結合

       在AndroidManifest.xml中能夠爲Activity同時設置這兩個屬性,taskAffinity這個屬性上面將結果,allowTaskReparenting這個屬性是標記Activity是否能夠更換返回棧,也就是從一個返回棧轉移到另外一個返回棧。當應用須要給其餘應用提供頁面支持的時候,這二者結合起來就頗有意義。

B應用提供了一個對外的Activity C,taskAffinity屬性是應用B包名,allowTaskReparenting設置爲true。

應用A調用了應用B的Activity C,而後按Home鍵退回到主屏幕,單擊應用B的桌面圖標。

  • 若是應用B已經啓動,Activity C會出如今應用B返回棧的棧頂,應用B顯示Activity C,應用A中Activity C消失,也就將是Activity C從應用A的返回棧轉移到應用B的返回棧
  • 若是應用B未啓動,這個時候並非啓動應用B的MainActivity,而是從新顯示了已經被應用A啓動的Activity C,或者說Activity C從應用A的返回棧轉移到了應用B的返回棧中。能夠理解成,因爲應用A啓動Activity C,當應用B啓動新建返回棧時候,系統發現Activity C本來所想要的返回棧建立完畢,就把Activity C從應用A的返回棧轉移到應用B的返回棧。

7. 清空返回棧

若是用戶將應用長時間的切換到後臺,系統會清除返回棧中除了根Activity的全部Activity。當用戶再次回到應用時候,僅恢復根Activity。有幾個標籤可以協助咱們控制返回棧的清空。

  • alwaysRetainTaskState,若是根Activity的該屬性設置爲True,系統會長時間的保存全部的Activity在返回棧中,並不會清空。(殺死應用除外)
  • clearTaskOnLaunch,若是根Activity的該屬性設置爲True,每當用戶離開再返回時候,系統都會將返回棧清空只留下根Activity。這個屬性和alwaysRetainTaskState恰好相反,即時用戶只離開片刻,系統也會清空返回棧。

8.結束語

從開始學Android開始,就想對四大組件進行一些梳理,將本身的知識點細化並記錄下來,可能網上已經有不少關於Activity的文章,沒用的不少,仍是本身來寫靠譜,溫故而知新,共勉。

注:關於Activity啓動匹配比較複雜,能夠去查看個人另外一篇文章Intent以及IntentFilter

相關文章
相關標籤/搜索