activity啟動模式窝剖,任務(wù)棧和taskAffinity

什么是任務(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和棧的詳細信息如下:
running activities

可以清楚的看到盗舰,其中包含了三個任務(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市顾犹,隨后出現(xiàn)的幾起案子倒庵,更是在濱河造成了極大的恐慌,老刑警劉巖炫刷,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異郁妈,居然都是意外死亡浑玛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門噩咪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顾彰,“玉大人,你說我怎么就攤上這事胃碾≌窍恚” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵仆百,是天一觀的道長厕隧。 經(jīng)常有香客問我,道長俄周,這世上最難降的妖魔是什么吁讨? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮峦朗,結(jié)果婚禮上建丧,老公的妹妹穿的比我還像新娘。我一直安慰自己波势,他們只是感情好翎朱,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布橄维。 她就那樣靜靜地躺著,像睡著了一般拴曲。 火紅的嫁衣襯著肌膚如雪挣郭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天疗韵,我揣著相機與錄音兑障,去河邊找鬼。 笑死蕉汪,一個胖子當(dāng)著我的面吹牛流译,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播者疤,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼福澡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驹马?” 一聲冷哼從身側(cè)響起革砸,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糯累,沒想到半個月后算利,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡泳姐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年效拭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胖秒。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡缎患,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阎肝,到底是詐尸還是另有隱情挤渔,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布风题,位于F島的核電站判导,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏俯邓。R本人自食惡果不足惜骡楼,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稽鞭。 院中可真熱鬧鸟整,春花似錦、人聲如沸朦蕴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涉茧,卻和暖如春赴恨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伴栓。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工伦连, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钳垮。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓惑淳,卻偏偏與公主長得像昌屉,于是被迫代替她去往敵國和親涂圆。 傳聞我的和親對象是個殘疾皇子码泞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容