第一行代碼學習筆記第二章——探究活動

知識點目錄

知識點回顧

2.1 活動是什麼

是一種包含用戶界面的組件,主要用於和用戶進行交互。

2.2 活動的基本用法

Android Studio一個工作區間只允許打開一個項目,故點擊導航欄File –> Close Project ,新建項目ActivityTest,選擇Add No Activity 手動創建活動。

這裏寫圖片描述

2.2.1 手動創建活動

將項目結構手動切換成Project模式:

這裏寫圖片描述

右擊com.example.activitytest –> New –> Activity –> Empty Activity ,創建FirstActivity。

這裏寫圖片描述

任何活動都應該重寫Activity的onCreate()方法。

2.2.2 創建和加載佈局

Android程序設計講究邏輯和視圖分離,最好每一個活動都能對應一個佈局,佈局就是用來顯示界面的內容。

創建佈局:

右擊app/src/main/res目錄——>New——>Directory,彈出新建目錄的窗口,創建一個名爲layout的目錄,然後layout目錄右鍵——>New——>Layout resource file,彈出新建佈局文件的窗口,將佈局文件命名爲first_layout,根元素默認選擇爲LinearLayout。

這裏寫圖片描述

加載佈局:

項目中添加的資源文件都會在R文件中生成一個相應的資源id。通過setContentView()方法來加載當前活動的佈局,而在setContentView()方法中我們傳入的是佈局文件的id。

setContentView(R.layout.first_layout);
2.2.3 在AndroidManifest文件中註冊

所有的活動都要在AndroidManifest.xml中進行註冊才能生效,但Android Studio會自動幫我們註冊。打開app/src/main/AndroidManifest.xml文件。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".FirstActivity"
            android:label="This is FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

其中:

package指定了程序的包名;

name指定註冊的是哪個活動;

label指定活動中標題欄的內容,還會成爲啓動器(Launcher)中應用程序顯示的名稱;

intent-filter標籤指定當前活動爲程序的主活動。

注:如果整個程序沒有指定任何一個活動作爲主活動,這個程序仍然可以正常安裝,只是無法在啓動器中看到或者打開這個程序。這種程序一般都是作爲第三方服務提供其他應用在內部進行調用的,如支付寶快捷支付服務。

2.2.4 在活動中使用Toast

Toast是Android系統提供的一種信息提醒方式。信息在一段時間後會自動消失,並且不會佔用任何屏幕空間。

在first.xml中定義一個Button,作爲Toast觸發點。在first.xml方法中添加如下代碼:

<Button
    android:id="@+id/button_1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Button 1"/>

在onCreate()方法中添加如下代碼:

 Button button1 = (Button) findViewById(R.id.button_1);
 button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    Toast.makeText(FirstActivity.this,"You Click Button 1",Toast.LENGTH_SHORT).show();
    }
 });

通過findViewById()方法獲取到在佈局文件中定義的元素,傳入R.id.button_1獲取到Button實例。給button1設置一個監聽器,然後在onClick()方法中執行監聽事件。Toast輸入的快捷鍵是Toast+Tab鍵。makeText()方法中的三個參數:

第一個參數:上下文Context。

第二個參數:Toast顯示的文本內容。

第三個參數:Toast顯示的時長,有兩個內置常量,分別是Toast.LENGTH_SHORT和Toast.LENGTH_LONG。

2.2.5 在活動中使用Menu

在res目錄下新建一個menu文件夾,右擊res目錄——>New——>Directory,輸入文件夾名menu,點擊OK。再在menu文件夾下新建一個名爲main的菜單文件,右擊menu文件夾——>New——>Menu resource file。然後在main.xml中添加如下代碼:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/add_item" android:title="Add"/>
    <item android:id="@+id/remove_item" android:title="Remove"/>
</menu>

其中:

item標籤是用來創建具體的某一個菜單項;

android:id 給菜單項指定一個唯一的標識符;

android:title 給菜單項指定一個名稱。

返回FirstActivity中重寫onCreateOptionMenu()方法,給當前活動創建菜單。

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main,menu);
    return true;
}

通過getMenuInflater()得到MenuInflater對象,再調用它的inflate()方法就可以給當前活動創建菜單了。

其中:

參數一:指定通過哪個資源文件來創建菜單。

參數二:指定菜單項添加到哪一個Menu對象中。

返回true,表示允許創建的菜單顯示出來;返回false,創建的菜單無法顯示。

重寫onOptionsItemSelected()方法,定義菜單響應事件。添加如下代碼:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.add_item:
            Toast.makeText(this, "You Click Add", Toast.LENGTH_SHORT).show();
            break;
        case R.id.remove_item:
            Toast.makeText(this, "You Click Remove", Toast.LENGTH_SHORT).show();
            break;
        default:
    }
    return true;
}

通過item.getItemId()方法來判斷點擊的是哪一個菜單項,然後在對應的菜單項中添加自己相應的邏輯。

標題欄右側一個三點的符號,就是菜單按鈕,菜單默認是不會顯示出來,只有點擊一下菜單按鈕纔會彈出具體內容,因此它不會佔用任何活動的空間。

運行效果如下:

這裏寫圖片描述

2.2.6 銷燬一個活動
  • 按Back鍵

  • 調用finish()方法。

2.3 使用Intent在活動之間穿梭

Intent是Android程序中各組件之間進行交互的一種重要方式,它不僅可以指明前組件想要執行的動作,還可以在不同組件之間傳遞數據。Intent一般可用於啓動活動、啓動服務以及發送廣播等場景。

2.3.1 使用顯式Intent

右擊com.example.activitytest包——>New——>Activity——>Empty Activity,新建一個SecondActivity,勾選Generate Layout File,佈局文件命名爲second_layout,不勾選Launcher Activity選項。

編輯second_layout.xml文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 2"/>
</LinearLayout>

修改FirstActivity()方法中按鈕的點擊事件,代碼如下:

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        startActivity(intent);
    }
});

其中,Intent中的參數:

參數一:啓動活動的上下文;

參數二:指定想要啓動的目標活動。

按Back鍵可以銷燬當前活動,返回上一個活動。

2.3.2 使用隱式Intent

隱式Intent不明確指出要啓動的活動,而是指定一系列更爲抽象的action和category等信息,然後讓系統去分析這個Intent,從而找出合適的活動去啓動。

通過在activity標籤下配置intent-filter的內容,可以指定當前活動能響應的action和category,打開AndroidManifest.xml,添加如下代碼:

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

只有action和category中的內容同時匹配Intent中指定的action和category時,這個活動才能響應該Intent。

修改FirstActivity中按鈕的點擊事件:

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent("com.example.activitytest.ACTION_START");
        startActivity(intent);
    }
});

這樣通過隱式的Intent也可以啓動SecondActivity。

每個Intent中只能指定一個action,卻可以指定多個category。

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
       Intent intent = new Intent("com.example.activitytest.ACTION_START");
       intent.addCategory("com.example.activitytest.MY_CATEGORY");
       startActivity(intent);
    }
});

打開AndroidManifest.xml,在SecondActivity的intent-filter中添加一個category聲明

<category android:name="com.example.activitytest.MY_CATEGORY" />
2.3.3 更多隱式Intent的用法

使用隱式Intent,不僅可以啓動自己程序內的活動,還可以啓動其他程序的活動。例如可以調用系統的瀏覽器來打開一個網頁。

修改FirstActivity中按鈕點擊事件的代碼,如下:

 button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("http://www.baidu.com"));
            startActivity(intent);
        }
    });

其中:

Intent的action是Intent.ACTION_VIEW,是一個Android內置的動作;

Uri.parse()方法將網址解析成一個Uri對象;

setData()方法將解析好的Uri對象傳遞進去。

因爲intent有setData()方法,所以我們也可以在intent-filter標籤中再配置一個data標籤,用於更精確指定當前活動響應的是什麼類型的數據。data標籤中可以配置以下內容:

  • android:scheme。用於指定數據的協議部分,例如上面的http

  • android:host。用於指定數據的主機名部分,例如上面的www.baidu.com

  • anddroid:port。用於指定數據的端口部分。一般緊隨主機名之後

  • android:path。用於指定主機名和端口之後的部分。如一段網址中跟在域名之後的內容

  • android:mimeType。用於指定可以處理的數據類型,允許使用通配符的方式進行指定

只有data標籤中指定的內容和Intent中攜帶的Data完全一致時,當前的活動才能夠響應該Intent。

除了http協議外,還可以指定其他協議。比如geo表示顯示地理位置、tel表示撥打電話。調用系統撥號界面示例代碼如下:

button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_DIAL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }
    });
2.3.4 向下一個活動傳遞數據

Intent不僅可以啓動一個活動,還可以在啓動活動的時候傳遞數據,傳遞數據的思路如下:

  • 在啓動頁面中通過Intent提供的一系列putExtra()方法,將傳遞的數據放在Intent中

  • 在被啓動的頁面中通過getIntent獲取Intent對象,再調用getXXXExtra()方法獲取值。

比如在FirstActivity中傳一個字符串到SecondActivity中,在FirstActivity中的代碼如下:

 button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String data = "Hello SecondActivity";
            Intent intent = new Intent(FirstActivity.this, SecondActivity.class );
            intent.putExtra("extra_data", data);
            startActivity(intent);
        }
    });

這裏採用的是顯式Intent的方式來啓動SecondActivity,並通過putExtra()方法傳遞一個字符串數據。putExtra()中的兩個參數:

  • 參數一:鍵。用於在後面從Intent中獲取值。

  • 參數二:要傳遞的數據值。

從SecondActivity中取值的代碼如下:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Intent intent = getIntent();
    String data = intent.getStringExtra("extra_data");
}

首先通過getIntent()方法獲取到啓動SecondActivity的Intent,然後調用getStringExtra()方法,傳入相應的鍵值,從而可以得到傳遞的數據。因爲這裏傳入的是字符串數據,那麼就使用getStringExtra()方法來獲取對應的數據;如果傳遞的是整型數據,則使用getIntExtra()方法;如果傳遞的是布爾型數據,則使用getBooleanExtra()方法,以此類推。

2.3.5 返回數據給上一個活動

如果在啓動另外一個活動後,想得到被啓動的活動的數據反饋,那麼我們在啓動活動的時候就不能再用startActivity()方法了,而應該使用startActivityForResult()方法,該方法接收兩個參數:
* 參數一:intent

  • 參數二:請求碼。用於在之後的回調中判斷數據的來源。

修改FirstActivity中按鈕的點擊事件:

button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(FirstActivity.this, SecondActivity.class );
            startActivityForResult(intent,1);
        }
    });

請求碼要確定是一個唯一值,這裏傳入1。接着在SecondActivity中給按鈕註冊點擊事件,並在點擊事件中添加返回數據的邏輯,代碼如下:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Button button2 = (Button) findViewById(R.id.button_2);
    button2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            intent.putExtra("data_return", "Hello FirstActivity");
            setResult(RESULT_OK, intent);
            finish();
        }
    });
}

這裏我們構建了一個Intent,但這裏的Intent僅僅只是用於傳遞數據,不啓動其他任何Activity。然後將要反饋的數據放在intent中,然後再調用setResult()方法,該方法是專門用於向上一個活動返回數據的。setResult()方法有兩個參數:

  • 參數一:用於向上一個活動返回處理結果。一般只使用RESULT_OK或RESULT_CANCELED。

  • 參數二:帶有數據的Intent。

最後調用finish()方法來銷燬當前活動。

因爲前面我們使用的是startActivityForResult()方法來啓動的SecondActivity,那麼在SecondActivity被銷燬後會回調上一個活動的onActivityResult()方法。因爲我們需要在FirstActivity中重寫這個方法來得到返回的數據,代碼如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case 1:
            if (resultCode == RESULT_OK) {
                String returnData = data.getStringExtra("data_return");
                Log.d(TAG, returnData);
            }
            break;
        default:
    }
}

其中,onActivityResult()方法中帶有三個參數:

  • 參數一:requestCode,請求碼。就是在活動時傳入的請求碼。

  • 參數二:resultCode,結果碼。就是在返回數據時傳入的處理結果。

  • 參數三:data,返回數據的Intent。

由於有可能在一個活動中調用startActivityForResult()方法去啓動很多不同的活動,每一個活動返回的數據都會回調onActivityResult()方法,因此我們需要通過requestCode的值來判斷數據的來源,再通過resultCode的值來判斷處理結果是否成功,最後再從data中取值,這樣纔算完成了向上一個活動返回數據的工作。

上面我們是通過點擊SecondActivity中的按鈕來返回上一個活動,如果我們是直接按Back鍵返回到FirstActivity,那麼數據就沒法返回了。爲了處理這種情況,我們可以通過重寫SecondActivity中的onBackPressed()方法來解決,代碼如下:

@Override
public void onBackPressed() {
    Intent intent = new Intent();
    intent.putExtra("data_return", "Hello FirstActivity2");
    setResult(RESULT_OK, intent);
    finish();
}

這樣當用戶按下Back鍵,就會去執行onBackPressed()方法中的代碼,同樣可以將數據返回到FirstActivity中去。

2.4 活動的生命週期

掌握Activity的生命週期對Android開發者來說是至關重要的。

2.4.1 返回棧

Android中的活動是可以層疊的,每啓動一個新的活動,就會覆蓋在原活動之上,當點擊Back鍵或執行finish操作時,就會銷燬最上面的活動,下面的一個活動就會重新顯示出來。而Android是使用任務(Task)來管理活動的,一個任務就是一組存放在棧裏的活動的集合,這個棧又被稱作返回棧(Back Stack)。棧是一種後進先出的數據結構,在默認情況下,每當我們啓動了一個新的活動,它都會進入返回棧,然後處於棧頂的位置。

2.4.2 活動狀態

每個活動在其生命週期中最多可能會有4中狀態。

1. 運行狀態

當Activity處於返回棧的棧頂時,這時就處於運行狀態。系統最不願意回收處於運行狀態的活動。

2. 暫停狀態

當Activity不再處於棧頂位置,但仍然可見時,這時活動就會進入暫停狀態。只有在內存極低的情況下,系統纔會去考慮回收這種活動。

3. 停止狀態

當Activity不再處於棧頂位置,且不可見時,這時活動就會進入停止狀態。系統仍然會爲這種活動保存相應的狀態和成員變量,但當其他地方需要內存時,處於停止狀態的活動有可能被系統回收。

4. 銷燬狀態

當Activity從返回棧中移除後,就是處於銷燬狀態。系統非常喜歡回收處於這種狀態的活動,從而來保證機器的內存充足。

2.4.3 活動的生存期

Activity中定義了7個回調方法來闡述活動生命週期的每一個環節:

  • oncreate(),該方法在活動第一次被創建的時候調用,主要完成一些初始化操作。例如加載佈局、綁定事件等。

  • onStart() ,該方法在活動由不可見到可見的時候調用。

  • onResume() ,該方法是活動準備好和用戶進行交互的時候調用。此時活動一定處於棧頂,且是運行狀態。

  • onPause(),該方法在系統準備去啓動或恢復另一個活動的時候調用。在這個方法中我們一般會快速的釋放掉一些耗CPU的資源、保存一些關鍵的數據。

  • onStop(),該方法在活動完全不可見時調用。與onPause()的主要區別是,如果新啓動的Activity是一個對話框式的活動,那麼onPause()會執行,onStop()不會執行。

  • onDestory(),該方法在活動被銷燬之前調用,之後的活動狀態變爲銷燬狀態。

  • onRestart(),該方法在活動由停止狀態變爲運行狀態之前調用,也就是活動被重新啓動。

上面7種方法除了onRestart()方法,其他的都是兩兩相對,從而又可以將活動分爲3中生存期:

1. 完整生存期

onCreate() ——> onDestory()

onCreate()到onDestory()就是完整生存期。

2. 可見生存期

onstart() ——> onStop()

onstart()到onStop()就是可見生存期

3. 前臺生存期

onResume() ——> onPause()

onResume()到onPause()就是前臺生存期

爲了更好的理解Activity的生命週期,Android官方提供了一張示意圖:

這裏寫圖片描述

2.4.4 體驗活動的生命週期

創建ActivityLifeCycleTest項目,新建NormalActivity和DialogActivity,佈局名分別爲actvity_normal.xml和activity_dialog.xml

編輯actvity_normal.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a normal Activity"/>

</LinearLayout>

編輯activity_dialog.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a dialog Activity"/>

</LinearLayout>

到清單文件中設置DialogActivity的主題:

<activity android:name=".DialogActivity" android:theme="@style/Theme.AppCompat.Dialog"/>

編輯activity_main.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
    android:id="@+id/start_normal_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start Normal Activity" />

<Button
    android:id="@+id/start_dialog_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start Dialog Activity" />

</LinearLayout>

在佈局中加入兩個按鈕,分別用於啓動NormalActivity和DialogActivity。

修改MainActivity中的代碼:

private static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate: ");
    setContentView(R.layout.activity_main);

    Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);
    Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);

    startNormalActivity.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this, NormalActivity.class);
            startActivity(intent);
        }
    });

    startDialogActivity.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this, DialogActivity.class);
            startActivity(intent);
        }
    });
}

@Override
protected void onStart() {
    super.onStart();
    Log.d(TAG, "onStart: ");
}

@Override
protected void onResume() {
    super.onResume();
    Log.d(TAG, "onResume: ");
}

@Override
protected void onPause() {
    super.onPause();
    Log.d(TAG, "onPause: ");
}

@Override
protected void onStop() {
    super.onStop();
    Log.d(TAG, "onStop: ");
}

@Override
protected void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: ");
}

@Override
protected void onRestart() {
    super.onRestart();
    Log.d(TAG, "onRestart: ");
}

在onCreate()方法中給兩個按鈕分別添加點擊事件,點擊第一個按鈕來啓動NormalActivity,點擊第二個按鈕來啓動DialogActivity(),然後分別在7個回調方法中打印log信息,這樣就可以通過觀察log的方式來直觀地理解活動的生命週期。

apk安裝完成後,MainActivity啓動時,log信息顯示:

D/MainActivity: onCreate: 
D/MainActivity: onStart: 
D/MainActivity: onResume:

點擊第一個按鈕,啓動NormalActivity,log信息顯示如下:

com.example.activitylifecycletest D/MainActivity: onPause: 
com.example.activitylifecycletest D/MainActivity: onStop: 

因爲MainActivity已經被NoramlActivity完全遮蓋住了,所以會走onPause()和onStop()

然後按下Back鍵,回到MainActivity,log信息顯示如下:

com.example.activitylifecycletest D/MainActivity: onRestart: 
com.example.activitylifecycletest D/MainActivity: onStart: 
com.example.activitylifecycletest D/MainActivity: onResume: 

然後再點擊第二個按鈕,啓動DialogActivity,log信息顯示如下:

com.example.activitylifecycletest D/MainActivity: onPause: 

從log中可以看出只執行了onPause()方法,onStop()並沒有執行,這是因爲DialogActivity並沒有將MainActivity完全遮蓋住。此時MainActivity並沒有進入停止狀態,只是進入了暫停狀態。

按下Back鍵,log信息如下:

com.example.activitylifecycletest D/MainActivity: onResume:

此時也只有onResume()方法執行。

最後在MainActivity界面按下Back鍵退出程序,log信息顯示如下:

com.example.activitylifecycletest D/MainActivity: onPause: 
com.example.activitylifecycletest D/MainActivity: onStop: 
com.example.activitylifecycletest D/MainActivity: onDestroy: 

依次執行了onPause()、onStop()、onDestory(),最終銷燬MainActivity

2.4.5 活動被回收了怎麼辦

如果活動處於停止狀態時,就有可能因爲內存不足被系統回收掉,這是我們就需要在回收之前保存活動的一些臨時數據。Activity中提供了一個onSaveInstanceState()回調方法,該方法可以保證在活動回收之前被調用。因此我們可以在該方法中保存一些重要的臨時數據。

在MainActivity中添加如下代碼:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Something you just typed";
    outState.putString("data_key",tempData);
}

可以看到Bundle保存數據也是通過鍵值對的形式保存。

數據保存下來了,那該從哪裏去取呢?我們發現onCreate()方法中有一個Bundle類型的參數,這個參數保存了所有保存的數據。我們可以通過相應的鍵來取出對應的值。

在MainActivity的onCreate()方法中取出值:

//獲取Bundle中保存的數據
    if (savedInstanceState != null) {
        String tempData = savedInstanceState.getString("data_key");
        Log.d(TAG, tempData);
    }

2.5 活動的啓動模式

啓動模式共有4種,分別爲standard、singleTop、singleTask和singleInstance。可以在AndroidManifest.xml中通過給標籤指定android:launchMode屬性來選擇啓動模式。

2.5.1 standard

standard又稱作“標準式”,在不顯式指定的情況下,所有的活動都是使用這種啓動模式。使用standard模式的活動,系統不會在乎這個活動是否已經在返回棧中存在,每次啓動活動都會創建該活動的一個新實例。

打開ActivityTest項目,修改FirstActivity中的onCreate()方法:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "Task id is " + getTaskId());
    setContentView(R.layout.first_layout);
    Button button1 = (Button) findViewById(R.id.button_1);
    button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
            startActivity(intent);
        }
    });
}

在FirstActivity的基礎上啓動FirstActivity,並添加一條打印信息,用於打印當前活動的實例。連續點擊兩次按鈕,可以看到logcat中打印信息如下所示:

com.example.activitytest.FirstActivity@6eb0acd
com.example.activitytest.FirstActivity@5fbd8e1
com.example.activitytest.FirstActivity@40b5390

可以看出每點擊一次就會創建一個新的FirstActivity實例。此時返回棧中就有3個FirstActivity實例,因此我們需要連續按3此Back鍵才能退出程序。

2.5.2 singleTop

singleTop又稱作“棧頂複用式”,就是在啓動活動時,如果發現返回棧的棧頂已經是該活動了,那麼就直接使用它,不會再創建新的實例。

修改AndroidManifest.xml中的FirstActivity的啓動模式,如下所示:

<activity
    android:name=".FirstActivity"
    android:launchMode="singleTop"
    android:label="This is FirstActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

運行程序,可以看到logcat信息如下:

com.example.activitytest/u0a63 for activity com.example.activitytest/.FirstActivity

之後無論你點擊多少次都不會再有新的打印信息出現。因爲此時FirstActivity處於棧頂的位置,不會再創建新的實例。但是當FirstActivity不處於棧頂的位置時,這時再啓動FirstActivity是會創建新的實例的。

2.5.3 singleTask

singleTask又稱作“棧內複用式”,每次啓動活動時都會先在返回棧中檢查是否存在該活動的實例,如果發現已經存在則直接使用,並把這個活動之上的所有活動統統出棧,如果沒有發現就會創建一個新的活動實例。

修改AndroidManifest.xml中的FirstActivity的啓動模式,如下所示:

<activity
    android:name=".FirstActivity"
    android:launchMode="singleTask"
    android:label="This is FirstActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

並在FirstActivity中添加onRestart()方法:

@Override
protected void onRestart() {
    super.onRestart();
    Log.d(TAG, "onRestart: ");
}

在SecondActivity中添加onDestroy()方法:

@Override
protected void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: ");
}

運行程序,在FirstActivity中點擊進入SecondActivity,然後在SecondActivity中點擊回到FirstActivity,查看logcat中的打印信息:

com.example.activitytest.FirstActivity@132654d
com.example.activitytest.SecondActivity@1d67686
com.example.activitytest D/FirstActivity: onRestart: 
com.example.activitytest D/SecondActivity: onDestroy:

從打印信息中可以看出,SecondActivity在啓動 FirstActivity時,發現返回棧中已經有一個FirstActivity的實例,並且是在SecondActivity下面,此時SecondActivity就會出棧,讓FirstActivity重新處於棧頂的位置。因此FirstActivity中的onRestart和SecondActivity中的onDestroy都會執行,現在任務棧中只剩一個FirstActivity實例,按一下Back鍵就可以退出程序。

2.5.4 singlestance

singlestance又稱作“單例式”。指定爲singlestance模式的活動會啓用一個新的返回棧來管理這個活動,有一個單獨的返回棧來管理這個活動,不管是哪個應用程序來訪問這個活動,都共用同一個返回棧,從而達到了共享活動實例的目的。

修改AndroidManifest.xml中的SecondActivity的啓動模式,如下所示:

<activity android:name=".SecondActivity"
    android:launchMode="singleInstance">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="com.example.activitytest.MY_CATEGORY" />
    </intent-filter>
</activity>

將SecondActivity的啓動模式指定爲singleInstance。

分別修改FirstActivity、SecondActivity、ThirdActivity中onCreate()方法中的log打印信息:

Log.d(TAG, "Task id is " + getTaskId());

修改代碼,在FirstActivity中啓動SecondActivity,在SecondActivity中啓動ThirdActivity,查看logcat中的打印信息,如下所示:

com.example.activitytest D/FirstActivity: Task id is 153
com.example.activitytest D/SecondActivity: Task id is 154
com.example.activitytest D/ThirdActivity: Task id is 153

從logcat信息中可以看出,SecondActivity的任務棧id與FirstActivity和ThirdActivity均不同,所以SecondActivity確實是放在一個單獨的返回棧中。

2.6 活動的最佳實踐

2.6.1 知曉當前是在哪一個活動

在ActivityTest項目中新建一個BaseActivity類。右擊com.example.activitytest包——>New——>Java Class,BaseActivity不需要在AndroidManifest.xml中註冊,所以選擇創建一個普通的Java類就可以了。然後讓BaseActivity繼承AppCompatActivity,並重寫onCreate()方法:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("BaseActivity", getClass().getSimpleName());
}

然後分別讓FirstActivity、SecondActivity、ThirdActivity繼承BaseActivity,而不是直接繼承AppCompatActivity。

運行程序,通過點擊按鈕分別進入FirstActivity、SecondActivity、ThirdActivity的界面,查看logcat中的打印信息:

com.example.activitytest D/BaseActivity: FirstActivity
com.example.activitytest D/BaseActivity: SecondActivity
com.example.activitytest D/BaseActivity: ThirdActivity

每當我們進入到一個活動的界面,該活動的類名就會被打印出來,這樣我們就可以時時刻刻知曉當前界面對應的是哪一個活動了。

2.6.2 隨時隨地退出程序

使用一個集合類對所有的活動進行管理,這樣就可以實現程序隨時隨地退出。

新建一個ActivityCollector類作爲活動管理器:

public class ActivityCollector {

    public static List<Activity> activities = new ArrayList<>();

    public static void addActivity(Activity activity) {
        activities.add(activity);
    }

    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }

    public static void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
        activities.clear();
    }
}

定義一個addActivity()方法來向List中添加一個活動;

定義一個removeActivity()方法用於從List中移除活動;

定義一個finishAll()方法將List中存儲的活動全部銷燬掉。

修改BaseActivity中的代碼:

public class BaseActivity extends AppCompatActivity {

    private static final String TAG = "BaseActivity";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, getClass().getSimpleName());
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }
}

在onCreate()方法中調用addActivity()方法,表示將當前正在創建的活動添加到活動管理器中;重寫onDestroy()方法,並調用removeActivity()方法,表示將一個馬上要銷燬的活動從活動管理器裏移除。

在任何地方想要退出程序,只需要調用ActivityCollector.finishAll()方法。例如想在ThirdActivity界面通過點擊按鈕直接退出程序,則只需要添加如下代碼:

Button button3 = (Button) findViewById(R.id.button_3);
    button3.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ActivityCollector.finishAll();
        }
    });

你還可以在銷燬所有活動的代碼後面加上殺掉當前進行的代碼,以保證程序完全退出,殺掉進程的代碼如下所示:

android.os.Process.killProcess(android.os.Process.myPid());

其中,killProcess()方法用於殺掉一個進程,它接收一個進程id參數,可以通過myPid()方法獲取當前進程的id。需要注意的是,killProcess()方法只能用於殺掉當前程序的進程,不能使用這個方法殺掉其他程序。

2.6.3 啓動活動的最佳寫法

通常情況下我們啓動一個活動的寫法如下:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);       
startActivity(intent);

但如果我們不知道啓動SecondActivity需要傳遞哪些數據?那我們就需要去看SecondActivity的源碼或問開發SecondActivity的同事,這樣就顯示比較繁瑣。

爲了優化這種問題,我們只需要在開發SecondActivity時就定義一個啓動方法:

public static void actionStart(Context context, String data1, String data2) {
    Intent intent = new Intent(context, SecondActivity.class);
    intent.putExtra("parm1", data1);
    intent.putExtra("parm2", data2);
    context.startActivity(intent);
}

直接在SecondActivity方法中定義一個actionStart()方法,將需要的數據通過參數傳遞過來,然後將它們存儲在Intent中,最後再調用startActivity()方法啓動SecondActivity。

這樣我們在其他地方啓動SecondActivity就很方便:

SecondActivity.actionStart(FirstActivity.this,"data1","data2");

2.7 小結與點評

本章主要學習了活動的基本用法,到啓動活動和傳遞數據的方式,再到活動的生命週期,以及活動的啓動模式等。

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