官术网_书友最值得收藏!

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 上一個頁面收到返回消息

主站蜘蛛池模板: 安徽省| 长乐市| 民权县| 屯门区| 抚顺市| 合肥市| 丰宁| 江达县| 宾川县| 菏泽市| 衡水市| 黔南| 施秉县| 永靖县| 修武县| 丹东市| 阳新县| 浙江省| 吉木萨尔县| 浙江省| 商都县| 大英县| 科尔| 兴山县| 台中县| 绍兴县| 玉门市| 灯塔市| 保靖县| 伊宁市| 嵩明县| 深泽县| 南丹县| 九龙城区| 汨罗市| 巧家县| 沙河市| 长治市| 海门市| 常德市| 南通市|