【Android筆記】Activity不同狀態間轉換研究

      在AndroidSDK發佈的官方文檔裏,實際上已經對Activity所包含的各個重要狀態的概念和相互之間轉換機制作了較爲詳細的描述,但梨子的滋味總是自己嚐了才能體會的深刻,筆者就在開發一些應用過程中出現過奇怪的錯誤,後來經過確定發現是由於沒有對Activity狀態轉換的一些細節理解到位,從而導致應用本身的一些運行狀態被打亂而出現各種錯誤。因此筆者找個時間特地基於AndroidSDK的Activity描述(AndroidSDK路徑/docs/reference/android/app/Activity.html)做了一些實驗,目的是搞清楚Activity各個重要狀態的轉換機制。

     在做實驗前,先依照官方文檔的介紹理清一些重要狀態的概念,筆者會依照自己開發的經驗和理解加上一些解釋,如果大家覺得有疑問和待商榷的地方也可以提出來探討。

     Android針對Activity的管理使用的是棧,就是說某一個時刻只有一個Activity處在棧頂,當這個Activity被銷燬後,下面的Activity纔有可能浮到棧頂,或者有一個新的Activity被創建出來,則舊的Activity就被壓棧沉下去了。從這裏我們可以看出Android似乎是依照一種層次管理所有的Activity的,爲什麼這麼做,個人覺得原因在於Android對Activity的作用定位很重要的一點是考慮其管理與用戶交互的作用,而談到交互,首要的就是界面了,因此Activity是直接涉及到與用戶交互的界面處理的,而任意時刻與用戶處於交互狀態的界面只能有一個,所以Android針對Activity的管理採用了具有層次感的棧的數據結構,理解這一點對於Activity一些狀態轉換的細節處理非常重要。

     依據這種基於界面層次性的理解,官方文檔主要提了兩點:

     1. 只有處於棧頂的Activity一定是出於運行狀態的。當啓動一個新的Activity時,系統會將它置於棧頂,同時使其運行,這個時候先前處於棧頂的Activity則被壓棧,只有當棧頂的Activity退出時纔可能重新處於棧頂。

     2.一個Activity總的來說擁有四種狀態,分別是

       1)激活(運行)狀態:  此時它一定是在屏幕的最前端的,對應Activity棧來說,它是在棧頂的;

       2)暫停狀態:此時它在屏幕上仍然是可見的,但是失去了焦點。這種情況會發生在當有一個不會佔滿整個屏幕或者擁有透明屬性的Activity啓動並獲得了屏幕焦點時。一個暫停狀態的Activity還是存活的(alive),它仍然維持着自己的各個內部狀態和成員信息,仍然和window manager保持連接,但是系統可能會在手機內存極低的情況下殺掉該Activity;

       3)終止狀態:此時這個Activity在屏幕上完全不可見,它已經被其他的Activity擋住了。這時它維持着自己的各個內部狀態和成員信息,但是由於用戶已完全看不見它,其window也會被隱藏掉,也就是說window manager不再管理其window信息了。停止狀態下的Activity會經常由於手機的內存徵用問題被系統殺掉。

       4)當Activity被暫停或者終止時,系統可以把它從內存中清除,這個過程也許會提示用戶是否要結束該Activity,也許只會簡單地殺掉其進程。當這個Activity重新被用戶調出來顯示在界面上時,用戶自己必須保證Activity能夠完全恢復先前維護的內部狀態信息,以使其回到Activity內部所處先前的狀態,以筆者的經驗這種情況如果處理不好很容易出現問題。

      這第二點實際上依據界面層次考慮就是3種情況:

      a.active/running: 此時Activity管理的界面可見,且直接面對和用戶的交互;

      b.pause: 此時Activity管理的界面可見,但處於後端,類似變成了背景;

      c.stop: 此時Activity管理的界面完全不可見。
澄清了上述重要的概念之後,再來看需要研究的Activity具體的函數,很自然可以知道最重要的函數一定就是針對上面三種情況加上Activity自身創建和消毀所涉及到的幾個函數了:

public class Activity extends ApplicationContext {
     protected void onCreate(Bundle savedInstanceState);

     protected void onStart();    
     protected void onRestart();
     protected void onResume();
     protected void onPause();
     protected void onStop();
     protected void onDestroy();
}

筆者對這幾個函數做了測試實驗,以此研究不同狀態間的轉換,和這幾個重要函數的調用順序,實驗非常簡單,兩個Activity之間的跳轉:Activity1的界面上只有一個按鈕,點擊後轉到到Activity2上,Activity2上同樣一個按鈕,點擊後跳到Activity1上。我將上述的函數均打上log,便於觀察,同時爲了過濾其他的log,使用了Error級別,純粹是爲了方便顯示。

大致代碼如下:

----------------------------------------------------------------------------------------------

public class ALStudyActivity extends Activity {
  private String LOG_TAG = "ALStudyActivity"; 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.e(LOG_TAG,"onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button01 = (Button)findViewById(R.id.Button01);
        button01.setOnClickListener(new Button.OnClickListener(){
         public void onClick(View v){
          Intent it = new Intent(ALStudyActivity.this,NewActivity.class);
          startActivity(it);
         }
        });
    }
  @Override
  protected void onDestroy() {

      Log.e(LOG_TAG,"onDestroy");
      super.onDestroy();
  }

  .... //其他幾個測試函數,不再列出

}

public class NewActivity extends Activity {
    private String LOG_TAG = "NewActivity";
    @Override
    public void onCreate(Bundle savedInstanceState) {
     Log.e(LOG_TAG,"onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);
        Button button02 = (Button)findViewById(R.id.Button02);
        button02.setOnClickListener(new Button.OnClickListener(){
         public void onClick(View v){
          Intent it = new Intent(NewActivity.this,ALStudyActivity.class);
          startActivity(it);
         }
        });
    }

    ... //省略,同上

}

----------------------------------------------------------------------------------------------

這裏ALStudyActivity對應Activity1, NewActivity對應Activity2

實驗1. Activity1->按手機(模擬器)Back鍵退出

輸出結果:

 test1

   

 

 

 

 

 

 

 

 

 

 

 實驗1 顯示了Activity在正常情 況下從正常到退出的全部情形。

   onDestory只有在Activity真正退出時纔會被調用。

 

 

 

實驗2. Activity1->手機Home鍵回主界面

輸出結果:

test2

  

 

 

 

 

 

 

 

 

 

 

 

 

 

    實驗2 顯示了一在按手機Home鍵退出時的函數調用時序。由

於不是退出,因此最終沒有調用onDestroy(),但是應用變得不可見,

因此調用了onStop。可以看到系統還調用了保存實例狀態的函數:

onSaveInstanceState(),也就是說按Home鍵退出到主界面系統

會幫助我們保持Activity的一些信息,同時給了我們保存額外需要維

護的信息的機會,這一點請注意,因爲系統不是在任何時候都會調用onSaveInstanceState()的,下面的實驗將可以看到。

 

實驗3.Activity1->Activity2 

輸出結果:

test3    

 

 

 

 

 

 

 

 

 

 

 

 從實驗3可以看到系統在Activity1的onPause()之後先將Activity2創

建並顯示出來,然後在執行的Activity1後續的操作,onPause()之後Acti

vity1就不再是最前端顯示了。

 

 

實驗4.Activity1->Activity2->Activity1

輸出結果: test4

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

實驗4要注意的是,由於我們使用的是

   Intent it = new Intent(...)
   startActivity(it);

的方式,實際上Activity2->Activity1執行的是創建了一個新的

Activity1實例,這和一開始的Activity1不是同一個,也就是說,

這裏Activity棧裏已經有了3個Activity實例,這一點很容易被弄

錯,可能界面上是好像回到了Activity1,但實際上完全不同。

 

實驗5.Activity1->Activity2->Activity1->Back鍵

輸出結果:

test5

   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   實驗5承接了實驗4,只是多按了Back鍵,這時候系統銷燬了

一個Activity1實例,棧裏還有兩個,棧頂的是Activity2,也就是

說界面上看見的是Activity2.

  

 

實驗6.Activity1->Activity2->Home鍵->Activity1

輸出結果:  test7

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

實驗6的特別地方在於,在按了Home鍵回到主界面後,Activity2也

被轉到後臺,程序走到了onStop(),然後我們重新啓動Activity1應

用,可以看到執行onRestart()的是Activity2,而不是Activity1!!

這是因爲此時Activity2處在棧頂,這一點也是很容易出錯的地方。

 

 

實驗7.Activity1->屏幕旋轉90度

輸出結果:

test8

 

   

 

 

 

 

 

 

 

 

 

 

 

  實驗7單獨列出來也是因爲代表了一類Activity被打斷的情況,就是系統配置發

生了變化,官方文檔裏也對爲什麼一定要採用這種先銷燬再重新創建的方式做了解

釋,可以參考Configuration Changes一段,我就不再多介紹了,這裏只是實

際跑了個實驗驗證。

 

 

實驗就做這麼多,其實還可以做更多一些情況的測試,但基本的轉換情況這裏都已提到。雖然運行,暫停,退出的基本概念大家比較清楚,但是從上面的實驗也可以看到,一些細節仍然需要在設計時考慮清楚,因爲畢竟我們無法估計用戶的操作序列和系統本身的各種情況,如果要想使我們的應用夠健壯,必須充分考慮到Activity不同狀態間轉換的問題。

 

此外,還有一些可以深入探討的問題:

1. 關於finish()的使用,上面的實驗沒有用到它,但我們看到前面的實驗只有在按Back鍵的時候系統會銷燬當前的Activity,如果處理不當會產生大量Activity新的實例,而顯然很多不是我們需要的,必須以合適的方式在恰當的時機消除,這時就需要finish(),函數使用雖然簡單,但是要用在什麼時機和地方是需要考慮的。

 

2. 關於onSaveInstanceState()的使用,我們前面的實驗也說明了,不是任何時候系統都會調用onSaveInstanceState()的,原因官方文檔也提了一些,請仔細閱讀,官方舉的一個例子是當一個Activity不再處於最前端時,如果系統由於內存原因需要關掉這個Activity,此時系統會調用onSaveInstanceState(),這樣我們纔有機會做些保存信息以便將來重啓的工作。

 

3.關於onPause()函數,由於Activity從最前端顯示到其他狀態的轉換會很頻繁,這個函數也會很頻繁的被調到,而它是一個同步函數,這個操作不完成,後面的操作無法進行,因此請不要在其中做太多操作,造成性能問題。

 

4.本文涉及的實驗關於Activity的配置都是使用的standard屬性,沒有涉及"singleTop" /"singleTask" / "singleInstance",這些會影響實驗結果,但是基本的狀態轉換通過標準已經體現出來,是其他的基礎,搞清楚了這些再來研究多個task情況下可能涉及到的Activity跳轉就容易理解了。

 

關於Activity的狀態轉換還有很多可以研究的地方,以後有機會再貼出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章