关于任务栈Task
栈的概念
栈
(Stack)
是一种常用的数据结构,栈只允许访问栈顶的元素,栈就像一个杯子,每次都只能取杯子顶上的东西,而对于栈就只能每次访问它的栈顶元素,从而可以达到保护栈顶元素以下的其他元素.”先进后出”或”后进先出”就是栈的一大特点,先进入栈的元素总是要等到后进入栈的元素出栈以后才能出栈.递归就是利用到了系统栈,暂时保存临时结果,对临时结果进行保护.
栈的基本操作:压栈、弹栈
任务栈
Task简单的说就是一组以栈的模式聚集在一起的Activity组件集合,类似于一个填充了Activity的容器,最先加入的Activity会处于容器最下面,最后加入的处于容器最上面,而从Task中取出Activity时是从最顶端先取出,最后取出的是最开始添加Activity,这就是后进先出
模式,而Activity在Task中的顺序是可以控制的,在Activity跳转时用到Intent Flag可以设置新建activity的创建方式
。
Activity的启动模式
Activity启动
模式
有四种,
可以根据实际的需求为Activity设置对应的启动模式,从而可以避免创建大量重复的Activity等问题。
设置Activity的启动模式,只需要在AndroidManifest.xml里对应的<activity>标签设置Android:launchMode属性。
standard
默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。
例如:
若我有一个Activity名为A1, 上面有一个按钮可跳转到A1。那么如果我点击按钮,便会新启一个Activity A1叠在刚才的A1之上,再点击,又会再新启一个在它之上。
点back键会依照栈顺序依次退出。
singleTop
可以有多个实例,但是不允许多个相同Activity叠加。即,如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法。
singleTask
只有一个实例。
如果在同一个应用程序中启动它,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。
如果是在别的应用程序中启动它,则会新建一个task,并在该task中启动这个Activity。
singleTask允许别的Activity与其在一个task中共存,也就是说,如果我在这个singleTask的实例中再打开新的Activity,这个新的Activity还是会在singleTask的实例的task中。
可以使用
singleTask
来退出整个应用。
将主Activity设为SingTask模式,然后在要退出的Activity中转到主Activity,然后重写主Activity的onNewIntent函数,并在函数中加上一句finish。
singleInstance
只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。
Intent中常用的Flag
Flag表示Intent的标志位,常用于Activity的场景中,它和Activity的启动模式有着密切的联系。
下面列举的是和Activity启动模式相关的Flag属性:
Intent.FLAG_ACTIVITY_NEW_TASK
系统会检查当前所有已创建的Task中是否有该要启动的Activity的Task,若有,则在该Task上创建Activity;若没有则新建具有该Activity属性的Task,并在该新建的Task上创建Activity。
FLAG_ACTIVITY_SINGLE_TOP
这个FLAG就相当于启动模式中的singletop
比如说原来栈中情况是ABCD,在D中启动D,栈中的情况还是ABCD
FLAG_ACTIVITY_CLEAR_TOP
这个FLAG就相当于启动模式中的SingleTask,这种FLAG启动的Activity会把要启动的Activity之上的Activity全部弹出栈空间。
比如:原来栈中的情况是ABCD,这个时候从D中跳转到B,这个时候栈中的情况就是AB了
FLAG_ACTIVITY_NO_HISTORY
这个标记顾名思义意思就是说,用这个FLAG启动的Activity,一旦【不可见】,他就不会存在于任务栈中。
比如,原来是ABC,这个时候在C中以这个FLAG启动D的 ,D再启动E,这个时候栈中情况为ABCE。
FLAG_ACTIVITY_BROUGHT_TO_FRONT
比方说我现在在A中启动B,启动时在A中的Intent加上这个标记,此时在B中再启动CD(正常启动CD),此时栈的情况是
ABCD。
如果这个时候在D中再启动B,这个时候栈的情况是 ACDB。
特别注意的是,我上面说的网上人描述的这个FLAG,会很容易让人误解成这样,A,B,C,D都是标准加载,然后我在D中启动A,这个intent加上FLAG_ACTIVITY_BROUGHT_TO_FRONT ,就会误认为变成B,C,D,A!!其实不是,这个时候应该是A,B,C,D,A.不信的人大家试试看。不过下面这个标记和这个标记就会让大家明白了!
FLAG_ACTIVITY_REORDER_TO_FRONT
就按在 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT 最后说的,如果在A,B,C,D正常启动的话,不管B有没有用FLAG_ACTIVITY_BROUGHT_TO_FRONT启动,此时在D中启动B的话,还是会变成A,C,D,B的。
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
这个也不知道具体怎么用,看文档有点象开辟新的Task,不过测试完,不像那么简单。
FLAG_ACTIVITY_NO_USER_ACTION
onUserLeaveHint()作为activity周期的一部分,它在activity因为用户要跳转到别的activity而要退到background时使用。
比如,在用户按下Home键(用户的choice),它将被调用。比如有电话进来(不属于用户的choice),它就不会被调用。
那么系统如何区分让当前activity退到background时使用是用户的choice?
它是根据促使当前activity退到background的那个新启动的Activity的Intent里是否有 FLAG_ACTIVITY_NO_USER_ACTION来确定的。
注意:通过调用finish()使该activity销毁时不会调用该函数
关于taskAffinity属性
清单文件中,activity 的属性
android:allowTaskReparenting
用于设定:
Activity是
否能够从启动它的任务栈中转移到另一个与启动它的任务栈有相同taskAffinity属性值的任务栈中
,转移时机是在另一个
任务栈
被带到前台的时候。如果设置为true,则能够转移,如果设置了false,则这个Activity必须要保留在启动它的那个
任务栈
中。
实验
1、新建两个工程,App1和App2
App1和App2都设置android:taskAffinity="aaa.aaa"android:allowTaskReparenting="true"
先运行App1,然后点击home键,让App1运行在后台
再运行App2,会发现这时显示的是App1的mainActivity,并且长按home键,会发现运行过的程序只有App1。
2、紧接着又在此基础上做了另外一个实验
在App1上新建一个secondActivity,设置android:taskAffinity="aaa.bbb"android:allowTaskReparenting="true"
在mainActivity中startActivity时,设置Intent中flag属性为FLAG_ACTIVITY_NEW_TASK,注意,这里的flag属性必须为newtask。
然后运行App1,点击进入secondActivity,这时长按home键,会发现运行过的程序中有两个App1,并且一个显示的是mainActivity另一个显示的是secondActivity。
这时点击home键,让程序回到后台
然后运行App2,会发现这时显示的是App1的mainActivity,此时点击返回会直接返回home。
注意,虽然此时App1的mainActivity被finish了,但App1的secondActivity还在后台等着呢。
同样,长按home键,会发现运行过的程序只有App1的两个任务栈,并没有App2。
3、在此基础上堆App1再次修改
在App1上新建一个thirdActivity,设置属性android:taskAffinity="aaa.aaa"android:allowTaskReparenting="true"
并在secondActivity中startActivity时,设置Intent中flag属性为FLAG_ACTIVITY_NEW_TASK;
运行App1,点击进入secondActivity,再进入thirdActivity,此时长按home键,会发现运行过的程序中有两个App1,并且一个显示的是secondActivity另一个显示的是thirdActivity。
此时点击返回,会回到mainActivity,再点击返回,会回到secondActivity,再点击返回,回到home页面。
以上实验中,如果startActivity时不设置Intent中flag属性为FLAG_ACTIVITY_NEW_TASK,则若先启动的是App1,那么不管是App1中的Activity还是App2中的mainActivity,都是运行App1的一个任务栈中。
演示代码
public class FirstActivity extends ListActivity {
private List<String> mData;//数据
private ListAdapter mAdapter;//适配器
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(this, "onCreate,任务栈id:" + getTaskId(), Toast.LENGTH_SHORT).show();
mData = new ArrayList<String>(Arrays.asList("启动第一个Activity", "启动第二个Activity", "====这是第一个Activity,任务栈id:" + getTaskId()));
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mData);
setListAdapter(mAdapter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
startActivity(new Intent(this, FirstActivity.class));
break;
case 1:
Intent intent = new Intent(this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//在这里设置连个卵用都没有!必须在清单文件中设置android:launchMode属性!
startActivity(intent);
break;
}
}
protected void onNewIntent(Intent intent) {
Toast.makeText(this, "onNewIntent,任务栈id:" + getTaskId(), Toast.LENGTH_SHORT).show();
super.onNewIntent(intent);//父类的此方法是空实现
}
// 利用时间差方法完成两次返回键退出,防止误操作。
private long currentBackPressedTime = 0;
/**退出间隔*/
public static final int BACK_PRESSED_INTERVAL = 2000;
@Override
//重写onBackPressed()方法,父类默认实现方式为调用 finish()方法
public void onBackPressed() {
// 判断时间间隔
if (System.currentTimeMillis() - currentBackPressedTime > BACK_PRESSED_INTERVAL) {
currentBackPressedTime = System.currentTimeMillis();
Toast.makeText(this, "再按一次返回键退出", Toast.LENGTH_SHORT).show();
} else {
finish();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 是否触发按键为back键,如果不是正常响应。onKeyDown的优先级比onBackPressed高
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}
}
public class SecondActivity extends ListActivity {
private List<String> mData;//数据
private ListAdapter mAdapter;//适配器
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(this, "onCreate,任务栈id:" + getTaskId(), Toast.LENGTH_SHORT).show();
mData = new ArrayList<String>(Arrays.asList("启动第一个Activity", "启动第二个Activity", "====这是第二个Activity,任务栈id:"+getTaskId()));
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mData);
setListAdapter(mAdapter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
startActivity(new Intent(this, FirstActivity.class));
break;
case 1:
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
break;
}
}
protected void onNewIntent(Intent intent) {
Toast.makeText(this, "onNewIntent,任务栈id:" + getTaskId(), Toast.LENGTH_SHORT).show();
super.onNewIntent(intent);//父类的此方法是空实现
}
}