什么是任务栈?
一个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键,容易搞乱,这里我们单独讲一下。首先遵循的是这样一个规则:出栈的时候,先让当前栈(前台栈)清空,再去清空后台栈。
举个例子:
可以清楚的看到,其中包含了三个任务栈:
第一个:栈名: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
网友评论