activity启动模式,任务栈和taskAffinity

什么是任务栈?

一个application在启动的时候,能有很多个activity,我们在按下back键的时候,会回退到上一个activity,那么系统是如何来管理这些activity呢?答案是以栈(task)的形式,遵循先进后出的原则,默认情况下,一个app只有一个任务栈(task),如果需要,我们可以指定多个任务栈(task)。我们可以总结出以下几点:

1.任务栈是app管理activity的一种容器,遵循先进后出原则

2.一个app默认只有一个任务栈,由系统指定

3.一个app可以有多个任务栈,需要我们自己指定

(以下任务栈全部称为task)

那么,我们如何给一个activity指定特定的task,指定特定的task有什么用呢?想要搞清楚这个,我们就需要了解activity的启动模式了。activity的启动模式一共有四种,分别是standard , singleTop , singleTask , singleInstance;

standard:

这是默认的启动模式,每次启动一个activity,都会一个一个的添加到当前的task中去。为什么是当前的呢,我们知道一个app可能有多个task,假如现在有两个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,那么系统就会报错:

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在企图进入当前的task的时候,发现context(applicationContext)不属于任何task,无法进入。解决办法就是新建一个task,错误信息已经说得很清楚了,具体代码如下:

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

singleTop:

顾名思义,如果activity已经位于task的顶端,那么当再次启动这个activity的时候,他不会被重新创建,而是调用它的onNewIntent(Intent i)方法,想要传递的数据,可以在intent参数里得到,onCreate,onStart不会调用。如果这个activity不在task顶,那么activity的存放方式和standard一样,会被重新创建,累加到task上。

singleTask:

栈内复用模式。如果activity在task里存在,并且此activity是singleTask模式,多次启动此activity都不会重新创建实例,而是回调onNewIntent(Intent i) 方法。具体来说,当activityA作为singleTask模式启动的时候,系统会检测,有没有相应的task,如果没有,就创建一个task,然后把activityA放入。如果存在相应的task,就会检查activityA是否位于栈顶,如果位于栈顶,就直接调用onNewIntent方法,如果不是,则activityA上边所有的activity出栈,然后调用onNewIntent方法。

singleInstance:

此模式的activity,独享一个task,也就是说这个task只能有这一个activity。启动的时候,会为此activity创建一个task,并把此activity压栈,如果在此activity还在栈中的时候,再次启动此activity,那么不会调用此activity的onCreate方法,而是调用onNewIntent和onStart方法。这种模式的使用场景是:假设程序中有一个活动是允许其它程序调用的,如果想使其它程序和这个程序共享这个活动的实例,使用其它三种启动模式是不行的,因为每个应用程序都有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用同一个返回栈,也解决了共享活动实例的问题。关于四种启动模式,基本解释清楚了,但是具体使用的时候,多个任务栈的情况下,按下back键,容易搞乱,这里我们单独讲一下。首先遵循的是这样一个规则:出栈的时候,先让当前栈(前台栈)清空,再去清空后台栈。

举个例子:


进入app,启动activityA,然后activityA->activityB->activityC->activityD,

如果activityA,activityB和activityD是standard模式,activityC是singleInstance模式,

那么此时此时app内有两个task(暂且叫做taskA和taskB)。
两个个task的包含分别是如下:
taskA:activityA,activityB,activityD。
taskB:activityC。
此时位于前台的task是taskA,后台的是taskB。按下四次back键,依次销毁的是activityD->activityB->activityA->activityC。
没错就是这样,前台栈清空后,再去清空后台栈。

任务栈基本就是这样,但是当我们想自己手动给一个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.

下面是我的翻译,如有不当,欢迎指出:
activity的任务栈的相关性。拥有相同affinity的activity在概念上属于同一个task。一个task的affinity取决于这个task内的根activity的taskaffinity。taskaffinity属性能决定两件事: ①.当activity被re-parent时,他会被放到哪个任务栈中. ②.当此activity被添加 FLAG_ACTIVITY_NEW_TASK 标记启动的时候,会被放入哪个task中。 默认情况下,application中所有的activity拥有相同的affinity,你可以通过给taskaffinity属性设置不同的值把他们分组。甚至可以把多个application中的activity放到同一个task中。如果想明确这个activity不属于任何task,把这个属性设置为空字符即可。 如果这个属性没有被设置,那么此属性的值就继承自application的此属性的值(查看 application的taskaffinity属性)。默认的值为application的包名。
看完上边官方文档的解释,我想你对taskAffinity属性已经有了理解了,总结起来暂时有这两方面: 1.taskAffinity属性能够给activity指定task,但必须使用FLAG_ACTIVITY_NEW_TASK 标记。 2.默认的taskAffinity的值是应用的包名。
下边我们写一个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 标记(可以认为FLAG_ACTIVITY_NEW_TASK和singleTask作用相同,当启动模式为singleTask时,framework会将它的启动标志设为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

可以清楚的看到,其中包含了三个任务栈:
第一个:栈名:example.ylh.com,栈id:8904,包含MainActivity和ActivityA。
第二个:栈名:example.ylh.com_task01,栈id:8905,包含ActivityB。
第三个:栈名:example.ylh.com_task02,栈id:8906,包含ActivityC。 结果符合我们的预期。到此,我们可以得出结论,使用taskAffinity和FLAG_ACTIVITY_NEW_TASK(或singleTask),可以做到给我们的activity指定相应的任务栈。

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.

大概意思是说,如果设为“true”,那么activity可以从启动它的task中移动到和此activity相关的task中。听完我这个解释,你估计还是不明白,
举个例子吧:
有A和B两个应用,B应用有一个activityC,并且activityC的allowTaskReparenting属性为true。现在有这样一个场景,A启动了B的activityC,然后点击home键回到桌面,在启动B应用,这个时候不是启动B应用的mainActivity,而是重新显示了activityC,activityC从A的任务栈转移到了B的任务栈(因为和activityC相关的task就是appB的task,所以把activityC加到栈顶)。

下边是具体代码:

应用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);
            }
        });
    }
}

应用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");
    }
}

应用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的堆栈情况打印如下:


正好符合我们的预期。
本篇到这里就结束了,如果从头看到尾你应该理解的很透彻了,哪里不理解的话自己写一个小demo测试一下(这很重要)。
参考资料:
《android开发艺术探索》
android官方文档
http://blog.csdn.net/zhangjg_blog/article/details/10923643

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容