知識點目錄
知識點回顧
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 小結與點評
本章主要學習了活動的基本用法,到啓動活動和傳遞數據的方式,再到活動的生命週期,以及活動的啓動模式等。