Android 四大元件之 " Activity "

cryAllen發表於2015-02-04
    距離上一篇文章,過去有半個多月了,在此期間忙於工作,疏於整理和總結,特此寫下這篇博文,來談談自己對Activity的理解。總所周知,Activity元件在Android中的重要性不言而喻,我們所能看到的互動動作離不開活動,我們能看到的介面也離不開活動,那麼我想從以下幾個方面來談談:
  • 什麼是活動(Activity)
  • 活動(Activity)用法
  • 活動(Activity)生命週期
  • 活動(Activity)啟動模式
  • 活動(Activity)管理

1,什麼是活動(Activity)

 活動(Activity)是最容易吸引使用者的地方了,它是一種可以包含使用者介面的元件,主要用於和使用者進行互動,一個應用程式可以包含零個或者多個活動。這是我對活動的理解,那我們再看看官方文件是怎麼定義的:“An Activity is an application component that provides a screen with which users can interact in order to do something, such as dial the phone, take a photo, send an email, or view a map.”大概意思是說,Activity是一個可以讓螢幕提供使用者互動動作的元件,比如打電話、照相、傳送郵件和檢視地圖等。簡單來說,我們在應用程式中能看到的內容,絕大多數都是Activity元件提供的。

2,活動(Activity)用法

    在一個應用程式中,有多個Activity組成,其中,有一個特殊的Activity,那就是“main”Activity,代表了應用程式啟動,看到的首個介面。每個Activity啟動其他的Activity可以通過不同動作。接下來我們來建立一個Activity。
 
  2.1,建立一個Activity
    為了建立一個Activity,我們必須要繼承Activity的子類,比如:public class MainActivity extends Activity。同時我們也要過載兩個方法onCreate()、onPause(),程式碼如下:
  
 
  其中onCreate()方法在你的activity建立的時候就呼叫了,你可以在這個方法中初始化一些常量、資源的連線,其中最重要的是setContentView()方法去載入活動介面的佈局。onPause()方法往往是當你活動互動暫停時,一些在介面上填過的互動資料可以在這裡儲存,以免造成丟失,使用者體驗不好。
 
  2.2,宣告活動,Activity在manifest註冊
    除了要定義和繼承Activity之外,我們還需要在manifest中進行註冊,表示宣告,如果不宣告的話,這個Activity在執行時候,會報ANR錯誤,就是應用程式沒有響應。那麼怎麼宣告呢,我們每新建一個專案時候,都會有一個AndroidManifest.xml檔案,我們需要在AndroidManifest.xml檔案中對我們的Activity進行註冊。如圖
  
  可以看到每一個應用程式,都必須有一個AndroidManifest.xml。
  
  
     可以看到,活動的註冊在<application>標籤中,這裡通過<activity>標籤來對活動進行註冊的,首先我們使用了android:name屬性來具體註冊哪一個活動,在這裡我們對我們剛才定義的MainActivity類進行註冊,由於在最外層的<manifest>標籤中已經通過package屬性指定了包名是com.example.helloworld,因此在註冊活動時這一部分就可以省略了,當然你可以定義為com.example.helloworld.MainActivity,不過直接使用.MainActivity也就足夠了,然後我們使用了android:label指定活動中的標題欄的內容,標題欄是顯示在活動的最頂部的,需要注意的是,給主活動指定的label不僅會成為標題欄的內容,還會成為啟動器(Launcher)中應用程式顯示的名稱。之後在<activity>標籤的內容我們加入了<intent-filter>標籤,並在這個標籤裡新增了<action android:name="android.intent.action.MAIN"/>和<category android:name="android.intent.category.LAUNCHER"/>這兩句宣告。表示在手機上點選應用圖示時,首先啟動的就是這個活動,如果你的應用程式中沒有宣告任何一個活動作為主活動,這個程式仍然是可以正常安裝的,只是你無法在啟動中看到或者開啟這個程式。這種沒有主活動的應用程式,基本都是用為第三方服務居多,比如支付寶快捷支付服務。
    基本上以上兩個步驟的結合,這個活動就能在應用程式中執行了。

3,活動(Activity)生命週期

  在說說Activity生命週期之前,我們先來理解"Activity返回棧”的概念。在Android中,多個Activity定義在一個Task中,也就是說一個Task是一組Activity的集合,然後Activity又被安排在back stack,即返回棧,是按照後進先出的規則進出棧的。如圖:
  
  當從手機中啟動一個應用程式圖示時,應用程式的Task隨之也變成前臺程式,如果首次啟用時,發現不存在Task例項,那麼系統會建立一個Task例項,然後把"main”Activity放入到stack中,預設是棧頂,這樣一個Task就管理了一組棧,棧裡管理多個Activity。當從Activity1中啟動Activity2時,Activity1被推到棧底,Activity2變成棧頂,同理,Activity3進棧的過程和Activity2是一樣的,如果我們按back按鈕,則Activity3被彈出,系統會根據記憶體情況進行銷售或者回收,Activity2則被推到棧頂,以此類推。當一直按back,返回到主頁面,則所有的activitys被全部彈出,則task不復存在。
    同時,task有兩種狀態:Foreground和Background,前景和背景。當處於Background時,所有的activitys是停止的,當處於Foreground時,表示當前使用者互動的應用程式。 
    
 比如兩個應用程式A和B,我們剛開始啟動了A應用程式,此時Task A是Foreground,用於與使用者互動,當我們點選Home Button時,此時Task A變成Background,裡面所有的activitys都是停止的,此時如果我們又啟動了B應用程式,Task B又會被例項化,Task B變成Foreground,不過Task A仍然是Background,一直等待著被恢復。所以是Android也是一個多工的系統,不同任務是可以被互相切換的。
    好了關於Task和Stack就先了解這麼多,接下來講講Activity的生命週期。每個活動在其生命週期中最多可能會有四種狀態
  • 執行狀態
  • 暫停狀態
  • 停止狀態
  • 銷燬狀態
  3.1,執行狀態
    當一個活動位於返回棧的棧頂時,這時活動就處於執行狀態。系統最不願意回收的就是處於執行狀態的活動,因為這會帶來非常差的使用者體驗。
 
    3.2,暫停狀態
    當一個活動不再處於棧頂位置,但仍然可見時,這時活動就進入了暫停狀態。比如對話方塊的形式只會佔用螢幕中間的部分割槽域,你很快就會在後面看到這種活動,處於暫停狀態的活動仍然是完全存活的,系統也不願意去回收這種活動,只有在記憶體極低的情況下,系統才會去考慮回收這種活動。
 
    3.3,停止狀態
    當一個活動不再處於棧頂位置,並且完全不可見的時候,就進入了停止狀態。系統仍然會為這種活動儲存相應的狀態和成員變數,但是這並不是完全可靠的,當其他地方需要記憶體時,處於停止狀態的活動有可能會被系統回收。
 
    3.4,銷燬狀態
    當一個活動從返回棧中移除後就變成了銷燬狀態。系統會傾向於回收處於這種狀態的活動,從而保證手機的記憶體充足。
 
    3.5,七個方法
    Activity類中定義了七個回撥方法,覆蓋了活動生命週期的每一個環節。有如下七個方法:
  • onCreate()
  • onStart()
  • onResume()
  • onPause()
  • onStop()
  • onDestroy()
  • onRestart()
 
    onCreate():每個活動中我們都會重寫這個方法,它會在活動第一次被建立的時候呼叫,比如完成一些初始化操作,載入佈局、繫結事件等
    onStart():這個方法在活動不可見變為可見的時候呼叫
    onResume():這個方法在活動準備好和使用者進行互動的時候呼叫,此時的活動一定位於返回棧的棧頂、並且處於執行狀態。
    onPause():這個方法在系統準備去啟動或者恢復另一個活動的時候呼叫。我們通常會在這個方法中將一些消耗CPU的資源釋放掉,以及儲存一些關鍵資料,但這個方法的執行速度一定要快,不然會影響到新的棧頂活動的使用。
    onStop():這個方法在活動完全不可見的時候呼叫。它和onPause()方法主要區別在於,如果啟動的新活動是一個對話方塊式的活動,那麼onPause()方法會得到執行,而onStop()方法不會執行。
    onDestroy():這個方法在活動被銷燬之前呼叫,之後活動的狀態將變為銷燬狀態。
    onRestart():這個方法在活動由停止狀態變為執行狀態之前呼叫,也就是活動被重新啟動了。
    
    除此之外,活動又可以分為三種生存期
  • 完整生存期:活動在onCreate()和onDestroy()方法之間所經歷的
  • 可見生存期:活動在onStart()和onStop()方法之間所經歷的
  • 互動活動期:活動在onResume()和onPause()方法之間所經歷的

    如圖:

  

   讓我們用程式碼來體驗下Activity的生命週期,首先我們先建立一個類來繼承Activity父類,用日誌來列印出活動的七個回撥方法。定義了兩個按鈕,一個是啟動正常的Activity,一個啟動對話方塊的Activity,佈局介面程式碼就不給出了,然後編寫點選事件程式碼。

 1 public class MainActivity extends Activity {
 2     
 3     public static final String TAG = "MainActivity";
 4 
 5     @Override
 6     protected void onCreate(Bundle savedInstanceState) {
 7         super.onCreate(savedInstanceState);
 8         Log.d(TAG, "onCreate");
 9         
10         requestWindowFeature(Window.FEATURE_NO_TITLE);
11         setContentView(R.layout.activity_main);
12         if (savedInstanceState != null) {
13             String tempData = savedInstanceState.getString("data_key");
14             Log.d(TAG, tempData);
15         }
16         Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
17         Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);
18         startNormalActivity.setOnClickListener(new OnClickListener() {
19             @Override
20             public void onClick(View v) {
21                 Intent intent = new Intent(MainActivity.this, NormalActivity.class);
22                 startActivity(intent);
23             }
24         });
25         startDialogActivity.setOnClickListener(new OnClickListener() {
26             @Override
27             public void onClick(View v) {
28                 Intent intent = new Intent(MainActivity.this, DialogActivity.class);
29                 startActivity(intent);
30             }
31         });
32 
33     }
34     
35     @Override
36     protected void onStart() {
37         super.onStart();
38         Log.d(TAG, "onStart");
39     }
40     
41     @Override
42     protected void onResume() {
43         super.onResume();
44         Log.d(TAG, "onResume");
45     }
46     
47     @Override
48     protected void onPause() {
49         super.onPause();
50         Log.d(TAG, "onPause");
51     }
52     
53     @Override
54     protected void onStop() {
55         super.onStop();
56         Log.d(TAG, "onStop");
57     }
58     
59     @Override
60     protected void onDestroy() {
61         super.onDestroy();
62         Log.d(TAG, "onDestroy");
63     }
64     
65     @Override
66     protected void onRestart() {
67         super.onRestart();
68         Log.d(TAG, "onRestart");
69     }
70     
71     @Override
72     protected void onSaveInstanceState(Bundle outState) {
73         super.onSaveInstanceState(outState);
74         String tempData = "Something you just typed";
75         outState.putString("data_key", tempData);
76     }
77 }

 

  首先啟動應用程式,預設進來的就是MainActivity介面,點選執行:觀察LogCat中的列印日誌
  
    可以看到,當MainActivity第一次被執行的時候,會依次執行onCreate()、onStart()和onResume()方法。
    然後點選正常啟動另外一個Activity按鈕,觀察LogCat日誌如下:
    
    由於MainActivity完全被另一個Activity覆蓋了,因此也執行了onPause()和onStop()方法。
    接著我們從另外一個Activity返回到MainActivity介面,即按下返回按鈕,此時觀察的LogCat日誌如下:
    
    會發現由於之前MainActivity已經進入了停止狀態,所以onRestart()方法會得到執行,之後有會依次執行onStart()和onResume()方法。此時要注意,onCreate()方法不會執行,因為MainActivity並沒有重新建立。
    然後我們點選啟動對話方塊的按鈕,觀察LogCat日誌如下:
    
    發現只有onPause()方法得到執行,onStop()方法並沒有執行,這是因為對話方塊的Activity並沒有完全遮擋住MainActivity,此時MainActivity只是進入了暫停狀態,並沒有進入停止狀態。相應的,按下返回鍵返回MainActivity也應該只有onResume()方法得到執行。如圖:
    
    最後在MainActivity按下返回鍵,退出程式,活動當然會被銷售,執行onDestroy()方法。
    
 
    3.6,活動被回收的情況
    前面說過了,當一個活動進入到停止狀態,是有可能被系統回收的,如果我上次執行的臨時資料和狀態存在,則被回收的話,相當於臨時資料被清空了,上次剛剛輸入的文字全部都沒了,那樣的使用者體驗真是糟糕啊。所以Activity中還提供了一個onSaveInstanceState()回撥方法,這個方法會保證一定在活動被回收之前呼叫,因此我們可以通過這個方法來解決會動被回收時臨時資料得不到儲存的情況。
  
1 @Override
2     protected void onSaveInstanceState(Bundle outState) {
3         super.onSaveInstanceState(outState);
4         String tempData = "Something you just typed";
5         outState.putString("data_key", tempData);
6     }

  執行以上程式碼,資料是儲存下來了,那麼應該在哪裡恢復呢,你可以細心的觀察到,在onCreate()方法裡其實也有一個Bundle型別的引數,這個引數在一般情況下都是null,但是當活動被系統回收之前有通過onSaveInstanceState()方法來儲存資料的話,這個引數就會帶有之前所儲存的全部資料,我們只需要再通過相應的取值方法來將資料取出即可。   

1     if (savedInstanceState != null) {
2             String tempData = savedInstanceState.getString("data_key");
3             Log.d(TAG, tempData);
4         }

4,活動(Activity)啟動模式

    每個活動(Activity)都有一個相應的啟動模式,啟動模式一共有四種,分別是standard、singleTop、singleTask、singleInstance,可以在AndroidManifest.xml中通過給<activity>標籤指定android:launchMode屬性來選擇啟動模式。
 
  4.1,standard
  standard是活動預設的啟動模式,在不進行顯示指定的情況下,所有活動都會自動使用這種啟動模式。對於返回棧,在standard模式下,每當啟動一個新的活動,它就會在返回棧中入棧,並處於棧頂的位置。對於standard模式的活動,系統不會在乎這個活動是否已經在返回棧中存在,每次啟動都會建立該活動的一個新的例項。
    如圖:
  
   
    
    4.2,singleTop
    除了有standard模式之外,還存在一種叫做singleTop的模式,當活動的啟動模式指定為singleTop,在啟動活動時如果發現返回棧的棧頂已經是該活動,則認為可以直接使用它,不會再建立新的活動例項。
    如圖:
    
    
    4.3,singleTask
    使用singleTop模式很好地解決重複建立棧活動的問題,可是如果該活動並沒有處於棧頂的位置,還是可能會建立多個活動例項的。如果想要讓某個活動在整個應用程式的上下文中只存在一個例項呢?可以使用singleTask模式來啟動。當活動的啟動模式指定為singleTask,每次啟動該活動時系統首先會在返回棧中檢查是否存在該活動的例項,如果發現已經存在則直接使用該例項,並把在這個活動之上的所有活動統統出棧,如果沒有發現就會建立一個新的活動例項。
    如圖:
    
  
    4.4,singleInstance
    singleInstance模式不同以上三個模式,指定為singleInstance模式的活動會啟用一個新的返回棧來管理這個活動。通常應用以下場景,假設我們的程式中有一個活動是允許其他程式呼叫的,如果我們想實現其他程式和我們的程式可以共享這個活動的例項,那麼該如何實現呢?使用前面三種啟動模式肯定是做不到的,因為每個應用程式都會有自己的返回棧,同一個活動在不同的返回棧中入棧時必然是建立了新的例項。而使用了singleInstance模式就可以解決這個問題,在這種模式下會有一個單獨的返回棧來管理這個活動,不管是哪個應用程式來訪問這個活動,都共用的同一個返回棧,也就解決了共享活動例項的問題。
    如圖:
    
 
   

5,活動(Activity)管理

    一般我們在日常開發中,基本上會有一個專門的集合類對所有活動進行管理,這樣的做的好處是可以保證在退出應用時,活動也可以隨時的釋放。我們需要定義一個專門管理活動的父類,然後讓每個自定義的Activity子類來繼承父類,程式碼如下: 
 1 public class BaseActivity extends Activity{
 2     
 3     private boolean allowFullScreen=true; //是否允許全屏
 4     private boolean allowDestroy=true; //是否允許銷燬    
 5     
 6     @SuppressWarnings("unused")
 7     private View view;
 8     
 9     public boolean isAllowFullScreen() { //獲取是否全屏
10         return allowFullScreen;
11     }
12 
13     public void setAllowFullScreen(boolean allowFullScreen) { //設定是否全屏
14         this.allowFullScreen = allowFullScreen;
15     }
16     
17     public boolean isAllowDestroy() { //獲取是否銷燬
18         return allowDestroy;
19     }
20 
21     public void setAllowDestroy(boolean allowDestroy) { //設定是否銷燬
22         this.allowDestroy = allowDestroy;
23     }
24 
25     public void setAllowDestroy(boolean allowDestroy, View view) { //設定是否銷燬(過載) 
26         this.allowDestroy = allowDestroy;
27         this.view = view;
28     }
29 
30     @Override
31     protected void onCreate(Bundle savedInstanceState) {
32         super.onCreate(savedInstanceState);
33         allowFullScreen = true;
34         AppManager.getAppManager().addActivity(this); //新增Activity到堆疊
35     }
36 
37     @Override
38     protected void onStop() {
39         super.onStop();
40     }
41     
42     @Override
43     protected void onStart() {
44         super.onStart();
45     }
46 
47     @Override
48     protected void onRestart() {
49         super.onRestart();    
50     }
51 
52     @Override
53     protected void onResume() {
54         super.onResume();
55     }
56 
57     @Override
58     protected void onPause() {
59         super.onPause();
60     }
61 
62     @Override
63     protected void onDestroy() {
64         AppManager.getAppManager().finishActivity(this); // 結束Activity&從堆疊中移除
65         super.onDestroy();    
66     }
67     
68     @Override
69     public boolean onKeyDown(int keyCode, KeyEvent event) {    
70         if (keyCode == KeyEvent.KEYCODE_BACK) { //監控返回鍵,並且包含檢視元件
71             if (!allowDestroy) {
72                 return false;
73             }
74         }
75         return super.onKeyDown(keyCode, event);
76     }
77 }

6,總結

    以上就是我對活動(Activity)的理解,理解不到位之處,請園裡的各位小夥伴歡迎指出,共同探討,一起進步。
 

閱讀擴充套件

源於對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中可以看到技術積累的過程。
 

相關文章