转载请注明出处(谢谢):
http://blog.csdn.net/javazejian/article/details/52071885
任务栈简单入门
最近又把两本进阶书看了一遍,但总感觉好记性不如烂笔头,所以还是决定通过博客记录一下,我们将分两篇来全面深入地记录Activity 启动模式与任务栈的内容。
android任务栈简单了解
1. android任务栈又称为Task,它是一个栈结构,具有后进先出的特性,用于存放我们的Activity组件。
2. 我们每次打开一个新的Activity或者退出当前Activity都会在一个称为任务栈的结构中添加或者减少一个Activity组件,因此一个任务栈包含了一个activity的集合, android系统可以通过Task有序地管理每个activity,并决定哪个Activity与用户进行交互:只有在任务栈栈顶的activity才可以跟用户进行交互。
3. 在我们退出应用程序时,必须把所有的任务栈中所有的activity清除出栈时,任务栈才会被销毁。当然任务栈也可以移动到后台, 并且保留了每一个activity的状态. 可以有序的给用户列出它们的任务, 同时也不会丢失Activity的状态信息。
4. 需要注意的是,一个App中可能不止一个任务栈,某些特殊情况下,单独一个Actvity可以独享一个任务栈。还有一点就是一个Task中的Actvity可以来自不同的App,同一个App的Activity也可能不在一个Task中。
嗯,目前android任务栈的概念我们就大概了解到这。下面我们主要还是来聊聊android的4种启动模式。
Activity的启动模式
为什么需要Activity的启动模式?
我们在开发项目的过程中,一般都需要在本应用中多个Activity组件之间的跳转,也可能需要在本应用中打开其它应用的可复用的Activity。如我们可能需要跳转到原来某个Activity实例,此时我们更希望这个Activity可以被重用而不是创建一个新的 Activity,但根据Android系统的默认行为,确实每次都会为我们创建一个新的Activity并添加到Task中,这样android系统是不是很傻?还有一点就是在我们每开启一次页面加入到任务栈Task中后,一个Activity的数据和信息状态都将会被保留,这样会造成数据冗余, 重复数据太多, 最终还可能导致内存溢出的问题(OOM)。为了解决这些问题,android系统提供了一套Activity的启动模式来修改系统Activity的默认启动行为。目前启动模式有四种,分别是standard,singleTop,singTask和singleInstance,接下来我们将分别介绍这四种模式。
Activity的4种启动模式
- Standard 模式
又称为标准模式,也是系统的默认模式(可以不指定),在这样模式下,每启动一个Activity都会重新创建一个Activity的新实例,并且将其加入任务栈中,而且完全不会去考虑这个实例是否已存在。我们通过图解来更清晰地了解Standard模式:
通过上图,我们可以发现,这个过程中,在standard模式下启动了三次MainActivity后,都生成了不同的新实例,并添加到同一个任务栈中。这个时候Activity的onCreate、onStart、onResume方法都会被调用。
- singleTop 模式
又称栈顶复用模式,顾名思义,在这种模式下,如果有新的Activity已经存在任务栈的栈顶,那么此Activity就不会被重新创建新实例,而是复用已存在任务栈栈顶的Activity。这里重点是位于栈顶,才会被复用,如果新的Activity的实例已存在但没有位于栈顶,那么新的Activity仍然会被重建。需要注意的是,Activity的onNewIntent方法会被调用,方法原型如下:
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
通过此方法的参数,我们可以获取当前请求的相关信息,此时Activity的onCreate、onStart方法不会被调用,因为Activity并没有被重建。同理,我们通过图解来协助我们更清晰的理解singleTop模式:
从上图我们可以看出,当需要新创建的MainActivity位于栈顶时,MainActivity并没有重新创建。下面我们再来看看新创建的MainActivity没有位于栈顶的情况。
嗯,这就是singTop模式。这种模式通常比较适用于接收到消息后显示的界面,如qq接收到消息后弹出Activity界面,如果一次来10条消息,总不能一次弹10个Activity,是吧?再比如新闻客户端收到了100个推送,你每次点一下推送他都会进入某个activiy界面(显示新闻只用一个activity,只是内容不同而已),这时也比较适合使用singleTop模式。
- singleTask 模式
又称为栈内复用模式。这是一种单例模式,与singTop点类似,只不过singTop是检测栈顶元素是否有需要启动的Activity,而singTask则是检测整个栈中是否存在当前需要启动的Activity,如果存在就直接将该Activity置于栈顶,并将该Activity以上的Activity都从任务栈中移出销毁,同时也会回调onNewIntent方法。情况如下图:
从图中可以看出,当我们再次启动MainActivity时,由于MainActivity位于栈中,所以系统直接将其置于栈顶,并移除其上方的所有Activity。当然如果所需要的MainActivity不存在栈中,则会创建新的Activity并添加到栈中。singleTask 模式比较适合应用的主界面activity(频繁使用的主架构),可以用于主架构的activity,(如新闻,侧滑,应用主界面等)里面有好多fragment,一般不会被销毁,它可以跳转其它的activity 界面再回主架构界面,此时其他Activity就销毁了。当然singTask还有一些比较特殊的场景这个我们后面会一一通过情景代码分析。
- singleInstance 模式
在singleInstance模式下,该Activity在整个android系统内存中有且只有一个实例,而且该实例单独尊享一个Task。换句话说,A应用需要启动的MainActivity 是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A单独在这个新的任务栈中,如果此时B应用也要激活MainActivity,由于栈内复用的特性,则不会重新创建,而是两个应用共享一个Activity的实例。如下图所示:
从图中我们可以看到最终AB应用都共享一个singleInstance模式的MainActivity,也没有去重新创建。到此Activity的四种启动模式我们都介绍完了,下面我们接着来聊聊怎么使用启动模式。
Activity启动模式的使用方式
前面我们说了那么多,那么我们该如何给Activity指定启动模式呢?事实上共有如下两种方式:
1.通过AndroidMenifest.xml文件为Activity指定启动模式,代码如下:
<activity android:name=".ActivityC"
android:launchMode="singleTask" />
2.通过在Intent中设置标志位(addFlags方法)来为Activity指定启动模式,示例代码如下:
Intent intent = new Intent();
intent.setClass(ActivityB.this,ActivityA.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
那么标志位是是什么呢?接下来我们就来了解一些常用的标志位
Intent Flag 启动模式
这里我们主要介绍一下一些常用的Activity的Flag,因为Activity的Flag比较多,我们知道一些常用的就够了,遇到比较特殊的还是查查官网文档吧。
- Intent.FLAG_ACTIVITY_NEW_TASK
该标志位表示使用一个新的Task来启动一个Activity,相当于在清单文件中给Activity指定“singleTask”启动模式。通常我们在Service启动Activity时,由于Service中并没有Activity任务栈,所以必须使用该Flag来创建一个新的Task。我们来重现一下这个错误,创建一个Service服务,并在onCreate方法中启动Activity,代码如下:
public class ServiceT extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Intent i =new Intent(getApplicationContext(),ActivityD.class);
startActivity(i);
}
}
启动应用并启动Service服务,后报错如下:
从异常信息我们可以看出,提示我们添加Intent.FLAG_ACTIVITY_NEW_TASK
标志位,所以我们代码必须改成如下:
public class ServiceT extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Intent i =new Intent(getApplicationContext(),ActivityD.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
}
Intent.FLAG_ACTIVITY_SINGLE_TOP
该标志位表示使用singleTop模式来启动一个Activity,与在清单文件指定android:launchMode="singleTop"
效果相同。Intent.FLAG_ACTIVITY_CLEAR_TOP
该标志位表示使用singleTask模式来启动一个Activity,与在清单文件指定android:launchMode="singleTask"
效果相同。Intent.FLAG_ACTIVITY_NO_HISTORY
使用该模式来启动Activity,当该Activity启动其他Activity后,该Activity就被销毁了,不会保留在任务栈中。如A-B,B中以这种模式启动C,C再启动D,则任务栈只有ABD。Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
使用该标识位启动的Activity不添加到最近应用列表,也即我们从最近应用里面查看不到我们启动的这个activity。与属性android:excludeFromRecents="true"
效果相同。
启动模式中singleTask的特殊情景
前面我们在分析singleTask模式时,提到过singleTask模式有些比较特殊的场景,现在我们就来了解了解它们。
特殊情景一:现在我们假设有如下两个Task栈,分别为前台任务栈和后台任务栈
从图中我们看出前台任务栈分别为AB两个Activity,后台任务栈分别为CD两个任务栈,而且其启动模式均为singleTask,此时我们先启动CD,然后再启动AB,再有B启动D,此时后台任务栈便会被切换到前台,而且这个时候整个后退列表就变成了ABCD,请注意我们这里强调的是后退列表,而非栈合并。因此当用户点击back键时,列表中的Activity会依次按DCBA顺序出栈,如下图所示:
这里我们通过两个应用ActivityTask和ActivityTask2来测试重现这个现象。因为两个是不同的应用所以启动时所在的栈也是不同。我们先启动ActivityTask2的应用,其ActivityC和ActivityD都是singleTask模式,然后再启动应用ActivityTask,此时ActivityC和ActivityD所在任务栈会被退居后台,而打开的ActivityA和ActivityB会在前台,而且都是默认模式。我们通过 adb shell dumpsys activity activities
命令查看此时栈的情况:
我们可以看到由两个栈,分别为id=222且栈名为“com.cmcm.activitytask”的任务栈其包含ActivityA和ActivityB(下面简称AB,栈名一般默认和包名相同),另外一个任务栈,id=221,栈名为“com.cmcm.activitytask2”,其包含ActivityC和ActivityD(下面检测CD)。现在我们通过ActivityB去启动ActivityD,然后按back键回退。B调用D代码如下:
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
/**
* Created by zejian
* Time 16/7/23.
* Description:
*/
public class ActivityB extends Activity {
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
btn= (Button) findViewById(R.id.main);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
ComponentName cn = new ComponentName("com.cmcm.activitytask2", "com.cmcm.activitytask2.ActivityD");
intent.setComponent(cn);
startActivity(intent);
}
});
}
}
运行结果如下:
我们可以看到包含CD的任务栈被提前的,虽然CD隔开了,但是我们从id和栈名可以发现他们是同一个栈,而AB所在的栈则在CD所在栈的后面,所以此时我们按back回退时,退出顺序是这样的D->C->B->A,动态图如下:
到这里我们就应该更加清晰的了解情景一的现象了。了解这点有什么用呢,这可以使用我们更好地去管理我们的任务栈,而不会导致栈混乱是进入一些用户本来就不需要界面,影响用户体验。
特殊情景二:
如果上面B不是请求启动D而是请求启动C,那么又会是什么情况呢?其实这个时候任务栈退出列表变成C->B->A,其实原因很简单,singleTask模式的ActivityC切换到栈顶时会导致在他之上的栈内的Activity出栈。同样我们还是使用上面的代码,把B启动D改为B启动C,那么此时B未启动C时任务栈的情况如下:
我们仍然可以看到两个任务栈,分别为id=242,栈名“com.cmcm.activitytask”的Task,包含ActivityA和ActivityB;id=241,栈名“com.cmcm.activitytask2”的Task,包含ActivityC和ActivityD。此时我们通过B启动C后栈的情况变成如下情况
因此,栈的退出列表就变成了C->B->A了,如下图所示:
动态图如下:
到此我们对SingleTask模式又有了更深入的理解,但是我们发现上面的例子使用的是两个应用,所以才会有不同的任务栈,那么我们能不能在一个应用中存在多个不同的任务栈呢(暂时不考虑singleInstance 模式)?答案当然是肯定的啦,这就需要通过taskAffinity属性来设置不同的任务栈名称,不过这点将放在下篇来记录,本篇就先到这里告一段落哈。