讀古今文學網 > Android程序設計:第2版 > 對生命週期進行可視化 >

對生命週期進行可視化

從本書前面的部分及Android開發者文檔中,你可能已經瞭解到了組件生命週期流圖,並瞭解了生命週期如何工作。這些描述的問題在於組件生命週期是動態的,而狀態圖是靜態的。此外,組件和過程生命週期轉換是由內存管理驅動的:當消耗完內存,在組件生命週期內會恢復內存。內存分配、垃圾收集及跨進程的內存恢復方式在本質上沒有運行代碼塊那麼明確,而且是依賴於配置的。通過檢測和運行代碼,將會看到應用的生命週期,可以在運行程序中進行實驗。

對活動生命週期進行可視化

下面將通過運行檢測程序使得Activity組件生命週期更清晰,並使用Eclipse中的LogCat視圖,觀察Activity生命週期的行為。以下代碼是Activity子類列表,實現了生命週期方法,並在每個方法中實現了日誌記錄。代碼中標注的生命週期是指在後文中的按方法解釋的生命週期處理。查看該列表,看要記錄什麼樣的信息:


package com.oreilly.demo.pa.ch10.finchlifecycle;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
public class FinchLifecycle extends Activity {
    // Make strings for logging
    private final String TAG = this.getClass.getSimpleName;
    private final String RESTORE = ", can restore state";
    // The string "fortytwo" is used as an example of state
    private final String state = "fortytwo";
    @Override
    public void onCreate(Bundle savedState) {
1
        super.onCreate(savedState);
        setContentView(R.layout.main);
        String answer = null;
        // savedState could be null
        if (null != savedState) {
            answer = savedState.getString("answer");
        }
        Log.i(TAG, "onCreate"
            + (null == savedState ? "" : (RESTORE + " " + answer)));
    }
    @Override
    protected void onRestart {
2
        super.onRestart;
        // Notification that the activity will be started
        Log.i(TAG, "onRestart");
    }
    @Override
    protected void onStart {
3
        super.onStart;
        // Notification that the activity is starting
        Log.i(TAG, "onStart");
    }
    @Override
    protected void onResume {
4
        super.onResume;
        // Notification that the activity will interact with the user
        Log.i(TAG, "onResume");
    }
    protected void onPause {
5
        super.onPause;
        // Notification that the activity will stop interacting with the user
        Log.i(TAG, "onPause" + (isFinishing ? " Finishing" : ""));
    }
    @Override
    protected void onStop {
6
        super.onStop;
        // Notification that the activity is no longer visible
        Log.i(TAG, "onStop");
    }
    @Override
    protected void onDestroy {
7
        super.onDestroy;
        // Notification that the activity will be destroyed
        Log.i(TAG,
                "onDestroy "
                        // Log which, if any, configuration changed
                        + Integer.toString(getChangingConfigurations, 16));
    }
    // ////////////////////////////////////////////////////////////////////////////
    // Called during the life cycle, when instance state should be saved/restored
    // ////////////////////////////////////////////////////////////////////////////
    @Override
    protected void onSaveInstanceState(Bundle outState) {
8
        // Save instance-specific state
        outState.putString("answer", state);
        super.onSaveInstanceState(outState);
        Log.i(TAG, "onSaveInstanceState");
    }
    @Override
    public Object onRetainNonConfigurationInstance {
9
        Log.i(TAG, "onRetainNonConfigurationInstance");
        return new Integer(getTaskId);
    }
    @Override
    protected void onRestoreInstanceState(Bundle savedState) {
十
        super.onRestoreInstanceState(savedState);
        // Restore state; we know savedState is not null
        String answer = null != savedState ? savedState.getString("answer") : "";
        Object oldTaskObject = getLastNonConfigurationInstance;
        if (null != oldTaskObject) {
            int oldtask = ((Integer) oldTaskObject).intValue;
            int currentTask = getTaskId;
            // Task should not change across a configuration change
            assert oldtask == currentTask;
        }
        Log.i(TAG, "onRestoreInstanceState"
                + (null == savedState ? "" : RESTORE) + " " + answer);
    }
    // ////////////////////////////////////////////////////////////////////////////
    // These are the minor life cycle methods, you probably won't need these
    // ////////////////////////////////////////////////////////////////////////////
    @Override
    protected void onPostCreate(Bundle savedState) {
⑪
        super.onPostCreate(savedState);
        String answer = null;
        // savedState could be null
        if (null != savedState) {
            answer = savedState.getString("answer");
        }
        Log.i(TAG, "onPostCreate"
                + (null == savedState ? "" : (RESTORE + " " + answer)));
    }
    @Override
    protected void onPostResume {
⑫
        super.onPostResume;
        Log.i(TAG, "onPostResume");
    }
    @Override
    protected void onUserLeaveHint {
⑬
        super.onUserLeaveHint;
        Log.i(TAG, "onUserLeaveHint");
    }
}
  

運行應用的準備工作完成後,首先選中Window→Show View→Other命令,顯示LogCat視圖,展開Show View對話框的Android文件夾。然後選擇LogCat,如圖10-1所示。

圖10-1:從顯示的列表中選中LogCat視圖

現在在模擬器或物理設備上運行該應用。因為本章給出的實例是基於Android API 11級別的Fragment API(對應於Android 3.0版本的Honeycomb)和Android Support Package中的Fragment類構建的,故你可以以任何一種代碼來運行該實例。

運行後將首先看到Eclipse的LogCat視圖中的日誌信息。只需要查看前文列表中的代碼輸出的日誌信息,可以過濾掉日誌信息。單擊日誌窗口工具欄中綠色的加號,彈出日誌過濾器對話框的定義,如圖11-2所示。

在這個例子中,可以基於在Finch Lifecycle類中使用的標籤來過濾日誌,它剛好是類的名字FinchLifecycle。把該過濾器命名為activity-lifecycle,如圖10-2所示。

圖10-2:定制過濾器,只顯示標籤為FinchLifecycle的日誌數據

現在,當運行程序時,會看到LogCat視圖中有一個標籤為activity-lifecycle的選項卡,其中只有在活動生命週期方法中輸出的日誌,如圖10-4所示。如果想查看所有的日誌信息,則可以查看Log選項卡,其中顯示的是未過濾的日誌。

當運行程序時,你會發現如果使用模擬器來運行Android 3.0,會出現如圖10-3所示的情況。

我們在這裡使用Android 3.0是因為本章涉及了生命週期和Fragment類。

圖10-3:在Android 3.0模擬器上運行本章示例代碼

注意:如果想在設備或模擬器上運行程序,則可以使用示例的backported版本,它利用了Android Support Package,它支持Fragment和Android API 11級到API 4級別的類和Android 1.6對應。

在LogCat視圖的activity-lifecycle選項欄,首先看到的是如圖10-4所示的日誌消息。

為了生成有意義的日誌信息,可以啟動應用並來回操作,使用應用切換開關或啟動器返回到Finch應用。啟動其他的應用,在返回到Finch應用時,會看到進程ID(即PID)發生了改變,但是應用的狀態看起來和之前的一致。這是因為該活動的狀態已經保存了,而且應用的所有其他組件狀態也保存了。圖10-5所示的日誌信息截圖說明了這種轉換。

圖10-4:顯示新的進程及保存的活動狀態的日誌輸出

圖10-5:輸出日誌說明了正在恢復的新進程和活動的狀態

注意:如果發現在LogCat視圖中沒有輸出,則切換到DDMS透視圖方式(使用Window菜單),並單擊設備或正在使用的Devices視圖中的模擬器。

啟動其他需要內存的應用,會觸發Android恢復內存的一些策略。當然,因為Android應用運行在Java之類的虛擬機上,所以第一件事就是垃圾回收,即回收那些閒置的、沒有引用的對象實例。Android為垃圾回收增加了另一種策略:用戶看不見的活動組件可以保存其狀態,然後「銷毀」這些組件,實際上只是系統消除這些組件的引用,然後可以對它們執行垃圾回收。Android還提供了另一種內存恢復策略:通知應用中的所有組件保存其狀態,然後可以刪除整個進程,恢復內存。這就是Android支持的跨越多個進程的「垃圾回收」方式。

內存恢復和生命週期

Android活動的生命週期似乎很短暫。可以「殺死」活動進程或「銷毀」Activity對象。最重要的是,甚至無法保證所有的生命週期中的方法能夠在該進程被殺死之前會得到調用。

理解Android生命週期的一個良好的基礎是把重點放在當銷毀Activity實例和殺死進程後會發生什麼。

銷毀活動

當Android想要丟棄某個Activity類的實例時,它會調用onDestroy方法銷毀活動。discard是指Android系統會設置其到Activity實例的引用為null。這意味著除非你的代碼保存了指向該活動的引用,否則該活動會逐漸被垃圾回收。destroy這個詞有時不易理解——它表示主動刪除。

在調用onDestroy方法後,可以確定該Activity類的實例不會再被使用,但是它並不表示應用,或者其所在的進程會停止運行。實際上,可以初始化並調用同一個Activity子類的實例。例如,在配置改變(如改變屏幕方向)時導致之前使用的Activity類被刪除,因而資源加載會重新以新的配置啟動執行。

終止進程

當Android系統的內存耗光時,它會殺死進程。通常情況下,Android應用運行在不同的進程中,因此垃圾收集不能夠回收Android系統中的所有內存。這表示在內存低的情況下,Android會查找不用的組件並終止它們。在極端情況下,Android還會終止正在使用的組件。在簡單的應用中,這些進程在調用onPause方法後就成為要被終止的候選進程。也就是說,如果Android系統需要終止某些進程來獲取內存,那麼在onPause方法之後調用的所有其他的Activity生命週期內的方法都無法確保會被調用。

在這兩個例子中,應用可能需要保存一些狀態,它臨時保存在應用的用戶界面中:尚未處理的各種用戶輸入,一些非數據模型的可視化指示符的狀態等。這就是為什麼應用的每個組件,尤其是每個活動,需要覆蓋一些生命週期方法的原因。

Activity類的生命週期方法

我們知道了通常情況下何時以及為何調用生命週期方法,下面一起來查看在前文給出的代碼中的各個方法及它們都執行了什麼。

1 創建Activity實例後會調用onCreate方法。這是大多數應用執行大部分初始化工作的原因:讀取佈局,創建View實例,綁定數據等。注意,如果沒有銷毀Activity實例,進程也沒有被殺死,就不會再調用。只有當創建新的Activity類的實例時才會調用。該方法的參數是Bundle對象,它包含了保存的應用狀態。如果不存在保存的狀態,則該參數的值會是null。

2 只有當活動停止後,才執行onRestart方法。「停止」是指該活動不在前端和用戶交互。在onStart方法之前會調用該方法。

3 當Activity對象及其視圖可見時,會調用onStart方法。

4 當Activity對象及其視圖和用戶交互時,會調用onResume方法。

5 當不同的Activity實例可見並且當前的Activity已經停止和用戶交互時,會調用onPause方法。

6 當Activity不可見或不再和用戶交互時,會調用onStop方法。

7 當Activity實例要被刪除時,會調用onDestroy方法——以後不再使用該方法。在調用這個方法之前,該活動已經停止和用戶交互,並且在屏幕上不可見。如果由於調用finish方法執行該方法,則調用isFinishing會返回true。

保存和恢復實例狀態

Activity子類保存狀態是為了恢復內存和組件生命週期。下面介紹如何保存狀態及何時保存。

有一個Bundle類,以鍵-值形式保存序列化數據。這些數據可以是基礎類型,也可以是實現了Parcelable接口的任何類型(見P118「Parcelable」一節)。可以在Android Developers網站上找到更多關於Bundle的信息:http://developer.android.com/reference/android/os/Bundle.html。為了保存Activity實例的狀態,需要使用Bundle類的put方法

在調用onCreate方法和onRestoreInstanceState方法時,會向該方法傳遞Bundle對象。Bundle對像保存了該Activity類的前一個實例所保存的數據,使得它能夠在不同實例之間共享。也就是說,如果Activity實例包含狀態,除了數據模型中所保存的,在該Activity類的多個實例之間可以保存和恢復這些數據。從用戶角度來看,似乎是從之前的狀態繼續執行,而實際上可能是Activity類的全新實例,可能在全新的進程中運行。

你可能已經注意到onPause生命週期方法並未提供Bundle對像來保存狀態。因此,什麼時候保存狀態呢?在Activity類中,有多種方法來保存狀態以及通知狀態正在恢復。

8 這時應用可以有機會保存實例狀態。實例狀態應該不會在應用的數據模型中持久存在,例如indicator狀態或者Activity對象的其他狀態。在父類中包含該方法的實現:它調用該Activity實例的每個View對象的onSaveInstanceState方法,它能夠保存這些View對象的狀態,而且是你需要以這種方式保存的唯一狀態。子類需要保存的數據是通過Bundle類的put方法保存的。

十 當要恢復實例狀態時,要調用onRestoreInstanceState方法。onRestoreInstanceState方法是在onStart方法和onPostCreate方法之間調用的,它是在P287「Activity類的生命週期方法」後文中描述的小生命週期方法。

配置變化和活動生命週期

在前面,我們探討了通過啟動足夠的應用來觸發Android系統殺死activity和應用的每個其他組件。如圖10-5所示的截圖日誌,顯示了進程ID的變化,並創建了定義該應用如何和用戶交互的Activity子類的新實例。該新的實例會重新加載該activity的所有資源,而且如果需要加載某些應用數據,則會重新加載這些資源。其帶來的直接影響是用戶看不出變化並繼續執行操作:新的實例看起來和老的實例相似,因為它們的狀態相同。

還有另一種方式可以強制Android使用新的Activity實例:改變系統的配置。應用最常遇到的配置變化是屏幕方向的變化。有很多可以影響配置更新的因素:鍵盤是否可訪問、語言環境、字體大小變化等。所有配置變化的共有特徵是,它們需要重新加載資源,因為通常需要重新計算佈局。

確保活動中的每個資源都根據新的配置加載的最簡單的方式是刪除活動的當前實例,並創建新的實例,從而重新加載所有資源。要使在模擬器上運行的應用重新加載資源,按下數值鍵9。它會改變模擬器的屏幕方向。在日誌中,會看到如圖10-6所示的屏幕截圖。從日誌中,會發現由於刪除Activity實例,配置發生變化,從而會調用onDestroy方法,而不是當系統由於內存低殺死進程時調用。你還會注意到對於多個Activity對象的新實例,進程ID保持不變——系統不需要從使用的應用中恢復內存。

這種做法看起來很浪費:創建Activity的新實例?有什麼用呢?為什麼不能保留原來的實例?這樣不會太慢嗎?但是,在大多數情況下,活動在啟動時加載的資源構成了該Activity實例的大部分狀態。在很多情況下,活動中發生的大部分計算是在讀取XML文件並計算佈局時發生的,而且,在大多數情況下,配置變化(如屏幕方向變化或語言環境變化)時幾乎所有的資源都需要重新計算其佈局。因此,把配置修改成使活動重啟動是不可避免的。

圖10-6:當調用onDestroy方法時,PID保持不變

記住,當Android「銷毀」一個activity時,唯一發生的是消除了到該活動的引用,該引用最終會被垃圾回收。每次當用戶從一個活動移動到另一個活動時,會執行創建該活動的所有計算。當配置發生變化時,將配置變化轉換為活動的重啟動作是不可避免的,所需的計算量也與重啟基本相同。

Activity類的小生命週期方法

有另外幾個方法,在生命週期過程中也會被調用,但它們不是在Android文檔中描述的活動的主生命週期方法。

 onPostCreate方法是在onRestoreInstanceState方法後調用的。如果應用需要恢復兩個階段的狀態,則可以使用onPostCreate方法。它是通過傳遞包含該實例狀態的Bundle對像實現的。

 onPostResume方法是在onResume方法後調用的,這時Activity實例應該是可見的而且在和用戶交互。

 當由於用戶操作導致活動不可見並停止和用戶交互時,會調用onUserLeaveHint方法——例如,按下後退鍵或home鍵。這是清除警告和對話的很便捷的方式。

在圖10-6所示的進程列表中,可以看到我們覆蓋了這些方法,從而可以通過日誌查看什麼時候調用了這些方法。例如當你需要額外的階段來保存實例狀態時,會需要這些方法。

但是,如果你真的需要在配置變化之間保存一些數據,而且這些狀態不能在實例之間的Bundle對像中保存,而且要保存的不是數據模型,那麼可以使用onRetainNonCon-figurationInstance方法來「隱藏」到一個對象的引用。新的Activity實例可以通過getLastNonConfigurationInstance方法請求該引用。

9 在onStop方法之後會調用onRetainNonConfigurationInstance方法,這表示不能夠保證該方法會被調用,而且即使被調用,也不能保證其返回的引用會被保存並提供給後續的Activity實例。可以在onCreate方法中調用getLastNonConfiguration-Instance方法,或者在後續恢復活動狀態時可以調用該方法。

為了說明這些方法的使用方法,當調用onRetainNonConfigurationInstance方法時,我們返回包含該活動的任務ID的對象,而當調用onRestoreInstanceState(Bundle)方法時,我們會檢查該任務ID。這種方式可以保證即使組件實例或整個進程對於用戶而言都已經變化,但從系統角度看它們還是同一個任務。

這些方法最常見的應用場景是保存Web查詢結果:可以重新執行查詢,但是到Web服務器的延遲可能是數秒的時間。因此,如果系統不能保存,則可以重新創建數據檢索新Activity對象,但是如果緩存這些數據則有很大優勢。但是,在本書第13章中,我們將向你說明在RESTful應用中如何使用本地數據庫進行緩存以減少這種類型的優化需要。