天天看點

Android Activities學習[Android Developers譯作整理]1、定義2、建立Activity3.啟動Activity4、關閉Activity5、管理Activity的生命周期

1、定義

Activity就是給使用者提供可視化互動界面的應用程式元件。
通常一個應用程式有多個松散組合的Activity組成,應用程式需要指定一個mainActivity作為程式首次啟動的入口點。一個Activity能夠啟動另外一個Activity,當一個新的Activity啟動時,系統将它壓入棧頂,先前的Activity就會停止,系統會将它儲存到回退棧(back stack)中,目前的Activity完成後,使用者通過按下傳回按鈕,目前的Activity就被銷毀,先前的Activity就會從回退棧彈出并恢複它之前的狀态。
Activity的這些動作都是通過它的生命周期回調方法實作的,開發者需要根據時機,在适當的回調方法裡做一些相應的處理工作。

2、建立Activity

必須繼承自Activity或者Activity的子類,必須實作相應的生命周期回調方法,其中OnCreate()方法必須實作。

2.1 實作使用者界面

定義一個布局,往布局中添加想要的部件。然後在OnCreate()方法中,使用setContentView()來将布局綁定到Activity。

2.2 在manifest檔案中聲明Activity

<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >
           
往<application>元素裡添加<activity>子元素,并使用<android:name>屬性來指定Activity的類名。

2.3 使用intent過濾器

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
           
在<activity>元素裡添加一個<intent-filter>元素,<action>指定該Activity能夠響應的動作,<category>指定該Activity可以響應的intent類型。

3.啟動Activity

3.1 不需要傳回結果

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
           
使用startActivity(Intent)方法來啟動另外一個Activity,這個Intent指定了要啟動的Activity,或者描述需要執行的動作類型(可以啟動本應用程式的Activity,也可以是其他應用程式的)。
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
           

3.2 需要傳回結果

有時候,我們需要獲得新啟動Activity的處理結果,這時候就需要使用startActivityForResult()來啟動Activity,然後通過實作onActivityResult()來接收後續啟動的Activity傳回的結果:當後續的Activity完成後,它會傳回一個Intent給onActivityResult()方法。
private void pickContact() {
    // Create an intent to "pick" a contact, as defined by the content provider URI
    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
    startActivityForResult(intent, PICK_CONTACT_REQUEST);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
        // Perform a query to the contact's content provider for the contact's name
        Cursor cursor = getContentResolver().query(data.getData(),
        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
        if (cursor.moveToFirst()) { // True if the cursor is not empty
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
            String name = cursor.getString(columnIndex);
            // Do something with the selected contact's name...
        }
    }
}
           

4、關閉Activity

通過調用finish()方法可以關閉一個Activity,也可以通過調用finishActivity()方法來關閉一個之前啟動的獨立Activity。

注:Android系統會為我們管理Activity,是以在大多數情況下,我們不需要顯式地去關閉一個Activity,因為這可能會對使用者體驗産生不利的影響,除非你可以确定使用者确實不再需要使用這個Activity。

5、管理Activity的生命周期

5.1 實作生命周期函數

通過實作Activity生命周期回調方法來管理Activity生命周期,這對實作一個健壯而且靈活的應用程式很重要。一個Activity的生命周期是直接受與它相關聯的其他Activity和它的任務和回退棧影響。

從本質上來說,一個Activity存在以下三種狀态:

  • Resumed(Running):Activity運作在前台,并且擁有使用者焦點;
  • Paused:另外一個Activity運作在前台并且擁有焦點,但是目前Activity仍然是可見的。(這可能是另外一個Activity在目前Activity的上方可見,或者是它部分透明或沒有覆寫整個螢幕)。處于該狀态的Activity仍是完全存活的(該Activity的對象保留在記憶體中,維持着所有狀态和成員資訊,而且還與視窗管理器相關聯),當系統記憶體非常貧乏時,系統将把它殺死。
  • Stopped:此時Activity完全被另外一個Activity遮住(也就是說它現在在“背景”)。處于該狀态的Activity也仍然是存活的(該Activity的對象保留在記憶體中,維持着所有狀态和成員資訊,但不與視窗管理器相關聯)。此時它對于使用者來說是不可見的,當其他地方需要記憶體空間的時候,系統将把它殺死。
Android Activities學習[Android Developers譯作整理]1、定義2、建立Activity3.啟動Activity4、關閉Activity5、管理Activity的生命周期
說明:
  • Activity的整個生命周期是從調用onCreate()開始到調用onDestroy()為止。應該在onCreate()方法裡設定一些全局的狀态(比如定義布局),在onDestroy()方法裡釋放剩餘的資源。
  • Activity的可見生命周期是從調用onStart()開始到調用onStop()為止。在此期間,使用者能夠看見Activity并與它互動。在這兩個方法之間,我們可以保持需要展示給使用者的資源。這個周期在整個Activity的生命周期裡可能被調用多次,因為Activity可以交替出現和隐藏。
  • Activity的前台生命周期是從調用onResume()開始到調用onPause()為止。在此期間,Activity在所有其他Activities前面,并且擁有使用者焦點。Activity可以頻繁地在前背景切換,是以這兩個方法裡的代碼應該盡量輕量,以免使用者等待時間過長。

5.2 儲存Activity狀态

當Activity從paused或stopped狀态回到resumed狀态時,所有的Activity狀态都能恢複。

但是當系統為了回收記憶體而銷毀了一個Activity,記憶體中的Activity對象被銷毀了,當重新傳回到這個Activity的時候,系統就不能簡單地将Activity恢複到銷毀前的原樣了。當使用者導航回到這個Activity的時候,系統必須重建立立這個Activity對象,但是使用者卻意識不到這種變化,是以期望它能夠恢複原樣。在這種情形下,我們可以通過實作一個額外的回調函數onSaveInstanceState()來確定Activity的重要狀态資訊能夠儲存下來。

Android Activities學習[Android Developers譯作整理]1、定義2、建立Activity3.啟動Activity4、關閉Activity5、管理Activity的生命周期

系統在Activity受到攻擊被破壞之前,會去調用onSaveInstanceState(),系統會給這個方法傳遞一個能以鍵值對形式存儲Activity狀态資訊的Bundle對象。然後,系統殺死了應用程式程序并且使用者導航回這個Activity,系統重建立立Activity,并且同時給onCreate()和onRestoreInstanceState()方法傳遞帶有狀态資訊的Bundle對象,是以,使用其中一個方法,我們就可以從這個Bundle對象中抽取出儲存的狀态資訊并恢複Activity的狀态。如果沒有存儲任何狀态資訊,那麼這個傳遞過來的Bundle對象是null的,這就好像是Activity第一次被建立時的情形。

注意:在Activity被銷毀之前,不保證onSaveInstanceState()一定會被調用,因為有些情形下是沒有必要儲存狀态的(比如使用者按下後退鍵離開Activity,因為使用者确定需要關閉這個Activity)。如果系統會調用onSaveInstanceState()方法,則是在onStop()或者也可能是在onPause()之前。

然而,即使我們沒有實作onSaveInstanceState(),Activity的onSaveInstanceState()預設實作也會儲存Activity的一些狀态,具體來說,預設實作會為布局中的每一個View調用相應的onSaveInstanceState()來儲存它們本身的狀态資訊。幾乎Android架構裡的每一個元件都以适當的方式實作了這個方法,這樣任意一個UI狀态的改變都會自動儲存,然後在Activity重建的時候恢複。(前提是這個控件有唯一的ID)

我們需要儲存一些不屬于UI狀态的重要成員變量資訊。當我們重寫onSaveInstanceState()方法時,始終需要調用父類的onSaveInstanceState()實作。同樣地,當重寫onRestoreInstanceState(),也需要調用父類的實作。

注意:由于onSaveInstanceState()不保證會被調用,我們應該僅用它來記錄Activity暫時性的狀态(也就是UI的狀态),而不應該用它來儲存持久化的資料,而當使用者離開Activity的時候,應該在onPause()中來儲存持久化的資料(比如儲存到資料庫)。

測試應用程式是否能夠儲存狀态資訊的最簡單辦法是旋轉裝置,當裝置旋轉時,系統銷毀并重建Activity。

5.3 處理配置改變

在運作期間,一些裝置配置可以改變(比如螢幕方向,鍵盤可用性和語言),當這樣的改變發生時,Android系統重建立立正在運作的Activity(系統調用onDestroy()後立即調用onCreate())。這種行為旨在幫助我們用我們所提供的(如不同的螢幕方向和大小不同的布局)替代資源進行應用程式自動重載,以适應新的配置。

如果按照如上所述的恢複Activity狀态正确地設計Activity,處理由于螢幕方向的變化給Activity帶來的重建立立,那麼這個應用程式在Activity生命周期中對于其他突發事件的應對将更具彈性。

處理重新啟動來儲存和恢複Activity狀态的一個的最佳方式是使用onSaveInstanceState()和onRestoreInstanceState()(或onCreate())。

當重建立立Activity需要儲存大量資料的時候,會大大影響使用者體驗,此時,我們有兩種選擇:

a、當配置改變時保留一個對象

如果重新啟動Activity需要恢複大量的資料,重建立立網絡連接配接,或者執行更加深入的操作,這樣由于配置變化帶來的全面重新開機可能導緻很差的使用者體驗。同樣,利用系統使用onSaveInstanceState()儲存的Bundle來重建立立Activity可能也不能完全恢複Activity的狀态,因為Bundle本來就不是設計用來攜帶大對象(比如bitmaps)的,并且它攜帶的資料需要經過序列化和反序列化,這樣會消耗大量的記憶體,使得配置的改變變得緩慢。當Activity由于配置的變化需要重新啟動的時候,我們可以通過保留一個有狀态的對象來減輕重新初始化Activity的負擔。

當運作時配置發生改變時,保留一個對象需要做:

1、重寫onRetainNonConfigurationInstance()方法,以傳回我們想要保留的對象; 2、當Activity再次被建立時,調用getLastNonConfigurationInstance()來恢複對象。

當Android系統由于配置發生改變而關閉Activity時,它會在onStop()和onDestroy()方法之間調用onRetainNonConfigurationInstance()。在onRetainNonConfigurationInstance()的實作中,我們可以傳回任意我們需要在配置改變後能夠有效地恢複狀态的對象。

比如,當我們的程式從網絡中下載下傳了大量的資料,如果使用者旋轉了裝置,則目前Activity需要重建,應用程式就需要重新擷取資料,這樣會影響使用者體驗。此時,我們可以通過實作onRetainNonConfigurationInstance()來傳回一個攜帶了這些資料的對象,然後當Activity重新啟動的時候,使用getLastNonConfigurationInstance()來恢複這些資料。

傳回對象:

@Override
public Object onRetainNonConfigurationInstance() {
    final MyDataObject data = collectMyLoadedData();
    return data;
}
           

注意:雖然我們可以傳回任意對象,但是不應傳回一個與Activity相關聯的對象(比如說是一個Drawable,Adapter,View,或者是其他與Context相關的對象),因為這樣做的話,會洩漏原Activity執行個體的所有views和resources。(洩漏資源是指應用程式一直維持這些資源而不能被垃圾回收,這樣會損失大量記憶體)。

Activity重新開機時擷取資料:

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

    final MyDataObject data = (MyDataObject) getLastNonConfigurationInstance();
    if (data == null) {
        data = loadMyData();
    }
    ...
}
           

getLastNonConfigurationInstance()傳回了通過onRetainNonConfigurationInstance()傳回的資料對象,如果這個對象是null(可能這個Activity的重新開機不僅僅是因為配置改變的緣故),則需要從原始資料源中重新加載資料。

b、自行處理配置改變(不推薦)

如果當一個特點的配置發生改變時,應用程式不需要更新資源,而且需要我們避免Activity重新開機的性能限制,那麼我們就需要自行來處理這些配置的改變了。

注:自行處理配置改變使得我們使用替代資源變得更加困難,因為系統不會自動為我們去應用替代資源。這種技術應該作為我們必須避免因配置改變帶來的Activity重新開機的最後一種手段。

為了聲明Activity處理配置的改變,我們需要在manifest檔案中的<activity>元素裡添加一個屬性 android:configChanges,這個屬性的值指定了我們需要處理什麼樣的配置改變,我們可以使用“|”分隔符來給這個屬性設定多個值。比如:

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

這樣,當它們之間的任一一個配置發生改變時,MyActivity不會重新啟動。取而代之的是MyActivity會接收onConfigurationChanged()的調用,這個方法傳遞一個指定了新裝置配置的Configuration對象,通過讀取這個Configuration對象的域,我們可以确定新的配置,并通過更新界面中使用的資源來作出一些适當的改變。當這個方法被調用時,Activity的Resources對象被更新了,傳回了基于新配置的資源,是以我們可以無需重新開機Activity而輕松地對界面元素進行重置。

注意:自Android3.2開始,當裝置旋轉時,“螢幕尺寸”也發生改變。是以,當我們開發基于Android3.2(API-13)或更高版本的應用程式時(通過minSdkVersion和targetSdkVersion聲明的),如果我們想阻止在運作時由于螢幕旋轉而帶來的Activity重新開機,我們除了包含“orientation”這個屬性值之外還必須包含“screenSize”這個值,也就是說我們必須聲明android:configChanges="orientation|screenSize",如果是基于Android3.2或者更低版本時,則不需考慮這個因素(即使它運作在Android3.2或者更高版本的機器上)。

例如,下面的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對象代表了所有的目前配置,不僅僅是那些改變了的配置。大部分情況下,我們不需要去關心這個配置是如何改變,是否可以簡單地對我們正在處理的有替代資源的資源進行重新配置設定。

注意來自Configuration域的值都是與Configuration類中常量相比對的整數值,使用的時候請注意對号入座。

記住:當聲明Activity自行處理配置的改變時,我們需要負責重置提供了替代資源的元素。比如說,如果我們分别提供了橫豎屏的背景圖檔,當我們在onConfigurationChanged()裡應該根據不同的情形重新配置設定資源。

如果當配置改變時不需要更新應用程式,我們可以不實作onConfigurationChanged(),在這種情況下,當配置改變後,配置改變之前的資源仍然在使用,我們僅僅是避免了Activity的重建。然而,我們的應用程式應該能夠在保持先前狀态完整的情況下關閉和重新開機。是以我們不應該考慮使用這種技術來擺脫在Activity的正常生命周期中保留狀态。不僅因為有其他使我們不能阻止應用程式重新啟動的配置發生更改,而且我們也應該處理諸如使用者離開應用程式,在使用者回來之前這個Activity應該被銷毀這樣的事件。

5.4 協調Activities

當一個activity啟動另外一個activity的時候,它們都經曆了生命周期的轉變:第一個activity暫停然後停止,而另外一個activity被建立。假定這些Activities共享磁盤或者别處的資料,了解到在第二個activity建立前第一個activity還沒有完全停止是非常重要的。相反,啟動第二個activity的程序和停止第一個activity的程序有重疊。

在同一程序中的兩個Activity中,當Activity A 啟動Acivity B,産生的操作順序為: 1、Activity A執行onPause()方法; 2、Activity B按照onCreate(),OnStart(),onResume()的順序執行。(ActivityB現在取得使用者的焦點。) 3、如果ActivityA在螢幕上不再是可見的,它執行onStop()方法。

這個可預測的生命周期回調順序,允許我們管理從一個Activity到另一個Activity的轉換資訊。例如,如果為了讓之後的Activity能夠讀取最新資料,第一個Activity停止時必須進行寫資料庫的操作,那麼我們應該在onPause()裡寫資料庫,而不是在onStop()裡寫。