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键,容易搞乱,这里我们单独讲一下。首先遵循的是这样一个规则:出栈的时候,先让当前栈(前台栈)清空,再去清空后台栈。

举个例子:
Activity启动模式,任务栈以及taskAffinity属性_第1张图片

进入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。
没错就是这样,前台栈清空后,再去清空后台栈。

taskAffinity属性详解
任务栈基本就是这样,但是当我们想自己手动给一个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 element’s taskAffinity attribute). The name of the default affinity for an application is the package name set by the element.

下面是我的翻译,如有不当,欢迎指出:
taskAffinity是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:

"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
    package="example.ylh.com" >

    "android.permission.WRITE_EXTERNAL_STORAGE"/>
    "android.permission.READ_EXTERNAL_STORAGE"/>
    "android.permission.SYSTEM_ALERT_WINDOW"/>

    "true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        ".MainActivity" >
            
                "android.intent.action.MAIN" />
                "android.intent.category.LAUNCHER" />
            
        

        ".activityDemo.ActivityA">

        ".activityDemo.ActivityB"
            android:taskAffinity="example.ylh.com_new_task01">

        ".activityDemo.ActivityC"
            android:taskAffinity="example.ylh.com_new_task02"
            android:launchMode="singleTask">
    

从上面的代码,一共有四个activity,启动顺序是
MainAcitivity->ActivityA->ActivityB->ActivityC
其中ActivityC加了singleTask启动模式,因为singleTask模式启动时会给intent添加
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启动模式,任务栈以及taskAffinity属性_第2张图片

可以清楚的看到,其中包含了三个任务栈:
第一个:栈名: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文件:


<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的堆栈情况打印如下:
Activity启动模式,任务栈以及taskAffinity属性_第3张图片
正好符合我们的预期。
本篇到这里就结束了,如果从头看到尾你应该理解的很透彻了,哪里不理解的话自己写一个小demo测试一下(这很重要)。
参考资料:
《android开发艺术探索》
android官方文档
http://blog.csdn.net/zhangjg_blog/article/details/10923643

你可能感兴趣的:(android,进阶)