- Android Studio開發實戰:從零基礎到App上線 (移動開發叢書)
- 歐陽燊
- 3795字
- 2020-11-28 17:31:41
3.5 Activity基礎
本節介紹Android四大組件之一Activity的基本概念和常見用法。首先說明Activity的生命周期,接著說明Intent的組成部分與工作原理,然后闡述如何使用Intent完成活動頁面之間的消息傳遞,包括如何傳遞請求參數、如何返回應答參數等。
3.5.1 Activity的生命周期
看到這里,相信讀者對Activity已經不陌生了。首先,一個Activity代表一個頁面。其次,Activity的onCreate方法是頁面的入口函數。更細心的讀者也許已經知道調用startActivity方法可以跳轉到下一個頁面。之所以到這時才介紹Activity,是因為Activity的邏輯復雜、概念繁多,必須在有一定基礎后講解才合適,不然一開始就講解高深的專業術語,讀者恐怕很難理解。
首先介紹Activity的生命周期,如同花開花落一般,Activity也有從含苞待放到盛開再到凋零的生命過程。下面是Activity與生命周期有關的方法說明。
● onCreate:創建頁面。把頁面上的各個元素加載到內存中。
● onStart:開始頁面。把頁面顯示在屏幕上。
● onResume:恢復頁面。讓頁面在屏幕上活動起來,例如開啟動畫、開始任務等。
● onPause:暫停頁面。讓頁面在屏幕上的動作停下來。
● onStop:停止頁面。把頁面從屏幕上撤下來。
● onDestroy:銷毀頁面。把頁面從內存中清除掉。
● onRestart:重啟頁面。重新加載內存中的頁面數據。
下面針對幾個常見的業務場景探究一下Activity的生命周期,主要有3個場景:頁面之間的跳轉、豎屏與橫屏的切換、按HOME鍵與返回App。用于場景測試的代碼如下,主要在每個生命周期函數中增加打印屏幕日志和后臺日志。
private void refreshLife(String desc) { Log.d(TAG, desc); mStr = String.format("%s%s %s %s\n", mStr, DateUtil.getNowTimeDetail(), TAG, desc); tv_life.setText(mStr); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_act_***); tv_life = (TextView) findViewById(R.id.tv_life); refreshLife("onCreate"); } @Override protected void onStart() { refreshLife("onStart"); super.onStart(); } @Override protected void onStop() { refreshLife("onStop"); super.onStop(); } @Override protected void onResume() { refreshLife("onResume"); super.onResume(); } @Override protected void onPause() { refreshLife("onPause"); super.onPause(); } @Override protected void onRestart() { refreshLife("onRestart"); super.onRestart(); } @Override protected void onDestroy() { refreshLife("onDestroy"); super.onDestroy(); }
1.頁面之間的跳轉
首先進入測試頁面ActJumpActivity,接著從該頁面跳轉到ActNextActivity,然 后 從ActNextActivity返回ActJumpActivity。界面上的日志截圖如圖3-20所示。其中,區域1表示進入頁面ActJumpActivity時的生命周期過程,區域2表示跳轉到ActNextActivity時的生命周期過程,區域3表示返回ActJumpActivity時的生命周期過程。

圖3-20 活動頁面跳轉時的界面日志截圖
從日志截圖可以看到,下一個頁面的創建伴隨上一個頁面的停止,不過顯示的日志信息不夠完整。下面我們跟蹤一下logcat里的日志,看看這中間到底發生了什么。
首先打開頁面ActJumpActivity,調用方法的順序為:本頁面onCreate→onStart→onResume。日志如下:
11:30:18.352:D/ActJumpActivity(2315):onCreate 11:30:18.352:D/ActJumpActivity(2315):onStart 11:30:18.352:D/ActJumpActivity(2315):onResume
從ActJumpActivity跳轉到ActNextActivity,調用方法的順序為:上一個頁面onPause→下一個頁面onCreate→onStart→onResume→上一個頁面onStop。日志如下:
11:30:32.668:D/ActJumpActivity(2315):onPause 11:30:32.688:D/ActNextActivity(2315):onCreate 11:30:32.688:D/ActNextActivity(2315):onStart 11:30:32.688:D/ActNextActivity(2315):onResume 11:30:33.116:D/ActJumpActivity(2315):onStop
從ActNextActivity回到ActJumpActivity(按返回鍵或在代碼中調用finish方法),調用的方法順序為:下一個頁面onPause→上一個頁面onRestart→onStart→onResume→下一個頁面onStop→onDestroy。日志如下:
11:30:40.740:D/ActNextActivity(2315):onPause 11:30:40.752:D/ActJumpActivity(2315):onRestart 11:30:40.752:D/ActJumpActivity(2315):onStart 11:30:40.752:D/ActJumpActivity(2315):onResume 11:30:41.160:D/ActNextActivity(2315):onStop 11:30:41.164:D/ActNextActivity(2315):onDestroy
至此,基本上可以弄清楚頁面跳轉時的生命周期了。總體上是跳轉前的頁面先調用onPause方法,然后跳轉后的頁面依次調用onCreate/onRestart→onStart→onResume,最后跳轉前的頁面調用onStop方法(若返回上級頁面,則下級頁面還需調用onDestroy方法)。
2.豎屏與橫屏的切換
首先進入測試頁面ActRotateActivity,此時默認為豎屏顯示;接著倒轉手機切換到橫屏,觀察日志;然后倒轉手機切換回豎屏,觀察日志。3個屏幕的顯示日志時間沒有重復,這里的日志截圖是3次截圖拼接而成的,如圖3-21所示。

圖3-21 活動頁面在橫豎屏切換時的界面日志截圖
從日志截圖可以看出,豎屏與橫屏似乎在每次切換時頁面都要重新創建。為進一步驗證實驗結果,再一次查看logcat里的日志,日志信息如下:
21:02:10.179 D/ActRotateActivity:onCreate 21:02:10.179 D/ActRotateActivity:onStart 21:02:10.179 D/ActRotateActivity:onResume 21:02:13.227 D/ActRotateActivity:onPause 21:02:13.227 D/ActRotateActivity:onStop 21:02:13.227 D/ActRotateActivity:onDestroy 21:02:13.247 D/ActRotateActivity:onCreate 21:02:13.247 D/ActRotateActivity:onStart 21:02:13.247 D/ActRotateActivity:onResume 21:02:16.239 D/ActRotateActivity:onPause 21:02:16.239 D/ActRotateActivity:onStop 21:02:16.239 D/ActRotateActivity:onDestroy 21:02:16.279 D/ActRotateActivity:onCreate 21:02:16.279 D/ActRotateActivity:onStart 21:02:16.279 D/ActRotateActivity:onResume
分析日志的時間與內容,無論是豎屏切換到橫屏,還是橫屏切換到豎屏,都是原屏幕的頁面從onPause到onStop再到onDestroy一路銷毀,然后新屏幕的頁面從onCreate到onStart再到onResume一路創建而來。
3.按HOME鍵與返回App
首先進入測試頁面ActHomeActivity;接著按HOME鍵,屏幕回到桌面;然后按任務鍵或長按HOME鍵(不同手機的操作不一樣),屏幕調出進程視圖;最后點擊測試App,屏幕返回測試頁面。一路下來的屏幕日志截圖如圖3-22所示。

圖3-22 按HOME鍵的界面日志截圖
從日志截圖可以看到,此時測試頁面的生命周期是典型的從活動狀態變為暫停狀態(回到桌面時)再到活動狀態(返回App頁面時)。觀察logcat的后臺日志,發現后臺日志與屏幕日志保持一致。
3.5.2 使用Intent傳遞消息
Intent的中文名是意圖,意思是我想讓你干什么,簡單地說,就是傳遞消息。Intent是各個組件之間信息溝通的橋梁,既能在Activity之間溝通,又能在Activity與Service之間溝通,也能在Activity與Broadcast之間溝通。總而言之,Intent用于處理Android各組件之間的通信,完成的工作主要有3部分:
(1)Intent需標明本次通信請求從哪里來、到哪里去、要怎么走。
(2)發起方攜帶本次通信需要的數據內容,接收方對收到的Intent數據進行解包。
(3)如果發起方要求判斷接收方的處理結果,Intent就要負責讓接收方傳回應答的數據內容。
為了做好以上工作,就要給Intent配上必須的裝備,Intent的組成部分見表3-5。
表3-5 Intent組成元素的列表說明

表達Intent的來往路徑有兩種方式,一種是顯式Intent,另一種是隱式Intent。
1.顯式Intent,直接指定來源類與目標類名,屬于精確匹配。
在聲明一個Intent對象時,需要指定兩個參數,第一個參數表示跳轉的來源頁面,第二個參數表示接下來要跳轉到的頁面類。具體的聲明方式有如下3種:
(1)在構造函數中指定,示例代碼如下:
Intent intent = new Intent(this, ActResponseActivity.class);
(2)調用setClass方法指定,示例代碼如下:
Intent intent = new Intent(); intent.setClass(this, ActResponseActivity.class);
(3)調用setComponent方法指定,示例代碼如下:
Intent intent = new Intent(); ComponentName component = new ComponentName(this, ActResponseActivity.class); intent.setComponent(component);
2.隱式Intent,沒有明確指定要跳轉的類名,只給出一個動作讓系統匹配擁有相同字串定義的目標,屬于模糊匹配。
因為我們常常不希望直接暴露源碼的類名,只給出一個事先定義好的名稱,這樣大家約定俗成、按圖索驥就好,所以隱式Intent起到了過濾作用。這個定義好的動作名稱是一個字符串,可以是自己定義的動作,也可以是已有的系統動作。系統動作的取值說明見表3-6。
表3-6 系統動作的取值說明

這個動作名稱通過setAction方法指定,也可以通過構造函數Intent(String action)直接生成Intent對象。當然,由于動作是模糊匹配,因此有時需要更詳細的路徑,比如知道某人住在天通苑小區,并不能直接找到他家,還得說明他住在天通苑的哪一期、哪號樓、哪一層、哪一個單元。Uri和Category便是這樣的路徑與門類信息,Uri數據可通過構造函數Intent(String action, Uri uri)在生成對象時一起指定,也可通過setData方法指定(setData這個名字有歧義,實際就是setUri); Category可通過addCategory方法指定,之所以用add而不用set方法,是因為一個Intent可同時設置多個Category,一起進行過濾。
下面是一個調用系統撥號程序的例子,其中就用到了Uri:
Intent intent = new Intent(); intent.setAction(Intent.ACTION_CALL); Uri uri = Uri.parse("tel:"+"15960238696"); intent.setData(uri); startActivity(intent);
隱式Intent還用到了過濾器的概念,即把不符合匹配條件的過濾掉,剩下符合條件的按照優先順序調用。創建一個Android工程,AndroidManifest.xml里的intent-filter就是XML中的過濾器。比如下面這個最常見的主頁面MainAcitivity,activity節點下面便設置了action和category的過濾條件。其中,android.intent.action.MAIN表示App的入口動作,android.intent.category.LAUNCHER表示在App啟動時調用。
<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>
3.5.3 向下一個Activity傳遞參數
前面說了,Intent的setData方法只指定到達目標的路徑,并非本次通信所攜帶的參數信息,真正的參數信息存放在Extras中。Intent重載了很多種putExtra方法傳遞各種類型的參數,包括String、int、double等基本數據類型,甚至Parcelable、Serializable等序列化結構。不過只是調用putExtra方法顯然不好管理,像送快遞一樣大小包裹隨便扔,不但找起來不方便,丟了也難以知道。所以Android引入了Bundle概念,我們可以把Bundle理解為超市的寄包柜或快遞收件柜,大小包裹由Bundle統一存取,方便又安全。
Bundle內部用于存放數據的實質結構是Map映射,可添加元素、刪除元素,還可判斷元素是否存在。開發者把Bundle全部打包好只需調用一次putExtras方法,把Bundle全部取出來也只需調用一次getExtras方法。
下面是前一個頁面向后一個頁面發送請求數據的代碼:
Intent intent = new Intent(MainActivity.this, FirstActivity.class); Bundle bundle = new Bundle(); bundle.putString("name", "張三"); bundle.putInt("age", 30); bundle.putDouble("height", 170.0f); intent.putExtras(bundle); startActivity(intent);
下面是后一個頁面接收前一個頁面請求數據的代碼:
Intent intent = getIntent(); Bundle bundle = intent.getExtras(); String name = bundle.getString("name", ""); int age = bundle.getInt("age", 0); double height = bundle.getDouble("height", 0.0f);
3.5.4 向上一個Activity返回參數
如同一般的通信一樣,Intent有時只把請求數據發送到下一個頁面就行,有時還要處理下一個頁面的應答數據(通常發生在下一個頁面返回到上一個頁面時)。如果只把請求數據發送到下一個頁面,前一個頁面調用startActivity方法就可以;如果還要處理一下個頁面的應答數據,此時就得分多步處理,詳細步驟如下:
步驟01 前一個頁面打包好請求數據,調用方法startActivityForResult(Intentintent,intrequestCode),表示需要處理結果數據,第二個參數表示請求編號,用于標識每次請求的唯一性。
步驟02 后一個頁面接收請求數據,進行相應處理。
步驟03 后一個頁面在返回前一個頁面時,打包應答數據并調用setResult方法返回信息。setResult的第一個參數表示應答代碼(成功還是失敗),代碼示例如下:
Intent intent = new Intent(); Bundle bundle = new Bundle(); bundle.putString("job", "碼農"); intent.putExtras(bundle); setResult(Activity.RESULT_OK, intent); finish(); //表示關閉當前頁面
步驟04 前一個頁面重寫方法onActivityResult,該方法的輸入參數包含請求編號和應答代碼,請求編號用于判斷對應哪次請求,應答代碼用于判斷后一個頁面是否處理成功。然后對應答數據進行解包處理,代碼示例如下:
@Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { Log.d(TAG, "onActivityResult. requestCode="+requestCode+", resultCode="+resultCode); Bundle resp = intent.getExtras(); String job = resp.getString("job"); Toast.makeText(this, "您目前的職業是"+job, Toast.LENGTH_LONG).show(); }
下面是完整的請求頁面代碼與應答頁面代碼,結合效果界面加深對Activity處理參數傳遞的理解。請求頁面的代碼如下:
public class ActRequestActivity extends AppCompatActivity implements OnClickListener { private EditText et_request; private TextView tv_request; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_act_request); findViewById(R.id.btn_act_request).setOnClickListener(this); et_request = (EditText) findViewById(R.id.et_request); tv_request = (TextView) findViewById(R.id.tv_request); } @Override public void onClick(View v) { if (v.getId() == R.id.btn_act_request) { Intent intent = new Intent(); intent.setClass(this, ActResponseActivity.class); intent.putExtra("request_time", DateUtil.getNowTime()); intent.putExtra("request_content", et_request.getText().toString()); startActivityForResult(intent, 0); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (data ! = null) { String response_time = data.getStringExtra("response_time"); String response_content = data.getStringExtra("response_content"); String desc=String.format("收到返回消息 :\n應答時間為%s\n應答內容為%s", response_time, response_content); tv_request.setText(desc); } } }
應答頁面的代碼如下:
public class ActResponseActivity extends AppCompatActivity implements OnClickListener { private EditText et_response; private TextView tv_response; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_act_response); findViewById(R.id.btn_act_response).setOnClickListener(this); et_response = (EditText) findViewById(R.id.et_response); tv_response = (TextView) findViewById(R.id.tv_response); Bundle bundle = getIntent().getExtras(); String request_time = bundle.getString("request_time"); String request_content = bundle.getString("request_content"); String desc=String.format("收到請求消息 :\n請求時間為%s\n請求內容為%s", request_time, request_content); tv_response.setText(desc); } @Override public void onClick(View v) { if (v.getId() == R.id.btn_act_response) { Intent intent = new Intent(); Bundle bundle = new Bundle(); bundle.putString("response_time", DateUtil.getNowTime()); bundle.putString("response_content", et_response.getText().toString()); intent.putExtras(bundle); setResult(Activity.RESULT_OK, intent); finish(); } } }
具體的效果圖分別如圖3-23、圖3-24、圖3-25所示。其中,圖3-23是當前頁面要向下一個頁面發送請求時的界面,圖3-24是下一個頁面準備返回上一個頁面時的界面,圖3-25是上一個頁面收到下一個頁面應答時的界面。

圖3-23 準備向下一個頁面發送請求

圖3-24 下一個頁面準備返回消息

圖3-25 上一個頁面收到返回消息
- Mastering OpenLayers 3
- DBA攻堅指南:左手Oracle,右手MySQL
- AngularJS Testing Cookbook
- FreeSWITCH 1.8
- Python自然語言處理實戰:核心技術與算法
- Selenium Design Patterns and Best Practices
- TestNG Beginner's Guide
- Blockly創意趣味編程
- Node.js:來一打 C++ 擴展
- Hands-On JavaScript for Python Developers
- MyBatis 3源碼深度解析
- 計算機應用基礎(Windows 7+Office 2010)
- 一覽眾山小:ASP.NET Web開發修行實錄
- 詩意的邊緣
- Roslyn Cookbook