本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):
- 顯示、隱式
Intent
的相關(guān)內(nèi)容;- 活動(dòng)
Activity
的生命周期;- 活動(dòng)
Activity
的啟動(dòng)模式;- 活動(dòng)的最佳實(shí)踐:活動(dòng)管理類、啟動(dòng)活動(dòng)的最佳寫(xiě)法。
2.1 活動(dòng)是什么
活動(dòng)(activity
)是一種可以包含用戶界面的組件,主要用于和用戶進(jìn)行交互。
2.2 使用 Intent 在活動(dòng)之間穿梭
Intent
大致可分為兩種:顯示 Intent
和 隱式 Intent
。
2.2.1 使用顯示 Intent
顯示 Intent
有多個(gè)構(gòu)造函數(shù)的重載,其中一個(gè)是 Intent(Context packageContext,Class<?>cls)
:
- 第一個(gè)參數(shù)
Context
要求提供一個(gè)啟動(dòng)活動(dòng)的上下文。 - 第二個(gè)參數(shù)
Class
則是指定想要啟動(dòng)的目標(biāo)活動(dòng)。
代碼如下所示:
// 顯示Intent
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
2.2.2 使用隱式 Intent
隱式 Intent
并不明確指出想要啟動(dòng)哪一個(gè)活動(dòng),而是指定了一系列更為抽象的 action
和 category
等信息,然后交由系統(tǒng)去分析這個(gè) Intent
,并找出合適的活動(dòng)去啟動(dòng)。
用法:打開(kāi) AndroidManifest.xml
,添加如下代碼:
<activity android:name=".inquiry_activity.SecondActivity">
<intent-filter>
<!-- 指定活動(dòng)能響應(yīng)的action和category -->
<action android:name="com.wonderful.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
此時(shí)在 activity
中的代碼修改為:
// 隱式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
startActivity(intent);
只有 <action>
和 <category>
中的內(nèi)容同時(shí)能夠匹配上 Intent
中指定的 action
和 category
時(shí),活動(dòng)才能響應(yīng)該 Intent
。
上例中 android.intent.category.DEFAULT
是一種默認(rèn)的 category
,若 Intent
中增加一個(gè) category
:
// 隱式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
intent.addCategory("com.wonderful.MY_CATEGORY");//增加一個(gè)category
startActivity(intent);
此時(shí)在 <intent-filter>
標(biāo)簽中應(yīng)再添加一個(gè) category
聲明:
<activity android:name=".inquiry_activity.SecondActivity">
<intent-filter>
<!-- 指定活動(dòng)能響應(yīng)的action和category -->
<action android:name="com.wonderful.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<!-- 新添加的category -->
<category android:name="android.intent.category.MY_CATEGORY"/>
</intent-filter>
</activity>
2.2.3 更多隱式 Intent 的用法
使用隱式 Intent
,還可以啟動(dòng)其他程序的活動(dòng),比如調(diào)用系統(tǒng)的瀏覽器來(lái)打開(kāi)某個(gè)網(wǎng)頁(yè):
// 隱式Intent:打開(kāi)網(wǎng)頁(yè)
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
比如調(diào)用系統(tǒng)的撥號(hào)界面:
// 隱式Intent:打開(kāi)系統(tǒng)撥號(hào)界面
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
2.2.4 向下一個(gè)活動(dòng)傳遞數(shù)據(jù)
Intent 中提供了一系列 putExtra()
方法的重載,可把要傳遞的數(shù)據(jù)暫存在 Intent
中,啟動(dòng)了另一個(gè)活動(dòng)后再?gòu)?Intent
中取出。
如 FirstActivity
中傳遞一個(gè)字符串到 SecondActivity
中,FirstActivity
中的代碼為:
String data = "hello SecondActivity";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);
SecondActivity
中的代碼如下:
Intent intent = getIntent();
// String:getStringExtra(); int:getIntExtra(); 布爾型:getBooleanExtra() ...以此類推
String data = intent.getStringExtra("extra_data");
Log.d(TAG, data);
2.2.5 返回?cái)?shù)據(jù)給上一個(gè)活動(dòng)
Activity 中有個(gè) startActivityForResult()
方法用于啟動(dòng)活動(dòng),此方法在活動(dòng)銷毀時(shí)能返回一個(gè)結(jié)果給上個(gè)活動(dòng)。
startActivityForResult()
方法接收兩個(gè)參數(shù):Intent
、請(qǐng)求碼(用作回調(diào)時(shí)判斷數(shù)據(jù)來(lái)源)。
修改 FirstActivity
中的代碼如下:
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);//請(qǐng)求碼只要是唯一值就行了,這里傳入1
接著在 SecondActivity
中添加返回?cái)?shù)據(jù):
Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();//銷毀當(dāng)前活動(dòng)
在 SecondActivity
被銷毀后回調(diào)上一個(gè)活動(dòng)的 onActivityResult()
方法,因此還需要在 FirstActivity
中重寫(xiě)此方法來(lái)得到返回?cái)?shù)據(jù):
/**
* 處理得到的返回?cái)?shù)據(jù)
* @param requestCode 啟動(dòng)活動(dòng)時(shí)傳入的請(qǐng)求碼
* @param resultCode 返回?cái)?shù)據(jù)時(shí)傳入的處理結(jié)果
* @param data 攜帶返回?cái)?shù)據(jù)的Intent
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
if (resultCode == RESULT_OK){
String returnedData = data.getStringExtra("data_return");
Log.d(TAG, returnedData);
}
break;
default:
break;
}
}
若在 SecondActivity
是通過(guò)按下 Back
鍵回到 FirstActivity
,可以在 SecondActivity
中重寫(xiě) onBackPressed()
方法:
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}
2.3 活動(dòng)的生命周期
2.3.1 返回棧
Android 是使用任務(wù)(Task)來(lái)管理活動(dòng)的,一個(gè)任務(wù)就是一組存放在棧里的活動(dòng)的集合,這個(gè)棧也被稱作返回棧(Back Stack
)。
棧是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),在默認(rèn)情況下,每當(dāng)啟動(dòng)了一個(gè)新的活動(dòng),它會(huì)在返回棧中入棧,并處于棧頂?shù)奈恢谩?/p>
每當(dāng)按下 Back
鍵或調(diào)用 finish()
方法去銷毀一個(gè)活動(dòng)時(shí),處于棧頂?shù)幕顒?dòng)會(huì)出棧,這時(shí)前一個(gè)入棧的活動(dòng)就會(huì)重新處于棧頂?shù)奈恢谩O到y(tǒng)總是會(huì)顯示處于棧頂?shù)幕顒?dòng)給用戶。
如圖所示:
2.3.2 活動(dòng)狀態(tài)
每個(gè)活動(dòng)在其生命周期中最多可能會(huì)有四種狀態(tài)。
1. 運(yùn)行狀態(tài)
活動(dòng)位于返回棧的棧頂。系統(tǒng)最不愿意回收的就是處于運(yùn)行狀態(tài)的活動(dòng),因?yàn)檫@會(huì)帶來(lái)非常差的用戶體驗(yàn)。
2. 暫停狀態(tài)
活動(dòng)不再處于棧頂位置,但仍然可見(jiàn)。(并不是每一個(gè)活動(dòng)都會(huì)占滿整個(gè)屏幕的,如對(duì)話框形式的活動(dòng)只會(huì)占用屏幕中間的部分區(qū)域)。
處于暫停狀態(tài)的活動(dòng)仍然是完全存活著的,系統(tǒng)也不愿意去回收這種活動(dòng)(因?yàn)樗€是可見(jiàn)的,回收可見(jiàn)的東西都會(huì)在用戶體驗(yàn)方面有不好的影響),只有在內(nèi)存極低的情況下,系統(tǒng)才會(huì)去考慮回收這種活動(dòng)。
3. 停止?fàn)顟B(tài)
活動(dòng)不再處于棧頂位置,并且完全不可見(jiàn)的時(shí)候。系統(tǒng)仍然會(huì)為這種活動(dòng)保存相應(yīng)的狀態(tài)和成員變量,但這并不是完全可靠的,當(dāng)其他地方需要內(nèi)存時(shí),處于停止?fàn)顟B(tài)的活動(dòng)有可能會(huì)被系統(tǒng)回收。
4. 銷毀狀態(tài)
當(dāng)一個(gè)活動(dòng)從返回棧中移除后就變成了銷毀狀態(tài)。系統(tǒng)會(huì)最傾向于回收處于這種狀態(tài)的活動(dòng),從而保證手機(jī)的內(nèi)存充足。
2.3.3 活動(dòng)的生存期
Activity 類中定義了七個(gè)回調(diào)方法,覆蓋了活動(dòng)生命周期的每一個(gè)環(huán)節(jié):
1. onCreate()
在活動(dòng)第一次被創(chuàng)建的時(shí)候調(diào)用。在這個(gè)方法中完成活動(dòng)的初始化操作,如加載布局、綁定事件等。
2. onStart()
在活動(dòng)由不可見(jiàn)變?yōu)榭梢?jiàn)的時(shí)候調(diào)用。
3. onResume()
在活動(dòng)準(zhǔn)備好和用戶進(jìn)行交互的時(shí)候調(diào)用。此時(shí)的活動(dòng)一定位于返回棧的棧頂,并且處于運(yùn)行狀態(tài)。
4. onPause()
在系統(tǒng)準(zhǔn)備去啟動(dòng)或恢復(fù)另一個(gè)活動(dòng)的時(shí)候調(diào)用。(釋放一些消耗 CPU 的資源,保存一些關(guān)鍵數(shù)據(jù))。
5. onStop()
在活動(dòng)完全不可見(jiàn)的時(shí)候調(diào)用。它和 onPause()
方法的主要區(qū)別在于,若啟動(dòng)的新活動(dòng)是一個(gè)對(duì)話框式的活動(dòng),那么 onPause()
方法會(huì)得到執(zhí)行,而 onStop()
方法并不會(huì)執(zhí)行。
6. onDestroy()
在活動(dòng)被銷毀之前調(diào)用,之后活動(dòng)的狀態(tài)將變?yōu)殇N毀狀態(tài)。
7. onRestart()
在活動(dòng)由停止?fàn)顟B(tài)變?yōu)檫\(yùn)行狀態(tài)之前調(diào)用,也就是活動(dòng)被重新啟動(dòng)了。
以上七個(gè)方法中除了 onRestart()
方法,其他都是兩兩相對(duì)的,從而又可以將活動(dòng)分為三種生存期。
- 完整生存期: 活動(dòng)在
onCreate()
方法和onDestroy()
方法之間所經(jīng)歷的; - 可見(jiàn)生存期: 活動(dòng)在
onStart()
方法和onStop()
方法之間所經(jīng)歷的; - 前臺(tái)生存期: 活動(dòng)在
onResume()
方法和onPause()
方法之間所經(jīng)歷的。
Android 官方提供了一張活動(dòng)生命周期的示意圖,如圖所示:
2.3.4 活動(dòng)被回收了怎么辦
Activity 中提供了一個(gè) onSaveInstanceState()
回調(diào)方法,這 個(gè)方法會(huì)保證一定在活動(dòng)被回收之前調(diào)用。
??
onSaveInstanceState()
方法會(huì)攜帶一個(gè) Bundle
類型的參數(shù),Bundle
提供了一系列的方法用于保存數(shù)據(jù),如用 putString()
方法保存字符串,用 putInt()
方法保存整型數(shù)據(jù), 以此類推。每個(gè)保存方法需要傳入兩個(gè)參數(shù),第一個(gè)參數(shù)是鍵,用于后面從 Bundle
中取值, 第二個(gè)參數(shù)是真正要保存的內(nèi)容。
在 Activity
中添加如下代碼就可以將臨時(shí)數(shù)據(jù)進(jìn)行保存:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}
Activity
被回收后的恢復(fù)操作,修改其 onCreate()
方法,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) { // 若不為空,取出相應(yīng)的數(shù)據(jù)
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
}
2.4 活動(dòng)的啟動(dòng)模式
啟動(dòng)模式一共有四種,分別是 standard
、singleTop
、singleTask
和 singleInstance
,可以在 AndroidManifest.xml
中通過(guò)給 <activity>
標(biāo)簽指定 android:launchMode
屬性來(lái)選擇啟動(dòng)模式。
2.4.1 standard
standard 是活動(dòng)默認(rèn)的啟動(dòng)模式,每當(dāng)啟動(dòng)一個(gè)新的活動(dòng),它就會(huì)在返回棧中入棧,并處于棧頂?shù)奈恢谩?duì)于使用 standard
模式的活動(dòng),系統(tǒng)不會(huì)在乎這個(gè)活動(dòng)是否已經(jīng)在返回棧中存在,每次啟動(dòng)都會(huì)創(chuàng)建該活動(dòng)的一個(gè)新的實(shí)例。
??
其原理示意圖如下:
2.4.2 singleTop
當(dāng)活動(dòng)的啟動(dòng)模式指定為 singleTop
,在啟動(dòng)活動(dòng)時(shí)如果發(fā)現(xiàn)返回棧的棧頂已經(jīng)是該活動(dòng),則認(rèn)為可以直接使用它,不會(huì)再創(chuàng)建新的活動(dòng)實(shí)例。
其原理示意圖如下:
2.4.3 singleTask
當(dāng)活動(dòng)的啟動(dòng)模式指定為 singleTask
,每次啟動(dòng)該活動(dòng)時(shí)系統(tǒng)首先會(huì)在返回棧中檢查是否存在該活動(dòng)的實(shí)例,如果發(fā)現(xiàn)已經(jīng)存在則直接使用該實(shí)例,并把在這個(gè)活動(dòng)之上的所有活動(dòng)統(tǒng)統(tǒng)出棧,如果沒(méi)有發(fā)現(xiàn)就會(huì)創(chuàng)建一個(gè)新的活動(dòng)實(shí)例。
其原理示意圖如下:
2.4.4 singleInstance
不同于以上三種啟動(dòng)模式,指定為 singleInstance
模式的活動(dòng)會(huì)啟用一個(gè)新的返回棧來(lái)管理這個(gè)活動(dòng)(其實(shí)如果 singleTask
模式指定了不同的 taskAffinity
,也會(huì)啟動(dòng)一個(gè)新的返回棧)。
??
其原理示意圖如下:
2.5 活動(dòng)的最佳實(shí)踐
2.5.1 隨時(shí)隨地退出程序
用一個(gè)專門(mén)的集合類對(duì)所有的活動(dòng)進(jìn)行管理,新建一個(gè) ActivityCollector
類作為活動(dòng)管理器,代碼如下所示:
public class ActivityCollector {
// 通過(guò)一個(gè)List來(lái)緩存活動(dòng)
public static List<Activity> activities = new ArrayList<Activity>();
// 用于向List中添加一個(gè)活動(dòng)
public static void addActivity(Activity activity) {
activities.add(activity);
}
// 用于從List中移除活動(dòng)
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
// 將List中存儲(chǔ)的活動(dòng)全部銷毀掉
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
在活動(dòng)管理器中,我們通過(guò)一個(gè) List
來(lái)暫存活動(dòng),然后提供了一個(gè) addActivity()
方法用于向 List
中添加一個(gè)活動(dòng),提供了一個(gè) removeActivity()
方法用于從 List
中移除活動(dòng),最后 提供了一個(gè) finishAll()
方法用于將 List
中存儲(chǔ)的活動(dòng)全部都銷毀掉。
接下來(lái)修改 BaseActivity
中的代碼如下:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());// 知曉當(dāng)前是在哪一個(gè)活動(dòng)
ActivityCollector.addActivity(this);// 將當(dāng)前正在創(chuàng)建的活動(dòng)添加到活動(dòng)管理期里
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);// 將一個(gè)馬上要銷毀的活動(dòng)從管理器里移除
}
}
在 BaseActivity
的 onCreate()
方法中調(diào)用了 ActivityCollector
的 addActivity()
方法,表明將當(dāng)前正在創(chuàng)建的活動(dòng)添加到活動(dòng)管理器里。然后在 BaseActivity
中重寫(xiě) onDestroy()
方法, 并調(diào)用了 ActivityCollector
的 removeActivity()
方法,表明將一個(gè)馬上要銷毀的活動(dòng)從活動(dòng)管理器里移除。
這樣,不管你想在什么地方退出程序,只需要調(diào)用 ActivityCollector.finishAll()
方法就可以了。
當(dāng)然你還可以在銷毀所有活動(dòng)的代碼后面再加上殺掉當(dāng)前進(jìn)程的代碼,以保證程序完全退出,殺掉進(jìn)程代碼如下:
// killProcess()方法用于殺掉一個(gè)進(jìn)程,它接收一個(gè)進(jìn)程id參數(shù),可通過(guò)myPid()方法獲得當(dāng)前程序的進(jìn)程id
android.os.Process.killProcess(android.os.Process.myPid());
需注意:killProcess()
方法只能用于殺掉當(dāng)前程序的進(jìn)程,不能殺掉其他程序。
2.5.2 啟動(dòng)活動(dòng)的最佳寫(xiě)法
啟動(dòng)活動(dòng)的方法:通過(guò) Intent
構(gòu)建出當(dāng)前的“意圖”,然后調(diào)用 startActivity()
或 startActivityForResult()
方法將活動(dòng)啟動(dòng)起來(lái),如果有數(shù)據(jù)需要從一個(gè)活 動(dòng)傳遞到另一個(gè)活動(dòng),也可以借助 Intent
來(lái)完成。
假設(shè) SecondActivity
中需要用到兩個(gè)非常重要的字符串參數(shù),在啟動(dòng) SecondActivity
的時(shí)候必須要傳遞過(guò)來(lái),那么很容易會(huì)寫(xiě)出如下代碼:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);
但此時(shí) SecondActivity
并不是由你開(kāi)發(fā)的,但現(xiàn)在你負(fù)責(zé)的部分需要有啟動(dòng) SecondActivity
這個(gè)功能,而你卻不清楚啟動(dòng)這個(gè)活動(dòng)需要傳遞哪些數(shù)據(jù)。這時(shí)要么你自己去閱讀 SecondActivity
中的代碼,要么詢問(wèn)負(fù)責(zé)編寫(xiě) SecondActivity
的同事。而換一種寫(xiě)法,就可以輕松解決掉上面的窘境。
修改 SecondActivity
中的代碼如下:
public class SecondActivity extends BaseActivity {
public static void actionStart(Context context, String data1, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
...
}
在 SecondActivity
中添加了一個(gè) actionStart()
方法,在這個(gè)方法中完成了 Intent
的構(gòu)建,另外所有 SecondActivity
中需要的數(shù)據(jù)都是通過(guò) actionStart()
方法的參數(shù)傳遞過(guò)來(lái)的,然后把它們存儲(chǔ)到 Intent
中,最后調(diào)用 startActivity()
方法啟動(dòng) SecondActivity
。
這樣 SecondActivity
所需要的數(shù)據(jù)全部都在方法參數(shù)中體現(xiàn)出來(lái)了,即使不用閱讀 SecondActivity
中的代碼,也可以非常清晰地知道啟動(dòng) SecondActivity
需要傳遞哪些數(shù)據(jù)。
另外,這樣寫(xiě)還簡(jiǎn)化了啟動(dòng)活動(dòng)的代碼,現(xiàn)在只需要一行代碼就可以啟動(dòng) SecondActivity
, 如下:
SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
好了,今天就到這,下篇文章將進(jìn)入第三章的學(xué)習(xí)--UI開(kāi)發(fā)。