什么是任務(wù)棧麻掸?
一個application在啟動的時候,能有很多個activity赐纱,我們在按下back鍵的時候脊奋,會回退到上一個activity,那么系統(tǒng)是如何來管理這些activity呢疙描?答案是以棧(task)的形式诚隙,遵循先進后出的原則,默認情況下起胰,一個app只有一個任務(wù)棧(task)久又,如果需要,我們可以指定多個任務(wù)棧(task)。我們可以總結(jié)出以下幾點:
1.任務(wù)棧是app管理activity的一種容器籽孙,遵循先進后出原則
2.一個app默認只有一個任務(wù)棧烈评,由系統(tǒng)指定
3.一個app可以有多個任務(wù)棧火俄,需要我們自己指定
(以下任務(wù)棧全部稱為task)
那么犯建,我們?nèi)绾谓o一個activity指定特定的task,指定特定的task有什么用呢?想要搞清楚這個瓜客,我們就需要了解activity的啟動模式了适瓦。activity的啟動模式一共有四種,分別是standard , singleTop , singleTask , singleInstance;
standard:
這是默認的啟動模式谱仪,每次啟動一個activity玻熙,都會一個一個的添加到當(dāng)前的task中去。為什么是當(dāng)前的呢疯攒,我們知道一個app可能有多個task,假如現(xiàn)在有兩個task,分別是taskA和taskB,taskB中有activityA,這個時候activityA啟動activityB,如果activityB是standard模式嗦随,那么activityB就放入taskB中,而不是taskA敬尺。說起來有些繞口枚尼,自己動動手畫一畫就一目了然了。
這里還有一個小問題砂吞,我們啟動一個activity的時候通常是這樣的代碼:
Intent i = new Intent(context,ActivityB.class);context.startActivity(i);
在startActivity的時候署恍,如果這個context是一個applicationContext,并且ActivityB的啟動模式是standard,那么系統(tǒng)就會報錯:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
因為activityB在企圖進入當(dāng)前的task的時候蜻直,發(fā)現(xiàn)context(applicationContext)不屬于任何task,無法進入盯质。解決辦法就是新建一個task,錯誤信息已經(jīng)說得很清楚了概而,具體代碼如下:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)呼巷;
singleTop:
顧名思義,如果activity已經(jīng)位于task的頂端赎瑰,那么當(dāng)再次啟動這個activity的時候王悍,他不會被重新創(chuàng)建,而是調(diào)用它的onNewIntent(Intent i)方法,想要傳遞的數(shù)據(jù)乡范,可以在intent參數(shù)里得到配名,onCreate,onStart不會調(diào)用。如果這個activity不在task頂,那么activity的存放方式和standard一樣晋辆,會被重新創(chuàng)建渠脉,累加到task上。
singleTask:
棧內(nèi)復(fù)用模式瓶佳。如果activity在task里存在芋膘,并且此activity是singleTask模式,多次啟動此activity都不會重新創(chuàng)建實例,而是回調(diào)onNewIntent(Intent i) 方法为朋。具體來說臂拓,當(dāng)activityA作為singleTask模式啟動的時候,系統(tǒng)會檢測习寸,有沒有相應(yīng)的task胶惰,如果沒有,就創(chuàng)建一個task霞溪,然后把activityA放入孵滞。如果存在相應(yīng)的task,就會檢查activityA是否位于棧頂鸯匹,如果位于棧頂坊饶,就直接調(diào)用onNewIntent方法,如果不是殴蓬,則activityA上邊所有的activity出棧匿级,然后調(diào)用onNewIntent方法。
singleInstance:
此模式的activity染厅,獨享一個task,也就是說這個task只能有這一個activity痘绎。啟動的時候,會為此activity創(chuàng)建一個task糟秘,并把此activity壓棧简逮,如果在此activity還在棧中的時候,再次啟動此activity尿赚,那么不會調(diào)用此activity的onCreate方法散庶,而是調(diào)用onNewIntent和onStart方法。這種模式的使用場景是:假設(shè)程序中有一個活動是允許其它程序調(diào)用的凌净,如果想使其它程序和這個程序共享這個活動的實例悲龟,使用其它三種啟動模式是不行的,因為每個應(yīng)用程序都有自己的返回棧冰寻,同一個活動在不同的返回棧中入棧時必然是創(chuàng)建了新的實例须教。而使用singleInstance模式可以解決這個問題,在這種模式下會有一個單獨的返回棧來管理這個活動斩芭,不管是哪個應(yīng)用程序來訪問這個活動轻腺,都共用同一個返回棧,也解決了共享活動實例的問題划乖。關(guān)于四種啟動模式贬养,基本解釋清楚了,但是具體使用的時候琴庵,多個任務(wù)棧的情況下误算,按下back鍵仰美,容易搞亂,這里我們單獨講一下儿礼。首先遵循的是這樣一個規(guī)則:出棧的時候咖杂,先讓當(dāng)前棧(前臺棧)清空,再去清空后臺棧蚊夫。
舉個例子:

進入app诉字,啟動activityA,然后activityA->activityB->activityC->activityD,
如果activityA这橙,activityB和activityD是standard模式奏窑,activityC是singleInstance模式导披,
那么此時此時app內(nèi)有兩個task(暫且叫做taskA和taskB)屈扎。
兩個個task的包含分別是如下:
taskA:activityA,activityB撩匕,activityD鹰晨。
taskB:activityC。
此時位于前臺的task是taskA止毕,后臺的是taskB模蜡。按下四次back鍵,依次銷毀的是activityD->activityB->activityA->activityC扁凛。
沒錯就是這樣忍疾,前臺棧清空后,再去清空后臺棧谨朝。
任務(wù)椔倍剩基本就是這樣,但是當(dāng)我們想自己手動給一個activity配置task的時候字币,要怎么操作呢则披,這就得提到一個重要的屬性了:
android:taskAffinity
官方的文檔是這樣解釋的:
The task that the activity has an affinity for. Activities with the same affinity conceptually belong to the same task (to the same “application” from the user’s perspective). The affinity of a task is determined by the affinity of its root activity. The affinity determines two things — the task that the activity is re-parented to (see the allowTaskReparenting attribute) and the task that will house the activity when it is launched with the FLAG_ACTIVITY_NEW_TASK flag.
By default, all activities in an application have the same affinity. You can set this attribute to group them differently, and even place activities defined in different applications within the same task. To specify that the activity does not have an affinity for any task, set it to an empty string.
If this attribute is not set, the activity inherits the affinity set for the application (see the <application> element’s taskAffinity attribute). The name of the default affinity for an application is the package name set by the <manifest> element.
下面是我的翻譯,如有不當(dāng)洗出,歡迎指出:
activity的任務(wù)棧的相關(guān)性士复。擁有相同affinity的activity在概念上屬于同一個task。一個task的affinity取決于這個task內(nèi)的根activity的taskaffinity翩活。taskaffinity屬性能決定兩件事: ①.當(dāng)activity被re-parent時阱洪,他會被放到哪個任務(wù)棧中. ②.當(dāng)此activity被添加 FLAG_ACTIVITY_NEW_TASK 標(biāo)記啟動的時候,會被放入哪個task中菠镇。 默認情況下冗荸,application中所有的activity擁有相同的affinity,你可以通過給taskaffinity屬性設(shè)置不同的值把他們分組辟犀。甚至可以把多個application中的activity放到同一個task中俏竞。如果想明確這個activity不屬于任何task绸硕,把這個屬性設(shè)置為空字符即可。 如果這個屬性沒有被設(shè)置魂毁,那么此屬性的值就繼承自application的此屬性的值(查看 application的taskaffinity屬性)玻佩。默認的值為application的包名。
看完上邊官方文檔的解釋席楚,我想你對taskAffinity屬性已經(jīng)有了理解了咬崔,總結(jié)起來暫時有這兩方面: 1.taskAffinity屬性能夠給activity指定task,但必須使用FLAG_ACTIVITY_NEW_TASK 標(biāo)記烦秩。 2.默認的taskAffinity的值是應(yīng)用的包名垮斯。
下邊我們寫一個demo演示一下: 這是我得AndroidManifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="example.ylh.com" >
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activityDemo.ActivityA"></activity>
<activity android:name=".activityDemo.ActivityB"
android:taskAffinity="example.ylh.com_new_task01"></activity>
<activity android:name=".activityDemo.ActivityC"
android:taskAffinity="example.ylh.com_new_task02"
android:launchMode="singleTask"></activity>
</application>
</manifest>
從上面的代碼,一共有四個activity只祠,啟動順序是
MainAcitivity->ActivityA->ActivityB->ActivityC
其中ActivityC加了singleTask啟動模式兜蠕,因為singleTask模式啟動時會給intent添加 FLAG_ACTIVITY_NEW_TASK 標(biāo)記(可以認為FLAG_ACTIVITY_NEW_TASK和singleTask作用相同,當(dāng)啟動模式為singleTask時抛寝,framework會將它的啟動標(biāo)志設(shè)為FLAG_ACTIVITY_NEW_TASK)熊杨。
ActivityA的代碼:
package example.ylh.com.activityDemo;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import example.ylh.com.R;
/**
* Created by Administrator on 2017/8/4.
*/
public class ActivityA extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
TextView tv = (TextView) findViewById(R.id.textview);
tv.setText("A");
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(ActivityA.this,ActivityB.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
});
}
}
ActivieyB代碼:
package example.ylh.com.activityDemo;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import example.ylh.com.R;
/**
* Created by Administrator on 2017/8/4.
*/
public class ActivityB extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
TextView tv = (TextView) findViewById(R.id.textview);
tv.setText("B");
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(ActivityB.this,ActivityC.class);
startActivity(i);
}
});
}
}
依次啟動后,從adb看到activity和棧的詳細信息如下: 可以清楚的看到盗舰,其中包含了三個任務(wù)棧:
第一個:棧名:example.ylh.com晶府,棧id:8904,包含MainActivity和ActivityA钻趋。
第二個:棧名:example.ylh.com_task01川陆,棧id:8905,包含ActivityB蛮位。
第三個:棧名:example.ylh.com_task02较沪,棧id:8906,包含ActivityC土至。 結(jié)果符合我們的預(yù)期购对。到此,我們可以得出結(jié)論陶因,使用taskAffinity和FLAG_ACTIVITY_NEW_TASK(或singleTask),可以做到給我們的activity指定相應(yīng)的任務(wù)棧骡苞。
allowTaskReparenting屬性
這個屬性解釋起來麻煩了點,但是很有意思楷扬。
官方api這樣解釋的:
Whether or not the activity can move from the task that started it to the task it has an affinity for when that task is next brought to the front — "true" if it can move, and "false" if it must remain with the task where it started.
大概意思是說解幽,如果設(shè)為“true”,那么activity可以從啟動它的task中移動到和此activity相關(guān)的task中。聽完我這個解釋烘苹,你估計還是不明白躲株,
舉個例子吧:
有A和B兩個應(yīng)用,B應(yīng)用有一個activityC镣衡,并且activityC的allowTaskReparenting屬性為true∷ǎ現(xiàn)在有這樣一個場景档悠,A啟動了B的activityC,然后點擊home鍵回到桌面望浩,在啟動B應(yīng)用辖所,這個時候不是啟動B應(yīng)用的mainActivity,而是重新顯示了activityC磨德,activityC從A的任務(wù)棧轉(zhuǎn)移到了B的任務(wù)棧(因為和activityC相關(guān)的task就是appB的task缘回,所以把activityC加到棧頂)。
下邊是具體代碼:
應(yīng)用A的activityA:
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import example.ylh.com.R;
/**
* Created by Administrator on 2017/8/4.
*/
public class ActivityA extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
TextView tv = (TextView) findViewById(R.id.textview);
tv.setText("A");
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent();
i.setClassName("ylh.bsdf.com","ylh.bsdf.com.ActivityC");
startActivity(i);
}
});
}
}
應(yīng)用B的activityC:
package ylh.bsdf.com;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
/**
* Created by Administrator on 2017/8/12.
*/
public class ActivityC extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.tv);
tv.setText("app B activityC");
}
}
應(yīng)用B的AndoridManifest文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ylh.bsdf.com">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ActivityC"
android:allowTaskReparenting="true">
</activity>
</application>
</manifest>
按照上邊的邏輯啟動后(activityA(appA)->activityC(appB)->home->appB)典挑,adb的堆棧情況打印如下:
正好符合我們的預(yù)期酥宴。
本篇到這里就結(jié)束了,如果從頭看到尾你應(yīng)該理解的很透徹了您觉,哪里不理解的話自己寫一個小demo測試一下(這很重要)拙寡。
參考資料:
《android開發(fā)藝術(shù)探索》
android官方文檔
http://blog.csdn.net/zhangjg_blog/article/details/10923643