回 Android手機程式設計人才培訓班 課程時間表

Activity生命週期與Intent介紹

前言

Activity(活動)為一個應用程式元件,主要用來跟使用者互動用。它提供一個視窗,這個視窗上可以描繪出使用者介面(UI),透過使用者介面就可以跟使用者做互動,一般來說這個視窗和裝置螢幕一樣大,但有時會比較小或是浮動在其它視窗上面。

一個應用程式通常至少會有一個Activity,稱為『main』activity,當應用程式起動後,第一個Activity就會被啟動,接著,透過Activity你可以在啟動其它的Activity,當其它Activity被啟動後,原來的Activity就會被停止,直到被啟動的Activity結束或是使用者按下了『Back』按鈕,原來的Activity就會被恢復繼續。

當一個Activity被啟動或是停止時,應用程式都可以透過回呼(callback)方法收到通知,有數個回呼方法可以在Activity進入不同狀態的時候收到通知,這些狀態則稱之為Activity的生命週期。接下來討論的是如何建立、啟動以及管理Activity的生命週期。

建立Activity(活動)

要建立一個新的Activity有手動和自動兩種方法。

手動加入新的Activity共有三個步驟:

  1. 加入新的XML版面設計

    透過選單:File->New->Android XML File後,選取要新增的版面類型後進行設計新的版面。
    注意:版面(layout)檔名只能使用小寫英文字母,並且檔名開頭不能為數字。
  2. 繼承Activity類別加入新的類別

    展開專案的src目錄,並在套件名稱上點擊滑鼠右鍵後,選擇:New->Class建立新的Java原始碼檔案。
    提醒:

    當類別剛建立出來時為一個空類別,必須覆載父類別AcitivtyonCreate(Buncle)方法,並呼叫setContentView()來載入要使用的介面(View)。

    透過ADT在類別內加入新的覆載方法:

    1. 在要覆載的類別內點擊滑鼠右鍵->Source->Override/Implement Methods...
    2. 將onCreate(Bundle)打勾
  3. 在AndroidManifest.xml中加入新的Activity元素

    在AndroidMenifest.xml中的Application標籤中新增一個Activity標籤並至少設定Name屬性:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" 
              package="com.ntust.MyFirstApp"
              android:versionCode="1"
              android:versionName="1.0" >
    
        <uses-sdk android:minSdkVersion="8"
                  android:targetSdkVersion="19" />
    
        <application android:allowBackup="true"
                     android:icon="@drawable/ic_launcher"
                     android:label="@string/app_name"
                     android:theme="@style/AppTheme" >
            <activity
                android:name=".MainActivity"
                android:label="@string/app_name" >
                    <intent-filter>
                        <action android:name="android.intent.action.MAIN" />
                        <category android:name="android.intent.category.LAUNCHER" />
                    </intent-filter>
            </activity>
    
            <activity android:name=".MyNewActivity" >
            </activity>
        </application>
    </manifest>
    

使用ADT自動建立新的Activity

待補充...

啟動一個Activity

透過呼叫startActivity()方法,並傳遞一個Intent物件,Intent內至少要設定好起要啟動的Activity類別名稱,你就可以啟動另一個Activity

如下範例:
Intent intent = new Intent(this, MyNewActivity.class);
startActivity(intent);

然而,你有時候可能想透過系統做一些其它的事情,例如:送電子郵件(email)、簡訊或是更新系統狀態,這些事情你的Activity可能無法自行做到,因此你也可以透過Intent設定好系統的ACTION代碼後透過系統提供的Activity服務來做,系統會自動幫你啟動適合的Activity。

例如,下面範例透過系統的Activity來發送電子郵件:
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, dataArray);
startActivity(intent);
dataArray參數裡面存放了該ACTION需要用到的資料,每個ACTION都會有規定好要傳遞的資料格式。

啟動Activity並接收回傳值

有時候,當你啟動一個Activity後,該Activity可能會執行一些事情,結束後你希望原來的Activity可以接收結果,這時,你可以改透過呼叫startActivityForResult()方法來取代原本的startActivity()來啟動一個Activity,然後透過在原Activity類別內實現onActivityResult()回呼(callback)方法,當被啟動的Activity結束後,它會透過另一個Intent物件來傳遞資料,你可以在onActivityResult()內收到該Intent物件。

例如,你透過系統提供的Activity想要取得聯絡人資訊:
private void pickContact() 
{
    // 建立一個Intent來取得聯絡資訊,該ACTION_PICK由content provider提供
    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) 
{
    // 如果requestCode是PICK_CONTACT_REQUEST且resultCode回傳成功(RESULT_OK)
    if(resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) 
    {
    
        // 開始取得聯絡人資料
        Cursor cursor = getContentResolver().query(data.getData(),
        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
        
        // 如果資料還沒結束則回傳true
        if(cursor.moveToFirst())
        { 
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
            String name = cursor.getString(columnIndex);
            
            // 你要做的其它事情....
        }
    }
}

因為onActivityResult()可能會用來處理多個Activity的回傳結果,因此第一個你必須檢查requestCode是不是你要的結果,requestCode是你在呼叫startActivityForResult()傳遞的第二個參數。而且你還必須檢查由Activity傳過來的resultCode是否為執行成功,成功的話你會收到RESULT_OKresultCode,這樣你收到的Intent物件才確認是有效的,由被呼叫的Activity回傳回來的結果。當呼叫的Activity如果是你自行開發的話,你可以使用系統提供的RESULT_OK常數來當成你Activity執行成功的resultCode,而且也建議你這麼做。

傳送回傳值

在Activity結束前可以透過呼叫setResult()並將資料放到Intent物件中來回傳資料給呼叫的Activity。

....
Intent it = new Intent();
Bundle bundle = new Bundle();
bundle.putString("returnData", "Hello, I'm return.");
it.putExtras(bundle);
setResult(RESULT_OK, it);
finish();
....
或是如果你只是想單純回傳狀態,而沒有要回傳資料可以這樣做:
....
setResult(RESULT_OK);
finish();
....

關閉Activity

你可以透過直接在Activity的任何地方呼叫finish()方法來直接結束本身這個Activity的執行,或是原Activity呼叫finishActivity()來主動結束被你呼叫的Activity。

在大部分的情況下,你不應該直接使用上面方法來直接結束一個Activity。Android系統會管理所有Activity的生命,所有的Activity都有自己的生命週期,應該由Android系統來管控,在程式內主動去結束掉Activity可能會改變生命週期而影響到使用者的體驗。

管理Activity生命週期

當然一個 activity 通常不只一個 UI(它可能包含了多個 View), 所有的 Activity 在系統裏由 Activity stack 所管理, 當一個新的 Activity 被執行後,它將會被放置到 stack 的最頂端,並且變成"running activity", 而之前的 Activity 原則上還是存在 stack 中,但不會是在 foreground(前景)的情況。

一般來說,View指的是使用者可以見到的UI,Activity是在UI底下提供功能的內在。一個 Activity基本上有四個狀態Active,Paused,Stopped,Dead:

Active (活動)

Active狀態是使用者啟動Application或Activity後,Activity在執行中的狀態。在Android平台上,相同時間下只會有一個Activity處於Active或Running狀態。其他的Activity都處於Dead, Stopped或是Paused的狀態。

Paused (暫停)

Paused 狀態是當 Activity 暫時暗下來, 退到背景畫面的狀態. 例如當電話來時, 原本執行中的Activity退到背景畫面。新出現的介面元件蓋住了原來的 Activity 畫面。Activity處在Paused狀態時,使用者無法與原來的 Activity 互動。

Stopped (停止)

Stopped狀態是有其它Activity正在執行,而這個Activity已經離開螢幕,不再動作的狀態。透過長按Home鍵,可以叫出所有處於Stopped狀態的Application List(應用程式列表)。在Stopped狀態的Activity,還可以透過Notification來喚醒。

Dead/Inactive (已回收或未啟動)

Dead狀態是Activity尚未被啟動,已經被手動終止,或已經被系統回收的狀態。要手動終止Activity,可以在程式中呼叫finish()方法。

當一個Activity是暫停或停止狀態時,系統可以根據目前記憶體的狀態來決定使否直接終止該Activity並釋放記憶體。當Activity再次被啟動時,將再從頭開始建立。

實作生命週期的回呼方法(callback method)

當Activity被交換叫前景或背景時,不同的狀態切換下,系統會在切換狀態時,如果你有實作回呼方法,你將可以在狀態切換時被通知。

透過管理生命週期的回呼(callback)方法,可以開發出更強健且彈性的應用程式,Activity的生命週期會根據其它Activity狀態所影響。

以下為每一個狀態的回呼方法實作:
public class ExampleActivity extends Activity 
{
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        // Activity被建立起來了
    }

    @Override
    protected void onStart() 
    {
        super.onStart();
        // Activity將要被顯示出來
    }

    @Override
    protected void onResume() 
    {
        super.onResume();
        // Activity已經被顯示出來
    }

    @Override
    protected void onRestart() 
    {
        super.onRestart();
        // Activity正在被重新啟動
    }

    @Override
    protected void onPause() 
    {
        super.onPause();
        // 另一個Activity已經得到焦點,而該Activity將要被暫停
        // Another activity is taking focus (this activity is about to be "paused").
    }

    @Override
    protected void onStop() 
    {
        super.onStop();
        // Activity已經為不可見狀態,且停止運作了
    }

    @Override
    protected void onDestroy() 
    {
        super.onDestroy();
        // Activity已經被終結並釋放所佔的記憶體
    }
}
提醒:你的實作應該在做任何事情之前呼叫父類別的實作『super.onxxxx()』,之後在處理你的事情,讓上面範例。
藉由實現這些方法,你可以監看到三種類型循環的生命週期:
  • Entire lifetime:一個Activity的Entire lifetime是由onCreate()開始, 一直到onDestroy()結束。
    一個Activity可以把所有的資源設定寫在onCreate中, 一直到onDestroy()時再釋放出來。
  • Visible lifetime:一個 Activity的Visible lifetime是指在onStart()onStop()之間。
    在這段時間內,使用者可以在螢幕上看見Activity,要注意這個"Visible"是個形容, Activity不見得一定在foreground(前景)跟使用者直接互動。
  • Foreground lifetime:一個 Foreground lifetime 指onResume()onPause()之間。
    這個時期的Activity是在其他的Activity 的前面,且可以直接跟使用者進行互動。所以這段時期指的就是圖中的Activity is running。
  • Activity生命週期圖

    儲存Activity狀態

    當Activity進入暫停(paused)或停止(stopped)狀態時,Activity的狀態會被保留,這是因為Activity物件的資都還被保留在記憶體裡,所有的資訊和目前的狀態是仍然存活的。所有在執行當中被改變的畫面或資料在Activity回到活動(active)狀態時都會被復原。

    foreground (when it "resumes"), those changes are still there.

    然而,當系統因為記憶體不足而銷毀Activity,系統將無法復原Activity狀態。當Activity重新被執行的時候,系統比須從新開始建立該Activity的狀態,但使用者有時可能做了一些重要的事情到一半,因為某種原因Activity被停止了,如果應用程式重新建立,將造成使用者的心血白費,因此我們可以透過onSaveInstanceState()來存下Activity狀態的資訊。

    系統會在銷毀Activity之前呼叫onSaveInstanceState()方法,系統會傳給該方法一個Bundle物件,你可以將你要儲存的資料透過一對對『名字-資料』的格式柔後呼叫不同資料型態 (像putString())的方法存到該物件當中。當系統銷毀了你的應用程式後,但使用者又啟動了該Activity時,系統會重新建立該Activity並且會在onCreate()onRestoreInstanceState()傳遞剛剛那個Bundle物件。你可以透過這個Bundle取回你存下來的資料來恢復Activity的狀態,如果你在上次Activity被銷毀時沒有存下狀態或是該Activity為第一次執行,Bundle物件將為一個null(空)物件。

    onSaveInstanceState()和onRestoreInstanceState()方法被呼叫的時機
    系統並沒有保證onSaveInstanceState()onRestoreInstanceState()方法一定會被呼叫,因為當系統判定狀態並不需要被儲存的時候(例如:使用者按下『Back』按鈕離開的時候),如果是使用者明確的要關閉Activity,而不是被系統強制銷毀,該方法就不會被呼叫。如果系統有呼叫onSaveInstanceState()方法,那它會在onStope()之前被呼叫,也可能在onPause()之前就被呼叫。

    然而,即使你沒有做任何onSaveInstanceState()的實作,Activity類別內部預設也有實作該方法。Android框架幫內建的所有widget元件做預測存下狀態的功能,但你必須幫每個元件取一個獨一無二的ID名稱,系統才有辦法幫你做這件事情。因為系統有預設的onSaveInstanceState()onRestoreInstanceState()方法,所以當你實作自己的方法時,你必須在你的方法內先呼叫原來的父類別方法後再開始做自己要做的事情。

    你也可以在layout檔中元素裡加上android:saveEnable="false",或是在程式碼內呼叫setSaveEnable(boolean)來關閉該元件的自動復原功能,但通常不會這麼做,除非你不想恢復Activity的狀態。

    因為onSaveInstanceState()方法並不保證被呼叫,且它只能用來恢復介面上的狀態,如果你的應用程式當中有重要的資料是需要永久保存的(像是使用者辛苦打的文章或是玩家的遊戲進度等等),你應該在onPause()當中來儲存永久性資料以避免Activity在非預期情況下被系統銷毀。

    如果要測試狀態儲存,可以透過旋轉螢幕測試,因為旋轉螢幕,原來的Activity會被銷毀後再重建。

    處理組態的改變

    一般裝置可以在應用程式執行時改變狀態(像:螢幕的方向,鍵盤和語言)。當狀態改變時,Android會重新建立執行中的Activity(呼叫onDestroy()後立刻呼叫onCreate(),這個機制設計來讓你的應用程式如果有提供新的版面及資源可以自動載入(像是不同方向的版面、不同大小的版面等等)。

    最好的方式來儲存或復原狀態是使用onSaveInstanceState()onRestoreInstanceState()(或onCreate())。

    協調每個Activitie

    當一個Activity啟動另一個Activity時,會有兩個不同的生命週期的轉換。第一個Activity會暫停而且停止(但假使它還是可以看見的狀態,雖然已經在背景了,並不會立刻被停止)。重要的是,在第二個Activity被建立起來之前,第一個Activity並不會被停止,而不是第二個Activity被啟動的時候,第一個Activity就被覆蓋掉而停止了。

    生命週期回呼方法已經完整的定義,當兩個Activity在互相轉換的時候,所有被呼叫的方法都是可以推論出來的。這裏有Activity A呼叫Activity B時,回呼方法被呼叫的順序範例:
    1. Activity A的onPause()方法被呼叫。
    2. Activity B的onCreate(), onStart()onResume()方法被順序呼叫。(Activity B現在擁有使用這個焦點)
    3. 然後,如果Activity A已經在畫面上看不見,它的onStop()會被呼叫。

    這些是可以推論出來的生命週期回呼方法被呼叫的順序,讓你可以決定Activity轉換期間和時該做什麼事情。例如:如果你打算在Activity被停止之前將資料寫回資料庫,比較好的時間點應該是onPause(),而不是onStope(),因為根據生命週期表,有些時候,轉換並還不會到達onStop(),原Activity就又被切回來了。

    簡單的總結幾個動作:
    • onCreate()用來做程式的初使化動作。
    • onDestory()通常都拿來把 onCreate()時的資料做釋放的動作。
    • onPause()時把需要保存的資料保存。
    • onResume()把保存的資料拿回來使用。

    再歸納一般 Android Application 遵循的動作流程:
      • 一般啟動
      • onCreate -> onStart -> onResume
      • 啟動一個 Activity 的基本流程是: 分配資源給這個 Activity(onCreate), 然後將 Activity 內容顯示到螢 幕上(onStart), 在一切就緒後, 取得螢幕的控制權(onResume), 使用者可以開始使用這個程式。
      • 呼叫另一個 Activity
      • onPause(1) -> onCreate(2) -> onStart(2) -> onResume(2) -> onStop(1)
      • 先凍結原本的 Activity, 再交出直接存取螢幕能力(onPause )的過程. 直到 Activity 2 完成一般啟動流程後, Activity 1 才會被停止。
      • 回復原 Activity
      • onPause(2) -> onRestart(1) -> onStart(1) -> onResume(1) -> onStop(2) -> onDestroy(2)
      • 按 Back 鍵可以回到原本的 Activity。
      • 退出/結束
      • onPause -> onStop -> onDestroy
      • 如果程式中有直接呼叫 finish 函式來關閉 Activity 的話, 系統會暫停(Pause), 停止(Stop)然後銷毀 (Destroy)。
      • 回收後再啟動
      • onCreate -> onStart -> onResume
      • 被回收掉的 Activity 一旦又重新被呼叫時,會像一般啟動一樣再次呼叫 Activity 的 onCreate 函式。