1. 程式人生 > >Android入門——詳解Activity及Activity間的傳值(一)

Android入門——詳解Activity及Activity間的傳值(一)

引言

在前一篇總結了下Android 入門——專案結構目錄、檔案詳解,從這一篇正式開始了總結和學習Android開發之旅,Web應用最基本的UI是網頁、Winform應用最基本的UI就是Form,那麼Activity就是Android最基本的UI,只要有UI都會涉及到Activity。換言之,一個Activity是一個應用程式元件,提供一個螢幕,使用者可以用來互動為了完成某項任務,例如撥號、拍照、傳送email、看地圖。每一個activity被給予一個視窗,在上面可以繪製使用者介面。視窗通常充滿螢幕,但也可以小於螢幕而浮於其它視窗之上。(設定樣式即可,或者程式碼設定大小、位置等等)

一Android幾個重要的術語

1 上下文物件Context

Context類是所有Android應用程式的核心,絕大部分的應用程式和底層互動的功能都通過Context訪問,其設的目的就是為了更加便捷於開發者與底層互動。其實質只是一個抽象類,定義了很多方法,比如獲取系統服務、得到SharedPreferences、啟動Activity等等。

public abstract class Context {  
     ...  
     public abstract Object getSystemService(String name);  //獲得系統級服務  
     public abstract void
startActivity(Intent intent); //通過一個Intent啟動Activity public abstract ComponentName startService(Intent service); //啟動Service //根據檔名得到SharedPreferences物件 public abstract SharedPreferences getSharedPreferences(String name,int mode); ... }

2 活動Activity

Activity繼承Context,Activity是用來與使用者互動的主要UI,一個普通的app由若干個業務(task)構成,可以把每一個提供使用者互動的業務理解為一個Activity,比如登入頁面是一個Activity。一般來說一個Activity的目的在於處理單個螢幕的UI效果展示。Android本身也預定義了很多的Activity,開發的很多工作我們都可以直接呼叫就行了,比如說:撥號介面,設定介面,拍照介面我們能看到的一切介面都是Activity。

3 意圖 Intent

Intent 本身是定義為一個類,一個Intent例項按照一定的格式把自己要表達一個意圖或目的封裝起來。Android則根據此Intent對例項,找出相配的元件,然後將 Intent物件傳遞給所找到的元件,此時他的傳話工作就完成了。說得直白點,Intent 其實就是各元件互動的的跑腿角色,比如你想打電話的時候,你是不是希望那個撥打電話的Activity啟動?那麼你要做的就是把你要做點事(打電話)封裝成對應的Intent 例項,然後Android系統接收到了Intent例項之後自動找到對應的接收者。

二使用Activity

1 Activity 的三個狀態

  1. 當它在螢幕前臺時(位於當前任務堆疊的頂部),它是啟用執行狀態。只要是響應使用者當前操作的Activity就是啟用執行狀態。
  2. 當它失去焦點但仍然對使用者可見時(被部分覆蓋時也算),它處於暫停狀態。即在它之上有另外一個Activity。這個Activity也許是透明的,或者沒有完全覆蓋全屏,所以被暫停的Activity仍對使用者可見。暫停的Activity仍然是存活狀態(它保留著所有的狀態和成員資訊並保持和視窗管理器的連線),但系統處於極低記憶體時仍然可以殺死這個Activity。
  3. 完全被另一個Activity覆蓋時則處於停止狀態。它仍然保留所有的狀態和成員資訊。然而對使用者是不可見的,所以它的視窗將被隱藏,如果其它地方需要記憶體,則系統經常會殺死這個Activity。

2 Activity的生命週期

當Activity從一種狀態轉變到另一種狀態時,系統會自動呼叫對應的方法來通知這種變化
這裡寫圖片描述

  1. onCreate:當Activity被建立(Activity物件從無到有,但如果只是被暫時隱藏,並沒有被銷燬時,重新變成啟用狀態的不會觸發)時,就會執行,且在一個生命週期內只執行一次。所以在這裡完成一些初始化工作,比如說呼叫setContentView(id)設定在資原始檔的id; 使用findViewById(id) 獲得所有的元件;繫結註冊對應的監聽器、資料繫結等等。
  2. onStart:Activity處於可見狀態的時候,即Activity處於棧頂
  3. onRestart:當Activity由完全被覆蓋但沒有被銷燬時,重新回到前臺
  4. onResume:當Activity**可以得到使用者焦點時,此時Activity獲得輸入焦點。這個方法內部仍然比較適合獲取執行所需資源,尤其適用於啟動音訊、視訊和動畫**。
  5. onPause:當Activity 部分被遮蓋時,此時的Activity不能獲得輸入焦點,但是部分可見。一般來說,在onResume方法中獲取的資源,比如手動管理的Cursor物件,均應該在onPause裡釋放,否則如果執行緒被終止,就有可能造成有些資源是放不完全。
  6. onStop :當activity被完全摭蓋時被呼叫
  7. onDestory:在activity銷燬之前被呼叫。這是activity能收到的最後一個呼叫。呼叫的原因可能是別人在這個activity上呼叫了finish(),也可能是系統為了更多的記憶體空間而把它所在的程序處死了。在這個方法中,可以呼叫isFinishing()來判斷自己屬於哪一種死法。

Activity生命週期小結

  1. Activity的完整生命週期自第一次呼叫onCreate()開始,直至呼叫onDestroy()為止。Activity在onCreate()中設定所有“全域性”狀態以完成初始化,而在onDestroy()中釋放所有系統資源。例如,如果Activity有一個執行緒在後臺執行從網路上下載資料,它會在onCreate()建立執行緒,而在onDestroy()銷燬執行緒。
  2. Activity的可視生命週期自onStart()呼叫開始直到相應的onStop()呼叫結束。在此期間,使用者可以在螢幕上看到Activity,儘管它也許並不是位於前臺或者也不與使用者進行互動。在這兩個方法之間,我們可以保留用來向用戶顯示這個Activity所需的資源。例如,當用戶不再看見我們顯示的內容時,我們可以在onStart()中註冊一個BroadcastReceiver來監控會影響UI的變化,而在onStop()中來注消。onStart() 和 onStop() 方法可以隨著應用程式是否為使用者可見而被多次呼叫。
  3. Activity的前臺生命週期自onResume()呼叫起,至相應的onPause()呼叫為止。在此期間,Activity位於前臺最上面並與使用者進行互動。Activity會經常在暫停和恢復之間進行狀態轉換——例如當裝置轉入休眠狀態或者有新的Activity啟動時,將呼叫onPause() 方法。當Activity獲得結果或者接收到新的Intent時會呼叫onResume() 方法。

如果按照Activity的活動狀態區分,還可以把Activity的生命週期歸納為:啟動ActivityActivity退居後臺Activity返回前臺鎖屏、解鎖時的生命週期方法變化

  • 啟動Activity: onCreate()—>onStart()—>onResume(),Activity進入執行狀態。

  • Activity退居後臺: 當前Activity轉到新的Activity介面或按Home鍵回到主屏:onPause()—>onStop(),進入停滯狀態。

  • Activity返回前臺:onRestart()—>onStart()—>onResume(),再次回到執行狀態。

  • Activity退居後臺,且系統記憶體不足,系統會殺死這個後臺狀態的Activity(此時這個Activity引用仍然處在任務棧中,只是這個時候引用指向的物件已經為null),若再次回到這個Activity,則會走onCreate()–>onStart()—>onResume()(將重新走一次Activity的初始化生命週期)

  • 鎖屏:onPause()->onStop()

  • 解鎖:onStart()->onResume()

3 建立Activity

  1. 編寫Java類繼承自Activity或者Activity的子類(ListActivity、FragmentActivity、ActionBarActivity等等),並重寫Activity的相關生命週期方法。
  2. 在manifest清單檔案中,宣告Activity(必須宣告,因為Activity也是Android的一個元件,只有聲明瞭Android才能找得到,內容提供者、服務、廣播也是元件也同樣需要先宣告再使用)

4 Activity的跳轉startActivity(intent)和startActivityForResult(intent)

Activity的跳轉又分為顯式跳轉和隱式跳轉,呼叫的方法大同小異,區別只是在於傳遞的intent物件不同,其實實質是Intent物件的兩種匹配方式。

//顯式跳轉,適合用於啟動同一APP中的Activity
  Intent intent = new Intent(LoginActivity.this, MainActivity.class);
  startActivity(intent);//無返回值的跳轉

//隱式跳轉
  Intent intent = new Intent();
  intent.setClass(LoginActivity.this, MainActivity.class);
  startActivity(intent);  
  /*startActivityForResult(intent,requestCode); 其中requestCode為請求碼,請求碼的值隨便設定,但必須>=0 ,作用在於加入你有多個操作都可以跳轉到MainActivity,你可以在LoginActivity的回撥方法中onActivityResult(int requestCode, int resultCode, Intent data)得到你的返回值,區別處理,(從MainActivity回來的時候會執行這個方法)
  */

5 Activity的4種啟動模式

通過Acitivty的xml標籤來改變任務棧的預設行為,使用android:launchMode=”standard|singleInstance|singleTask|singleTop”來控制Acivity任務棧。(任務棧是一種後進先出的結構。位於棧頂的Activity處於焦點狀態,當按下back按鈕的時候,棧內的Activity會一個一個的出棧,並且呼叫其onDestory()方法。如果棧內沒有Activity,那麼系統就會回收這個棧,每個APP預設只有一個棧,以APP的包名來命名.)

  1. standard(預設): 標準模式,每次啟動Activity都會建立一個新的Activity例項,並且將其壓入任務棧棧頂,而不管這個Activity是否已經存在。Activity的啟動三回撥(onCreate()->onStart()->onResume())都會執行。
  2. singleTop: 棧頂複用模式,如果新Activity已經位於任務棧的棧頂,那麼此Activity不會被重新建立,所以它的啟動三回撥就不會執行,同時Activity的onNewIntent()方法會被回撥來重用,否則建立新的例項並放入棧頂(即使棧中已經存在該例項,只要不在棧頂都會建立新的例項)
  3. singleTask: 棧內複用模式,建立這樣的Activity的時候,系統會先確認它所需任務棧已經建立,否則先建立任務棧.然後放入Activity,如果棧中已經有一個Activity例項,那麼這個Activity就會被調到棧頂,onNewIntent(),並且singleTask會清理在當前Activity上面的所有Activity。如果任務棧中正好存在Activity該例項重用(會呼叫例項的onNewIntent())重用時會讓該例項重回棧頂,因此在他上面的例項就會被移出棧,如果棧中不存在該例項,則建立新的例項並放入棧中
  4. singleInstance:加強版的singleTask模式,這種模式的Activity只能單獨位於一個任務棧內,由於棧內複用的特性,後續請求均不會建立新的Activity,除非這個獨特的任務棧被系統銷燬了,並讓多個應用共享該Activity的例項,一旦該模式的Activity例項已存在於某個棧中,任何應用再啟用該Activity時都會重用該棧中的例項,其效果相當於多個應用共享一個應用,不管誰啟用該Activity都會進入同一個應用中。在這個模式下的Activity例項所處的task中,只能有這個activity例項,不能有其他的例項。

Activity的堆疊管理以ActivityRecord為單位,所有的ActivityRecord都放在一個List裡面.可以認為一個ActivityRecord就是一個Activity棧

6 Activity之間的傳值

1 通過Intent物件傳值(最常用)

Intent物件傳值是Activity之間的傳統方式(同樣適用於Service、Broadcast Receiver),主要是通過Intent.putExtra()設定要傳遞的值,再通過getXXXExtra()方法獲取傳遞過來的資料。但是隻能傳遞Java簡單型別的資料(包括簡單型別的陣列)和實現了java.io.Serializable的類的物件例項(類成員也必須是序列化的即實現了java.io.Serializable介面)

//傳值:直接儲存到Intent中
Intent toMain=new Intent(LoginActivity.this, ManageUserActivity.class);
toMain.putExtra("LoginUser", "userEdt");
toMain.putExtra("Phone", "phone");
startActivity(toMain);

// 接收
Intent intent = getIntent();  
String loginUser=intent.getStringExtra("LoginUser").toString();
String phone=intent.getStringExtra("Phone").toString();

/***********************傳值:先儲存到Bundle***************************/
intent = new Intent(); 
Bundle bundle = new Bundle();  
bundle.putString("USERNAME", et_username.getText().toString());  
intent.putExtras(bundle);
//接收
Bundle bundle = this.getIntent().getExtras();  
String str=bundle.getString("USERNAME");

2 通過靜態變數物件傳值

將類成員定義成public static ,就直接可以通過類成員傳遞資料,好處是在於可以傳遞任意型別的資料。

3 通過Android剪下板存取資料

只能直接傳遞文字資料和Intent物件支援的資料,不過還可以傳遞可序列化的資料、影象等複雜資料的,思路是用字串傳遞二進位制資料,將二進位制資料轉成字串只需要對其進行編碼(一般採用Base64),再傳遞,接收之後再解碼還原。

ClipboardManager clipboard=(ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);//得到剪下板物件
clipboard.setText(et_username.getText().toString());

//從剪下板中獲取資料
ClipboardManager clipboard=(ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);//得到剪下板物件
clipboard.getText();
/*傳遞複雜資料,可序列化的資料、影象等,Data為一個可序列化的類,有資料成員id,name……,clipData為已經初始化了Data的可序列化例項**/
ClipboardManager clipboard=(ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);//得到剪下板物件
Data clipData=new Data();
...
//開始對可序列化例項進行Base64編碼
ByteArrayOutputStream bouts=new ByteArrayOutputStream();
String strBase64="";
try{
    ObjectOutputStream oos=new ObjectOutputStream (bouts);
    oos.writeObject(clipData);
    //使用base64的將byte[]資料轉成base64 字串
    strBase64=Base64.encodeToString(bouts.toByteArray(),Base64.DEFAULT);
    oos.close();
}catch(){}
//向剪下板中寫入Base64格式的字串
clipboard.setText(strBase64);
……
/********從剪下板中獲取Base64格式的字串,並解碼還原成Data物件********/
//獲得剪下板Base64字串
String reBase64Str=clipboard.getText().toString();
//將Base64格式的字串還原成byte格式的資料
byte[] buff=Base64.decode(reBase64Str,Base64.DEFAULT);
ByteArrayInputStream bins=new ByteArrayInputStream (buff);
try{
    ObjectInputStream ois=new ObjectInputStream (bins);
    //將byte[]還原成data
    Data data=(data)ois.readObject();
    //輸出原始的Base64格式的字串
    System.out.println("Data.id"+data.getId()+"Data.name"+data.getName());
    ...
}catch(){}

4 通過全域性物件

可以為每一個應用程式定義一個全域性物件,該物件由系統建立,但是使用全域性物件的類必須繼承android.app.Application,並在該類中定義成員變數和方法

//1定義繼承Application全域性物件的類,並不會自動建立全域性物件的,因為Android系統並不知道哪個類是全域性類
public class DataTemp extends Application{
    public String userName();
    public Data d=new Data();//可不序列化
}
//2在manifest中通過application裡的android:name屬性定義該全域性類
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.weima.amis"
    android:versionCode="1"
    android:versionName="1.0" >
        <application
        android:name=".DataTemp"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
       android:theme="@android:style/Theme.Black.NoTitleBar" >
       ...
//3指定全域性類後,在程式執行時,全域性物件會被自動建立,而且一直駐留在記憶體中知道app徹底退出記憶體可以在應用程式中任何地方獲取DataTemp 的物件
DataTemp  dt=(dt)getApplicationContext();

Activity傳值小結

根據官方建議,傳遞簡單型別或可序列化的物件,宜採用Intent物件進行資料傳遞,如果是傳遞不序列化的物件則可以採用靜態變數或全域性的物件方式,最後採用全域性物件。

7 Activity的關閉

對於單一Activity的應用來說,退出很簡單,直接finish()即可。當然,也可以用killProcess()和System.exit()這樣的方法,不過這些方法都無法完美的關閉應用程式,現提供幾個方法,供參考:
1. 拋異常強制退出:該方法通過拋異常,使程式Force Close。驗證可以,但是,需要解決的問題是,如何使程式結束掉,而不彈出Force Close的視窗。
2. 記錄開啟的Activity:每開啟一個Activity,就記錄下來。在需要退出時,關閉每一個Activity即可。
3. 傳送特定廣播:在需要結束應用時,傳送一個特定的廣播,每個Activity收到廣播後,關閉即可。
4. 遞迴退出在開啟新的Activity時使用startActivityForResult,然後自己加標誌,在onActivityResult中處理,遞迴關閉。除了第一個,都是想辦法把每一個Activity都結束掉,間接達到目的。但是這樣做同樣不完美。你會發現,如果自己的應用程式對每一個Activity都設定了nosensor,在兩個Activity結束的間隙,sensor可能有效了。但至少,我們的目的達到了,而且沒有影響使用者使用。為了程式設計方便,最好定義一個Activity基類,把Activity放到棧中去處理這些共通問題。