Task和BackStack的基本概念
Task的理解
Task是多個(gè)Activity的集合,用戶進(jìn)行操作時(shí)將與這些Activity進(jìn)行交互。 這些 Activity 按照啟動(dòng)順序排隊(duì)存入一個(gè)棧(即“back stack”)。
Back Stack
大部分Task都啟動(dòng)自Home屏幕。當(dāng)用戶觸摸application launcher中的圖標(biāo)(Home屏幕上的快捷圖標(biāo))時(shí),應(yīng)用程序的Task就進(jìn)入前臺(tái)。 如果該應(yīng)用不存在Task(最近沒有使用過此應(yīng)用),則會(huì)新建一個(gè)Task,同時(shí)android為新 Task創(chuàng)建一個(gè)新的返回棧(back stack),該應(yīng)用的“main”activity作為棧的根Activity被打開。
Task和Back Stack的關(guān)系
當(dāng)用戶主頁執(zhí)行另一個(gè)新的Task時(shí),一個(gè)Task被移動(dòng)到后臺(tái)執(zhí)行,此時(shí)它的返回棧(Back Stack)也被保存在后臺(tái), 同時(shí)android為新Task創(chuàng)建一個(gè)新的返回棧(Back Stack)。當(dāng)后臺(tái)的Task被再次運(yùn)行從而返回前臺(tái)時(shí),它的返回棧(Back Stack)被移到前臺(tái),并恢復(fù)其之前執(zhí)行的Activity如果后臺(tái)有太多運(yùn)行Task,系統(tǒng)將會(huì)殺死一些Task釋放內(nèi)存。
如果當(dāng)前Activity啟動(dòng)了另一個(gè)Activity,則新的Activity被壓入棧頂并獲得焦點(diǎn)。 前一個(gè)Activity仍保存在棧中,但是被停止。Activity停止時(shí),系統(tǒng)會(huì)保存用戶界面的當(dāng)前狀態(tài)。 當(dāng)用戶按下返回鍵,則當(dāng)前Activity將從棧頂彈出(被銷毀),前一個(gè)Activity將被恢復(fù)(之前的用戶界面狀態(tài)被恢復(fù))。Activity在棧中的順序永遠(yuǎn)不會(huì)改變,只會(huì)壓入和彈出——被當(dāng)前Activity啟動(dòng)時(shí)壓入棧頂,用戶用返回鍵離開時(shí)從棧頂退出。 這樣,Back Stack以“后進(jìn)先出”的方式運(yùn)行。
實(shí)現(xiàn)退出APP時(shí)銷毀回退棧中所有Activity(context.finishAffinity());
Activity的啟動(dòng)模式
standard
默認(rèn)模式,可以不用寫配置。在這個(gè)模式下,每次激活 Activity 時(shí)都會(huì)創(chuàng)建 Activity實(shí)例,并放入回退棧中(棧頂)。該Activity可以被實(shí)例化多次,各個(gè)實(shí)例可以屬于不同的Task,一個(gè)Task中也可以存在多個(gè)實(shí)例。
例如:
若我有一個(gè)Activity名為A1, 上面有一個(gè)按鈕可跳轉(zhuǎn)到A1。那么如果我點(diǎn)擊按鈕,便會(huì)新啟一個(gè) Activity A1疊在剛才的A1之上,再點(diǎn)擊,又會(huì)再新啟一個(gè)在它之上……
點(diǎn)Back鍵會(huì)依照棧順序依次退出。
singleTop
如果在回退棧的棧頂正好存在該Activity的實(shí)例,就重用該實(shí)例(會(huì)調(diào)用實(shí)例的 onNewIntent()),否則就會(huì)創(chuàng)建新的實(shí)例并放入棧頂(注:即使棧中已經(jīng)存在該 Activity的實(shí)例,只要不在棧頂,都會(huì)創(chuàng)建實(shí)例),即:不允許多個(gè)相同 Activity 疊加,保證Task棧頂部有且只有一個(gè)Activity實(shí)例對(duì)象
例如:
若我有兩個(gè)Activity名為B1,B2,兩個(gè)Activity內(nèi)容功能完全相同,都有兩個(gè)按鈕可以跳到B1或者B2,唯一不同的是B1為standard,B2為singleTop。
若我意圖打開的順序?yàn)锽1->B2->B2,則實(shí)際打開的順序?yàn)锽1->B2(后一次意圖打開B2,實(shí)際只調(diào)用了前一個(gè)的onNewIntent方法)
若我意圖打開的順序?yàn)锽1->B2->B1->B2,則實(shí)際打開的順序與意圖的一致,為B1->B2->B1->B2。和默認(rèn)啟動(dòng)沒區(qū)別。
singleTask
如果在棧中已經(jīng)有該Activity的實(shí)例,就重用該實(shí)例(會(huì)調(diào)用實(shí)例的onNewIntent ())。重用時(shí),會(huì)讓該實(shí)例回到棧頂,因此在它上面的實(shí)例將會(huì)被移出棧。如果棧中不存在該實(shí)例,將會(huì)創(chuàng)建新的實(shí)例放入棧中。
即:保證Task棧中有且只有一個(gè)該Activity實(shí)例對(duì)象。
singleInstance
在一個(gè)新棧中創(chuàng)建該Activity實(shí)例,并讓多個(gè)應(yīng)用共享該Activity實(shí)例。一旦這種模式的Activity實(shí)例存在于某個(gè)棧中,任何應(yīng)用再激活這個(gè)Activity時(shí)都會(huì)重用該棧中的實(shí)例,其效果相當(dāng)于多個(gè)應(yīng)用程序共享一個(gè)應(yīng)用,不管誰激活該Activity 都會(huì)進(jìn)入同一個(gè)應(yīng)用中。此啟動(dòng)模式和我們電腦使用的瀏覽器工作原理類似,在多個(gè)程序中訪問瀏覽器時(shí),如果當(dāng)前瀏覽器沒有打開,則打開瀏覽器,否則會(huì)在當(dāng)前打開的瀏覽器中訪問。此模式會(huì)節(jié)省大量的系統(tǒng)資源,因?yàn)樗鼙WC要請(qǐng)求的Activity對(duì)象在當(dāng)前的棧中只存在一個(gè)。
“l(fā)aunchMode”設(shè)置為”singleInstance”的Activity總是在棧底, 只能被實(shí)例化一次, 不允許其它的Activity壓入”singleInstance”的 Activity所在Task棧,即整個(gè) Task棧中只能有這么一個(gè) Activity。
注意: 雖然Activity啟動(dòng)了一個(gè)新的Task,但用戶仍然可以用回退鍵返回前一個(gè) Task。
即:只有一個(gè)實(shí)例,并且這個(gè)實(shí)例獨(dú)立運(yùn)行在一個(gè)Task中,這個(gè)Task只有這個(gè)實(shí)例,不允許有別的Activity 存在。
例如:
程序有三個(gè)ActivityD1,D2,D3,三個(gè)Activity 可互相啟動(dòng),其中D2為singleInstance模式。那么程序從D1開始運(yùn)行, 假設(shè)D1的taskId為200,那么從D1啟動(dòng)D2時(shí),D2會(huì)新啟動(dòng)一個(gè)Task,即D2與D1不在一個(gè)Task中運(yùn)行。假設(shè)D2的taskId為201,再從D2啟動(dòng)D3時(shí),D3的taskId為200,也就是說它被壓到了D1啟動(dòng)的任務(wù)棧中。
若是在別的應(yīng)用程序打開D2,假設(shè)Other的taskId為200,打開D2,D2會(huì)新建一個(gè)Task運(yùn)行,假設(shè)它的taskId為201,那么如果這 時(shí)再從D2啟動(dòng)D1或者D3,則又會(huì)再創(chuàng)建一個(gè)Task,因此,若操作步驟為other->D2->D1,這過程就涉及到了3個(gè)Task了。
注: 當(dāng)應(yīng)用中有三個(gè)Activity 為 A、B、C,B啟動(dòng)模式為singleInstance ,若此時(shí)啟動(dòng)順序?yàn)?A > B > C ,那么此時(shí)按返回鍵將回到A,再按返回鍵A結(jié)束后才會(huì)回到B。即:只有當(dāng)當(dāng)前任務(wù)的回退棧中的所有 Activity都結(jié)束 ,才會(huì)回到上一個(gè)Task。
Intent屬性的特點(diǎn)及用法
說完Activity,再來說說Intent的屬性
Intent中的ComponentName屬性
明確指定Intent將要啟動(dòng)哪個(gè)組件,因此這種Intent被稱為顯式Intent,沒有指定 ComponentName屬性的Intent被稱為隱式Intent。隱式Intent沒有明確要啟動(dòng)哪個(gè)組件,應(yīng)用會(huì)根據(jù)Intent指定的規(guī)則(動(dòng)作)去啟動(dòng)符合條件的組件。ComponentName不僅可以啟動(dòng)本程序中的Activity,還可以啟動(dòng)其它程序的 Activity。
打開本應(yīng)用程序的其他 Activity
Intent intent =new Intent();
ComponentNamecomponent=new ComponentName(Context, TwoActivity.class);
intent.setComponent(component);
startActivity(intent);
打開其他應(yīng)用程序的 Activity
Intent intent = new Intent();
ComponentName componentName = new ComponentName("目標(biāo)應(yīng)用程序的包名","目標(biāo)Activity的類(含路徑)");
intent.setComponent(componentName);
startActivity(intent);
Intent中Action屬性
Action、Category屬性與intent-filter配置:
通常,Action, Category 屬性結(jié)合使用。定義這兩個(gè)屬性都是在主配置文件 AndroidManifest.xml 的<intent-filter>節(jié)點(diǎn)中。Intent通過定義Action屬性(其實(shí)就是定義一段自定義的字符串),這樣就可以把Intent與具體的某個(gè)Activity分離。否則,每次跳轉(zhuǎn)都有寫成類似new Intent(MainActivity.this,NextActivity.class)這樣的形式,也就是說必須將要跳轉(zhuǎn)的目標(biāo)Activity的名字寫出來,這樣的編碼其實(shí)是“硬編碼”,并沒有實(shí)現(xiàn)松耦合。調(diào)用Intent對(duì)象的setAction()方法實(shí)現(xiàn)頁面跳轉(zhuǎn)雖然略微復(fù)雜(需要在 AndroidManifest.xml文件中配置),但是實(shí)現(xiàn)松耦合。
例如,在 A 應(yīng)用(甚至其他應(yīng)用)中對(duì)某個(gè) Activity 做如下配置:
<activity
android:name=".MainActivity"
android:enabled="true"
android:exported="true"
android:label="@string/app_name" >
<intent-filter>
……
</intent-filter>
<intent-filter>
<action android:name="com.rair.xxx" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
然后在B 應(yīng)用中啟動(dòng)配置以上信息的界面,使用如下代碼:
Intent intent = new Intent();
intent.setAction("com.rair.xxx");
intent.addCategory(Intent.CATEGORY_DEFAULT);//可以省略,默認(rèn)就是DEFAULT
startActivity(intent);
這樣子所有配置了該信息的Activity都會(huì)彈出來讓用戶選擇。類似打開音樂彈出多個(gè)音樂播放器、打開連接彈出多個(gè)瀏覽器等。
Intent中Category屬性
Category 屬性用于指定當(dāng)前動(dòng)作(Action)被執(zhí)行的環(huán)境。通過 addCategory() 方法或在清單文件 AndroidManifest.xml 中設(shè)置。默認(rèn)為:CATEGORY_DEFAULT。一般我們配置的 隱式意圖 都需要加上該屬性值
例如:
CATEGORY_DEFAULT:Android系統(tǒng)中默認(rèn)的執(zhí)行方式,按照普通Activity的執(zhí)行方式執(zhí)行。
CATEGORY_HOME:設(shè)置該組件為HomeActivity。
CATEGORY_LAUNCHER:設(shè)置該組件為在當(dāng)前應(yīng)用程序啟動(dòng)器中優(yōu)先級(jí)最高的Activity,通常為入口ACTION_MAIN配合使用?!?br>
CATEGORY_BROWSABLE:設(shè)置該組件可以使用瀏覽器啟動(dòng)。
等...
該屬性大部分都是配合系統(tǒng)組件使用。
例如:
實(shí)現(xiàn)頁面跳轉(zhuǎn)到Home界面:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);
實(shí)現(xiàn)頁面跳轉(zhuǎn)到瀏覽器界面:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_APP_BROWSER);
startActivity(intent);
以及讓某一個(gè)Activity作為啟動(dòng)頁面,則清單配置文件中對(duì)Activity配置:
<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>
Intent中Data屬性
用于添加數(shù)據(jù)。通常是啟動(dòng)某個(gè)系統(tǒng)程序或其他程序,帶給此程序的信息。Data屬性通常用于向Action屬性提供操作的數(shù)據(jù)。Data屬性的值是個(gè)Uri對(duì)象。
Uri的格式如下:scheme://host:port /path
系統(tǒng)內(nèi)置的屬性常量:
tel://:號(hào)碼數(shù)據(jù)格式,后跟電話號(hào)碼。
mailto://:郵件數(shù)據(jù)格式,后跟郵件收件人地址。
smsto://:短息數(shù)據(jù)格式,后跟短信接收號(hào)碼。
content://:內(nèi)容數(shù)據(jù)格式,后跟需要讀取的內(nèi)容?!?br> file://:文件數(shù)據(jù)格式,后跟文件路徑。
market://search?q=pname:pkgname:市場(chǎng)數(shù)據(jù)格式,在GoogleMarket里搜索包名為pkgname的應(yīng)用。
geo://latitude,longitude:經(jīng)緯數(shù)據(jù)格式,在地圖上顯示經(jīng)緯度指定的位置。
在intent-filter中指定data屬性的實(shí)際目的是:要求接收的Intent中的data必須符合intent-filter中指定的data屬性,這樣達(dá)到反向限定Intent的作用。
例如:在AndroidManifest.xml中進(jìn)行如下設(shè)置:
<activity android:name=".TestActivity">
<intent-filter>
<action android:name="com.rair.test"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file"/>
</intent-filter>
</activity>
那么啟動(dòng)該 Activity 的 Intent 必須進(jìn)行如下設(shè)置:
Intent intent = new Intent();
intent.setAction("com.rair.test");
Uri uri = Uri.parse("file://****");
intent.setData(uri);
data屬性解析:android:scheme、android:host、android:port、android:path
data的前四個(gè)屬性構(gòu)成了URI的組成部分
data元素組成的URI模型如下:
舉例:
URI file://com.android.rair.test:520/mnt/sdcard
scheme-->file:
host-->com.android.rair.test
port-->520
path-->mnt/sdcard
其中host和port為URI的authority,如果沒有指定host,port將被忽略
data的各屬性并不是獨(dú)立的,data的各屬性構(gòu)成了URI的整個(gè)組成部分。要使 authority(host和port)有意義,必須指定scheme;要使path有意義,必須使 scheme和authority(host 和 port)有意義。
URI和intent-filter匹配:
Intent中URI和intent-filter進(jìn)行比較的時(shí)候只會(huì)進(jìn)行部分的比較:
(1)當(dāng)intent-filter中只設(shè)置了scheme,只會(huì)比較URI的scheme部分;
(2)當(dāng)intent-filter中只設(shè)置了scheme和authority,那么只會(huì)匹配URI中的 scheme和authority;
(3)當(dāng)intent-filter中設(shè)置了scheme、authority和path,那么會(huì)匹配URI中的 scheme、authority、path;(path可以使用通配符進(jìn)行匹配)
Intent利用Action屬性和Data屬性啟動(dòng)Android系統(tǒng)內(nèi)置組件的代碼
(一)、直接撥打電話:需要打電話權(quán)限
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:5556"));
startActivity(intent);
//調(diào)用撥號(hào)面板:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("tel:654654"));
startActivity(intent);
(二)、利用Uri打開瀏覽器等:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.parse("https://www.baidu.com");//瀏覽器
intent.setData(uri);
startActivity(intent);
Intent中Type屬性
Type屬性用于指定Data所指定的Uri對(duì)應(yīng)的MIME類型。MIME只要符合“abc/xyz”這樣的字符串格式即可。
注意: 如果格式不符合 "abc/xyz" 則程序不能編譯運(yùn)行。
利用Action、Data 和Type屬性啟動(dòng)Android系統(tǒng)內(nèi)置組件的代碼:
例如:播放視頻
Intent intent = new Intent();
Uri uri =Uri.parse("file:///sdcard/media.mp4");
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(uri,"video/*");
startActivity(intent);
Intent中Extra屬性
1、通過 intent.putExtra(鍵, 值)的形式在多個(gè)Activity之間進(jìn)行數(shù)據(jù)交換。
2、利用 Action、Data 、Extra 屬性啟動(dòng) Android 系統(tǒng)內(nèi)置組件的代碼:
例如:調(diào)用發(fā)送短信的程序
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SENDTO);
Uri uri = Uri.parse("smsto:13343333433");
intent.setData(uri);
intent.putExtra("sms_body", "已經(jīng)編輯好的短信內(nèi)容....");
startActivity(intent);
Intent中Flags屬性
FLAG_ACTIVITY_SINGLE_TOP:
和上面Activity的LaunchMode的singleTop類似。如果某個(gè)Intent添加了這個(gè)標(biāo)志,并且這個(gè)Intent的目標(biāo)Activity就是棧頂?shù)腁ctivity,那么將不會(huì)新建一個(gè)實(shí)例壓入棧中。
FLAG_ACTIVITY_CLEAR_TOP:
例如現(xiàn)在的棧情況為:A B C D。D此時(shí)通過intent跳轉(zhuǎn)到B,如果這個(gè)Intent添加FLAG_ACTIVITY_CLEAR_TOP標(biāo)記,則棧情況變?yōu)椋篈B。如果沒有添加這個(gè)標(biāo)記,則棧情況將會(huì)變成:A B C D B。也就是說,如果添加了FLAG_ACTIVITY_CLEAR_TOP標(biāo)記,并且目標(biāo)Activity在棧中已經(jīng)存在,則將會(huì)把位于該目標(biāo)Activity之上的Activity從棧中彈出銷毀。這跟上面把B的 Launchmode設(shè)置成singleTask類似。
FLAG_ACTIVITY_NO_HISTORY:
例如現(xiàn)在棧情況為:ABC。C通過 intent 跳轉(zhuǎn)到D,這個(gè)Intent添加FLAG_ACTIVITY_NO_HISTORY標(biāo)志,則此時(shí)界面顯示D的內(nèi)容,但是它并不會(huì)壓入棧中。如果按返回鍵,返回到C,棧的情況還是:A B C。如果此時(shí)D中又跳轉(zhuǎn)到E,棧的情況變?yōu)椋篈 B C E,此時(shí)按返回鍵會(huì)回到C,因?yàn)镈根本就沒有被壓入棧中。
FLAG_ACTIVITY_NEW_TASK:
例如現(xiàn)在棧1的情況是:A B C。C通過Intent跳轉(zhuǎn)到D,并且這個(gè)Intent添加了FLAG_ACTIVITY_NEW_TASK標(biāo)記,如果D這個(gè)Activity在Manifest.xml中的聲明中添加了Taskaffinity,并且和棧1的affinity不同,系統(tǒng)首先會(huì)查找有沒有和D的Taskaffinity相同的Task棧存在,如果有存在,將D壓入那個(gè)棧,如果不存在則會(huì)新建一個(gè)D的affinity的棧將其壓入。如果D的Taskaffinity默認(rèn)沒有設(shè)置,或者和棧1的affinity相同,則會(huì)把其壓入棧1,變成:A B C D,這樣就和不加FLAG_ACTIVITY_NEW_TASK標(biāo)記效果是一樣的了。
注意: 如果試圖從非Activity的非正常途徑啟動(dòng)一個(gè)Activity,比如,從一個(gè)service中啟動(dòng)一個(gè)Activity,則Intent必須要添加FLAG_ACTIVITY_NEW_TASK標(biāo)記。
Activity的管理及退出程序
在處理Activity時(shí)可能需要跳轉(zhuǎn),刪除指定的Activity或所有的Activity。當(dāng)退出應(yīng)用程序時(shí),我們可能需要關(guān)閉所有的Activity,這里對(duì)于新手來說經(jīng)常會(huì)存在MainActivity已經(jīng)finish掉了,但是還有其他的Activity存在。我們可以設(shè)計(jì)一個(gè)全局的Activity棧,使用這個(gè)棧來管理Activity。
/**
* Activity管理類
*
*/
public class ActivityControl {
private static Stack<Activity> activityStack;
private static ActivityControl instance;
private ActivityControl() {
}
/**
* 單一實(shí)例
*/
public static ActivityControl getInstance() {
if (instance == null) {
instance = new ActivityControl();
}
return instance;
}
/**
* 添加Activity到堆棧
*/
public void addActivity(Activity activity) {
if (activityStack == null) {
activityStack = new Stack<Activity>();
}
activityStack.add(activity);
}
/**
* 獲取當(dāng)前Activity(堆棧中最后一個(gè)壓入的)
*/
public Activity currentActivity() {
Activity activity = activityStack.lastElement();
return activity;
}
/**
* 結(jié)束當(dāng)前Activity(堆棧中最后一個(gè)壓入的)
*/
public void finishActivity() {
Activity activity = activityStack.lastElement();
finishActivity(activity);
}
/**
* 結(jié)束指定的Activity
*/
public void finishActivity(Activity activity) {
if (activity != null) {
activityStack.remove(activity);
activity.finish();
activity = null;
}
}
/**
* 結(jié)束指定類名的Activity
*/
public void finishActivity(Class<?> cls) {
for (Activity activity : activityStack) {
if (activity.getClass().equals(cls)) {
finishActivity(activity);
}
}
}
/**
* 結(jié)束所有Activity
*/
public void finishAllActivity() {
for (int i = 0, size = activityStack.size(); i < size; i++) {
if (null != activityStack.get(i)) {
activityStack.get(i).finish();
}
}
activityStack.clear();
}
/**
* 退出應(yīng)用程序
*/
@SuppressWarnings("deprecation")
public void AppExit(Context context) {
try {
finishAllActivity();
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.restartPackage(context.getPackageName());
System.exit(0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Activity是Android組件中最基本也是最為常見用的四大組件之一。是一個(gè)應(yīng)用程序組件,提供一個(gè)屏幕,用戶可以用來交互為了完成某項(xiàng)任務(wù)。Activity中所有操作都與用戶密切相關(guān),是一個(gè)負(fù)責(zé)與用戶交互的組件,可以通過setContentView(View)來顯示指定控件。在一個(gè)android應(yīng)用中,一個(gè)Activity通常就是一個(gè)單獨(dú)的屏幕,它上面可以顯示一些控件也可以監(jiān)聽并處理用戶的事件做出響應(yīng)。Activity之間通過Intent進(jìn)行通信。因此,Activity在我們開發(fā)中很重要,ok,到此為止。