Android學習手記之Activity

Activity 是一個應用組件,用戶可與其提供的屏幕進行交互,以執行撥打電話、拍攝照片、發送電子郵件或查看地圖等操作。 每個 Activity 都會獲得一個用于繪制其用戶界面的窗口。窗口通常會充滿屏幕,但也可小于屏幕并浮動在其他窗口之上。

1、生命周期方法

  • onCreate:表示activity被創建,這是生命周期的第一個方法。在這個方法中主要是做一些初始化工作,例如通過setContentView去加載資源文件,初始化數據等。
  • onRestart:表示activity重啟,一般當activity有不可見變為課件狀態的時候,onRestart會被調用,例如按home鍵后調用onPause、onStop.再回到頁面會調用onRestart;
  • onstart:表示activity正在啟動,即將開始時,這個activity已經可見了,但是還沒有出現在前臺,還無法和用戶交互.如果 Activity 轉入前臺,則后接 onResume(),如果 Activity 轉入隱藏狀態,則后接 onStop()。
  • onResume:在 Activity 即將開始與用戶進行交互之前調用。 此時,Activity 處于 Activity 堆棧的頂層,并具有用戶輸入焦點。始終后接 onPause()。
  • onPause:當系統即將開始繼續另一個 Activity 時調用。 此方法通常用于確認對持久性數據的未保存更改、停止動畫以及其他可能消耗 CPU 的內容,諸如此類。 它應該非常迅速地執行所需操作,因為它返回后,下一個 Activity 才能繼續執行。如果 Activity 返回前臺,則后接 onResume(),如果 Activity 轉入對用戶不可見狀態,則后接 onStop()。
  • onStop:Activity 對用戶不再可見時調用。如果 Activity 被銷毀,或另一個 Activity(一個現有 Activity 或新 Activity)繼續執行并將其覆蓋,就可能發生這種情況。如果 Activity 恢復與用戶的交互,則后接 onRestart(),如果 Activity 被銷毀,則后接 onDestroy()。
  • onDestroy:在 Activity 被銷毀前調用,一般做回收工作和最終的資源釋放。這是 Activity 將收到的最后調用。 當 Activity 結束(有人調用 Activity 上的 finish()),或系統為節省空間而暫時銷毀該 Activity 實例時,可能會調用它。 您可以通過 isFinishing() 方法區分這兩種情形。
activity lifecycle

2、保存 Activity 狀態

當 Activity 暫停或停止時,Activity 對象仍保留在內存中 — 有關其成員和當前狀態的所有信息仍處于 Activity 狀態。 因此,用戶在 Activity 內所做的任何更改都會得到保留,這樣一來,當 Activity 返回前臺(當它“繼續”)時,這些更改仍然存在。
系統為了恢復內存而銷毀某項 Activity 時,Activity 對象也會被銷毀,因此系統在繼續 Activity 時根本無法讓其狀態保持完好,而是必須在用戶返回Activity時重建 Activity 對象。但用戶并不知道系統銷毀 Activity 后又對其進行了重建,因此他們很可能認為 Activity 狀態毫無變化。 在這種情況下,您可以實現另一個回調方法對有關 Activity 狀態的信息進行保存,以確保有關 Activity 狀態的重要信息得到保留:onSaveInstanceState()。
系統會先調用 onSaveInstanceState(),然后再使 Activity 變得易于銷毀。系統會向該方法傳遞一個 Bundle,您可以在其中使用 putString() 和 putInt() 等方法以名稱-值對形式保存有關 Activity 狀態的信息。然后,如果系統終止您的應用進程,并且用戶返回您的 Activity,則系統會重建該 Activity,并將 Bundle 同時傳遞給 onCreate() 和 onRestoreInstanceState()。您可以使用上述任一方法從 Bundle 提取您保存的狀態并恢復該 Activity 狀態。如果沒有狀態信息需要恢復,則傳遞給您的 Bundle 是空值(如果是首次創建該 Activity,就會出現這種情況)。
比如配置發生變化,activity會依次調用onPause、onSaveInstanceState、onStop、onDestory、onCreate、onRestoreInstanceState.

注:無法保證系統會在銷毀您的 Activity 前調用 onSaveInstanceState(),因為存在不需要保存狀態的情況(例如用戶使用“返回” 按鈕離開您的 Activity 時,因為用戶的行為是在顯式關閉 Activity)。 如果系統調用 onSaveInstanceState(),它會在調用 onStop() 之前,并且可能會在調用 onPause() 之前進行調用。

但是,您可能會遇到這種情況:重啟應用并恢復大量數據不僅成本高昂,而且給用戶留下糟糕的使用體驗。 在這種情況下,您有兩個其他選擇:

  1. 在配置變更期間保留對象
    允許 Activity 在配置變更時重啟,但是要將有狀態對象傳遞給 Activity 的新實例。
  2. 自行處理配置變更
    阻止系統在某些配置變更期間重啟 Activity,但要在配置確實發生變化時接收回調,這樣,您就能夠根據需要手動更新 Activity。

2.1在配置變更期間保留對象

如果重啟 Activity 需要恢復大量數據、重新建立網絡連接或執行其他密集操作,那么因配置變更而引起的完全重啟可能會給用戶留下應用運行緩慢的體驗。 此外,依靠系統通過 onSaveInstanceState() 回調為您保存的 Bundle,可能無法完全恢復 Activity 狀態,因為它 并非設計用于攜帶大型對象(例如位圖),而且其中的數據必須先序列化,再進行反序列化, 這可能會消耗大量內存并使得配置變更速度緩慢。在這種情況下,如果 Activity 因配置變更而重啟,則可通過保留 Fragment 來減輕重新初始化 Activity 的負擔。此片段可能包含對您要保留的有狀態對象的引用。
當 Android 系統因配置變更而關閉 Activity 時,不會銷毀您已標記為要保留的 Activity 的片段。您可以將此類片段添加到 Activity 以保留有狀態的對象。
要在運行時配置變更期間將有狀態的對象保留在片段(Fragment)中,請執行以下操作:

  1. 擴展 Fragment 類并聲明對有狀態對象的引用。
  2. 在創建片段(Fragment)后調用 setRetainInstance(boolean)
  3. 將片段(Fragment)添加到 Activity。
  4. 重啟 Activity 后,使用 FragmentManager 檢索片段。

例如,按如下所示定義片段(Fragment):

public class RetainedFragment extends Fragment {

    // data object we want to retain
    private MyDataObject data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(MyDataObject data) {
        this.data = data;
    }

    public MyDataObject getData() {
        return data;
    }
}

注意:盡管您可以存儲任何對象,但是切勿傳遞與 Activity 綁定的對象,例如,DrawableAdapterView或其他任何與 Context 關聯的對象。否則,它將泄漏原始 Activity 實例的所有視圖和資源。 (泄漏資源意味著應用將繼續持有這些資源,但是無法對其進行垃圾回收,因此可能會丟失大量內存。)

然后,使用 FragmentManager 將片段添加到 Activity。在運行時配置變更期間再次啟動 Activity 時,您可以獲得片段中的數據對象。 例如,按如下所示定義 Activity:

public class MyActivity extends Activity {

    private RetainedFragment dataFragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);

        // create the fragment and data the first time
        if (dataFragment == null) {
            // add the fragment
            dataFragment = new DataFragment();
            fm.beginTransaction().add(dataFragment, “data”).commit();
            // load the data from the web
            dataFragment.setData(loadMyData());
        }

        // the data is available in dataFragment.getData()
        ...
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // store the data in the fragment
        dataFragment.setData(collectMyLoadedData());
    }
}

在此示例中,onCreate() 添加了一個片段或恢復了對它的引用。此外,onCreate() 還將有狀態的對象存儲在片段實例內部。onDestroy() 對所保留的片段實例內的有狀態對象進行更新。

2.2自行處理配置變更

如果應用在特定配置變更期間無需更新資源,并且因性能限制您需要盡量避免重啟,則可聲明 Activity 將自行處理配置變更,這樣可以阻止系統重啟 Activity。

注:自行處理配置變更可能導致備用資源的使用更為困難,因為系統不會為您自動應用這些資源。 只能在您必須避免Activity因配置變更而重啟這一萬般無奈的情況下,才考慮采用自行處理配置變更這種方法,而且對于大多數應用并不建議使用此方法。

要聲明由 Activity 處理配置變更,請在清單文件中編輯相應的 <activity>
元素,以包含 android:configChanges
屬性以及代表要處理的配置的值。android:configChanges
屬性的文檔中列出了該屬性的可能值(最常用的值包括 "orientation" 和 "keyboardHidden",分別用于避免因屏幕方向和可用鍵盤改變而導致重啟)。您可以在該屬性中聲明多個配置值,方法是用管道 | 字符分隔這些配置值。
例如,以下清單文件代碼聲明的 Activity 可同時處理屏幕方向變更和鍵盤可用性變更:

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

現在,當其中一個配置發生變化時,MyActivity 不會重啟。相反,MyActivity 會收到對 onConfigurationChanged() 的調用。向此方法傳遞Configuration 對象指定新設備配置。您可以通過讀取 Configuration 中的字段,確定新配置,然后通過更新界面中使用的資源進行適當的更改。調用此方法時,Activity 的 Resources 對象會相應地進行更新,以根據新配置返回資源,這樣,您就能夠在系統不重啟 Activity 的情況下輕松重置 UI 的元素。

注意:從 Android 3.2(API 級別 13)開始,當設備在縱向和橫向之間切換時,“屏幕尺寸”也會發生變化。因此,在開發針對 API 級別 13 或更高版本系統的應用時,若要避免由于設備方向改變而導致運行時重啟(正如 minSdkVersion
targetSdkVersion
屬性中所聲明),則除了 "orientation"值以外,您還必須添加 "screenSize" 值。即,您必須聲明 android:configChanges="orientation|screenSize"。但是,如果您的應用是面向 API 級別 12 或更低版本的系統,則 Activity 始終會自行處理此配置變更(即便是在 Android 3.2 或更高版本的設備上運行,此配置變更也不會重啟 Activity)。

例如,以下 onConfigurationChanged() 實現 檢查當前設備方向:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

Configuration 對象代表所有當前配置,而不僅僅是已經變更的配置。大多數時候,您并不在意配置具體發生了哪些變更,而且您可以輕松地重新分配所有資源,為您正在處理的配置提供備用資源。 例如,由于 Resources 對象現已更新,因此您可以通過 setImageResource() 重置任何 ImageView,并且使用適合于新配置的資源(如提供資源中所述)。請注意,Configuration 字段中的值是與 Configuration 類中的特定常量匹配的整型數。有關要對每個字段使用哪些常量的文檔,請參閱 Configuration參考文檔中的相應字段。

請謹記:在聲明由 Activity 處理配置變更時,您有責任重置要為其提供備用資源的所有元素。 如果您聲明由 Activity 處理方向變更,而且有些圖像應該在橫向和縱向之間切換,則必須在 onConfigurationChanged()
期間將每個資源重新分配給每個元素。

如果無需基于這些配置變更更新應用,則可不用實現 onConfigurationChanged()
。在這種情況下,仍將使用在配置變更之前用到的所有資源,只是您無需重啟 Activity。 但是,應用應該始終能夠在保持之前狀態完好的情況下關閉和重啟,因此您不得試圖通過此方法來逃避在正常 Activity 生命周期期間保持您的應用狀態。 這不僅僅是因為還存在其他一些無法禁止重啟應用的配置變更,還因為有些事件必須由您處理,例如用戶離開應用,而在用戶返回應用之前該應用已被銷毀。

如需了解有關您可以在 Activity 中處理哪些配置變更的詳細信息,請參閱 android:configChanges
文檔和 Configuration 類。

3、啟動模式

Android提供了四種啟動模式:

  • standard(默認模式)。系統在啟動 Activity 的任務中創建 Activity 的新實例并向其傳送 Intent。Activity 可以多次實例化,而每個實例均可屬于不同的任務,并且一個任務可以擁有多個實例。
  • singleTop(棧頂復用模式)。如果當前任務的頂部已存在 Activity 的一個實例,則系統會通過調用該實例的 onNewIntent() 方法向其傳送 Intent,并且不會調用這個Activity的onCreate、onStart不會被系統調用。因為它并沒有發生改變,而不是創建 Activity 的新實例。,如果新的activity示例已經存在但沒有位于棧頂,就會重新創建一個activity。(比如當前棧為ABCD,A在棧底、D在棧頂、如果再次啟動D,如果D的模式為SingleTop,那么棧內的情況仍然為ABCD,如果D為standard,那么D背重新創建,棧內的情況為ABCDD)。
  • singleTask(棧內復用模式)。這是一種單例模式,在這種模式下,只要Activity在一個棧中存在,那么多次啟動此Activity都不會重新創建實例,和singleTop一樣,系統也會回調其 onNewIntent() 方法向其傳送 Intent。具體一點,當一個具有singleTask模式的Activity請求啟動后,比如Activity A,系統會首先尋找是否存在A想要的任務棧,如果存在A所需的任務棧,這個時候就會看A是否在棧中有實例存在,如果有實例存在,那么系統就把A調到棧并調用它的 onNewIntent() ,如果實例不存在,就創建A的實例并把A壓入棧中。例如
  • 比如目前的任務棧S1中的情況為ABC,這個時候Activity D以singleTask模式請求啟動,其所需要的任務棧為S2,由于S2和D的實例均不存在,所以系統會創建任務棧S2,然后在創建D的實例并將其壓入到棧S2
  • 另外一種情況,假設D所需的任務棧為S1,其他情況跟上面一樣由于S1已經存在,所以直接創建D的實例并將其入棧到S2.
  • 如果D所需的任務棧為S1,并且當前的任務棧S1的情況為ADBC,根據棧內復用的原則,此時的D不會重建,系統會把D切換到棧頂并調用其onNewIntent方法,同時由于singleTask默認具有clearTop的效果,會導致棧內所有在D上的Activity全部出棧,于是最終的情況為AD。
  • singleInstance(單例模式),加強版的singleTask模式,處了具有singeTask模式的所有特性外,此種模式下的Activity正能單獨的位于一個任務棧中。例如ActivityA,當A啟動后,系統會為它創建一個新的任務棧,然后A獨自在這個新的任務棧中,由于棧內復用的特性,后續的請求均不會創建新的activity,除非這個獨特的任務棧被系統銷毀了。

taskAffinity
用于指定Activity的任務棧,默認情況下所有的Activity的任務棧的名字為應用包名。
當taskAffinity和singleTask啟動模式配對使用,它具有該模式的目前任務棧的名字,待啟動activity會運行在名字和TaskAffinity相同的任務棧中。

給Activity指定啟動模式的方法,即可以在XML文件中配置,也可以在代碼中設置,需要說明的是從優先級上來講,代碼設置的方式要高于XML配置,如果兩種方式同時存在,以代碼設置方式為準,同時XML配置不能指定FLAG_ACTIVITY_CLEAR_TOP,而代碼設置不能指定singleInstance啟動模式。
xml配置方法如下:

 <activity
      ...
     android:launchMode=["multiple"|"singleTop"|"singleTask"|"singleInstance"]
     android:taskAffinity="string"  
   ...
  />

代碼中通過Intent指定方法如下:

  Intent intent =new Intent(context,AnotherActivity.class)
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  startActivity(intent);

Intent的設置啟動模式的Flags的值可以為

  • FLAG_ACTIVITY_NEW_TASK
    在新任務中啟動 Activity。如果已為正在啟動的 Activity 運行任務,則該任務會轉到前臺并恢復其最后狀態,同時 Activity 會在 onNewIntent() 中收到新 Intent。這會產生與 "singleTask" launchMode 值相同的行為
  • FLAG_ACTIVITY_SINGLE_TOP
    如果正在啟動的 Activity 是當前 Activity(位于返回棧的頂部),則 現有實例會接收對 onNewIntent() 的調用,而不是創建 Activity 的新實例。正如前文所述,這會產生與 "singleTop" launchMode 值相同的行為。
  • FLAG_ACTIVITY_CLEAR_TOP
    如果正在啟動的 Activity 已在當前任務中運行,則會銷毀當前任務頂部的所有 Activity,并通過 onNewIntent() 將此 Intent 傳遞給 Activity 已恢復的實例(現在位于頂部),而不是啟動該 Activity 的新實例。產生這種行為的 launchMode
    屬性沒有值。FLAG_ACTIVITY_CLEAR_TOP 通常與 FLAG_ACTIVITY_NEW_TASK 結合使用。一起使用時,通過這些標志,可以找到其他任務中的現有 Activity,并將其放入可從中響應 Intent 的位置。

注:如果指定 Activity 的啟動模式為 "standard",則該 Activity 也會從堆棧中刪除,并在其位置啟動一個新實例,以便處理傳入的 Intent。 這是因為當啟動模式為 "standard" 時,將始終為新 Intent 創建新實例。

  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    具有該標識的activity不會出現在歷史Activity的列表中,當某些情況下我們不希望通過歷史列表回到我們的activity是這個標記比較有用,他等同于XML中指定的Activity屬性android:excludeFromRecents="true"

4、參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,615評論 25 708
  • Activity是一個應用組件,用戶可與其提供的屏幕進行交互。以執行撥打電話,拍攝照片,發送電子郵件或查看地圖等操...
    DanieX閱讀 1,107評論 0 4
  • Activity https://developer.android.com/guide/components/a...
    XLsn0w閱讀 712評論 0 4
  • 一、概述 Activity 作為與用戶交互的一個窗口,是使用非常頻繁的一個基本組件。Android系統是通過Act...
    三也視界閱讀 2,253評論 3 11
  • 下了汽車,我抱著滾蛋急急地跑到站牌下,抬頭看了看天,無奈地從包里翻出一把遮陽傘。 明明是在出發前查過天氣的,可預測...
    Sharl閱讀 138評論 0 1