本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。
前言:
Activity 作为 Android 四大组件之一,几乎是被接触得最多的;Android对Activity的管理,Android采用Task来管理多个Activity,当我们启动一个应用时,Android就会为之创建一个Task,然后启动这个应用的入口Activity;
在开发实际项目中会包含着多个Activity,系统中使用任务栈来存储创建的Activity实例,任务栈是一种“后进先出”的栈结构。举个栗子,若我们多次启动同一个Activity,系统会创建多个实例依次放入任务栈中,当按back键返回时,每按一次,一个Activity出栈,直到栈空为止,当栈中;
无任何Activity,系统就会回收此任务栈;
因此在Android基础中,Activity的启动模式非常重要;
本文将全面详细介绍 Activity的启动模式
一、任务和任务栈详解
1、Android中任务详解
①任务是指在执行特定作业时与用户交互的一系列 Activity。这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷方式)时,该应用的任务将出现在前台。如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开;
②当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。因此,返回栈以“后进先出”对象结构运行;
③任务是一个有机整体,当用户开始新任务或通过“主页”按钮转到主屏幕时,可以移动到“后台”。尽管在后台时,该任务中的所有 Activity 全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅仅失去焦点而已。然后,任务可以返回到“前台”,用户就能够回到离开时的状态;
④由于返回栈中的 Activity 永远不会重新排列,因此如果应用允许用户从多个 Activity 中启动特定 Activity,则会创建该 Activity 的新实例并推入堆栈中(而不是将 Activity 的任一先前实例置于顶部)。因此,应用中的一个 Activity 可能会多次实例化(即使 Activity 来自不同的任务)。
2、任务栈
(1)程序打开时就创建了一个任务栈, 用于存储当前程序的activity,所有的activity属于一个任务栈。
(2)一个任务栈包含了一个activity的集合, 去有序的选择哪一个activity和用户进行交互:只有在任务栈栈顶的activity才可以跟用户进行交互。
(3)任务栈可以移动到后台, 并且保留了每一个activity的状态. 并且有序的给用户列出它们的任务, 而且还不丢失它们状态信息。
(4)退出应用程序时:当把所有的任务栈中所有的activity清除出栈时,任务栈会被销毁,程序退出。
(5)每开启一次页面都会在任务栈中添加一个Activity,而只有任务栈中的Activity全部清除出栈时,任务栈被销毁,程序才会退出,这样就造成了用,户体验差, 需要点击多次返回才可以把程序退出了。
(6)每开启一次页面都会在任务栈中添加一个Activity还会造成数据冗余, 重复数据太多, 会导致内存溢出的问题(OOM)。
为了解决任务栈的缺点,我们引入了启动模式。
启动模式(launchMode)在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里;
Activity 中有个启动模式的概念,分别是 standard、singleTop、singleTask 以及 singleinstance。
二、启动模式详解
1、standard
standard 是标准启动模式,当我们没有指定 Activity 的启动模式时,默认就是这种模式。在 standard 模式下,每次启动一个 Activity 都会创建一个新的实例,它的 onCreate、onStart 以及 onResume均会被调用。这个新创建的 Activity将会放在启动它的 Activity 所在的任务栈的栈顶。
比如 Activity A 在栈 S ,它启动了 Activity B(standard 模式),那么 B 将会进入 A 所在的栈 S。
如果在没有任务栈的情况下启动 standard 模式的 Activity,比如在 Service 中,此时新的 Activity 没有任务栈可入,会出现异常:
- Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
此时应该为这个 Activity 指定 FLAG_ACTIVITY_NEW_TASK,这样就会新建一个任务栈。
2、singleTop
singleTop 是栈顶复用模式。在这种模式下,如果新启动的 Activity 已经在任务栈的栈顶了,那么就不会重新创建新的实例,而是调用这个 Activity 的 onPause、onNewIntent 以及 onResume 方法。如果新启动的 Activity 不是位于栈顶,那么还是会重新创建。
比如现在栈内情况是 ABCD 四个Activity,A 位于栈底,D 位于栈顶。如果 D 的启动模式为 singleTop,那么不会再次创建 D 的实例,栈内依然是 ABCD。
如果上面的 D 为 standard 启动模式,那么栈内将变为 ABCDD。
3、singleTask
singleTask 是栈内复用模式。这是最复杂的一种模式,因为它可能涉及到多个栈。当一个具有 singleTask 模式的 Activity 启动后,比如 Activity A,系统会首先寻找是否存在所需的任务栈,如果不存在,就重新创建一个任务栈,然后创建 A 的实例后把 A 放入到栈中。如果存在 A 所需要的任务栈,这时要看 A 是否在栈中有实例存在,如果有,那么系统就会把它调到栈顶并且调用它的 onNewIntent 方法,如果不存在,就创建 A 的实例并把 A 压入栈中。这里所说的 A 所需要的任务栈是什么意思呢?其实 Activity 是可以指定自己想要的任务栈的名字的,通过一个参数:TaskAffinity,默认情况下,所有的 Activity 所需要的任务栈的名字为应用的包名。
如果任务栈 S1 中的情况为 ABC,这个时候 Activity D 以 singleTask 模式请求启动,它需要的任务栈为 S2,由于 S2 和 D 的实例均不存在,所以系统就会先创建任务栈 S2,然后在创建 D 的实例并将其入栈到 S2
如果上面 D 所需的任务栈为 S1,那么因为 S1 已经存在,所以系统直接创建 D 的实例并且入栈到 S1。
如果 D 所需的任务栈为 S1,但是 S1 中的情况为 ADBC,此时 D 不会重新创建,而是把 D 切换到栈顶并调用 onNewIntent 方法。那 B 和 C 怎么办?它们会全部出栈,相当于 clearTop 效果。
4、singleInstance
singleInstance 是单实例模式。这种模式是 singleTask 的加强版,它除了具有 singleTask 的所有特性外,还加强了一点,那就是此种模式的 Activity 只能单独位于一个任务栈中。
比如 Activity A 是 singleInstance 模式,当 A 启动后,系统会创建一个新的任务栈,然后 A 独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的 Activity,除非这个栈被销毁了;
三、启动模式设置详解
启动模式有2种设置方式:在AndroidMainifest设置、通过Intent设置标志位。
1、在AndroidMainifest的Activity配置进行设置
- <activity
- android:launchMode="启动模式"
- //属性
- //standard:标准模式
- //singleTop:栈顶复用模式
- //singleTask:栈内复用模式
- //singleInstance:单例模式
- //如不设置,Activity的启动模式默认为**标准模式(standard)**
- </activity>
2、通过Intent设置标志位
- Intent inten = new Intent (ActivityA.this,ActivityB.class);
- intent.addFlags(Intent,FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- FLAG_ACTIVITY_SINGLE_TOP:指定启动模式为栈顶复用模式(SingleTop)
- FLAG_ACTIVITY_NEW_TASK:指定启动模式为栈内复用模式(SingleTask)
- FLAG_ACTIVITY_CLEAR_TOP:所有位于其上层的Activity都要移除,SingleTask模式默认具有此标记效果;
- FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有该标记的Activity不会出现在历史Activity的列表中,即无法通过历史列表回到该Activity上;
3、二者区别
Intent设置方式的优先级 > Manifest设置方式,即 以前者为准;
Manifest设置方式无法设定 FLAG_ACTIVITY_CLEAR_TOP;Intent设置方式 无法设置单例模式(SingleInstance);
四、启动模式的实际应用场景
1. SingleTask模式的运用场景
最常见的应用场景就是保持我们应用开启后仅仅有一个Activity的实例。最典型的样例就是应用中展示的主页(Home页)。
假设用户在主页跳转到其他页面,运行多次操作后想返回到主页,假设不使用SingleTask模式,在点击返回的过程中会多次看到主页,这明显就是设计不合理了。
2. SingleTop模式的运用场景
假设你在当前的Activity中又要启动同类型的Activity,此时建议将此类型Activity的启动模式指定为SingleTop,能够降低Activity的创建,节省内存!
3.SingleInstance模式的运用场景
SingleInstance是activity启动的一种模式,一般做应用层开发很少用到,我一般用到的app定时提醒会用到这个模式吧。这个模式使用起来有很多坑,假设有activityA,activityB,activityC这三个activity,我们将activityB设置为SingleInstance
第一种情况
A开启B,B开启C,如果finish activityC,那么activityA会显示而不是我们想要的activityB,这是因为activityB和activityA、activityC所处的栈不同,C关闭了,就要显示C所处栈的下一个activity,解决这个问题办法很多,我自己用的方法是通过记录开启activity,在被关闭的activity的finish方法中重新开启activityB。
第二种情况
A开启B,然后按home键,再从左面点开应用,显示的是A,这是因为launch启动我们应用的时候 会从默认的栈找到栈顶的activity显示,这个解决办法的思路跟第一种差不多,也就不献丑了
第三种情况
A开启C,C开启B,B开启A,结果显示的是C,这还是两个栈造成的,B开启A的时候,其实是到达A所处的栈,栈顶是C,所以就显示C了,解决办法是用flag把默认栈activity清理了,重新开启A,或者回退到C时再开启A。
三种情况的解决方法都是基于页面少的情况,如果页面多了会产生更多的问题
为了必避免这个问题,最好不用在中间层使用SingleInstance
TIPS: (1)如果想让C和B同一个栈,那就使用taskinfinity,给他俩设置同样的栈名
(2)onActivityResult不能与SingleInstance不能一起使用,因为不同栈
4、standard 运用场景
Activity 的启动默认就是这种模式。在 standard 模式下,每次启动一个 Activity 都会创建一个新的实例;
在正常应用中正常打开和关闭页面就可以了,退出整个app就关闭所有的页面
5、Activity时的生命周期不同
由于当一个Activity设置了SingleTop或者SingleTask模式或者SingleInstance模式后,跳转此Activity出现复用原有Activity的情况时,此Activity的onCreate方法将不会再次运行。onCreate方法仅仅会在第一次创建Activity时被运行。
而一般onCreate方法中会进行该页面的数据初始化、UI初始化,假设页面的展示数据无关页面跳转传递的參数,则不必操心此问题,若页面展示的数据就是通过getInten() 方法来获取,那么问题就会出现:getInten()获取的一直都是老数据,根本无法接收跳转时传送的新数据!
这时我们须要另外一个回调 onNewIntent(Intent intent)方法。此方法会传入最新的intent,这样我们就能够解决上述问题。这里建议的方法是又一次去setIntent。然后又一次去初始化数据和UI
/** 复用Activity时的生命周期回调*/
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- setIntent(intent);
- initData();
- initView();
- }
6、实际中的栈管理类
管理Activity的类,一般在BaseActivity会调用这个类,然后所有的Activity继承BaseActivity,这样管理好整个项目的Activity
- /**
- * activity堆栈管理
- */
- public class ActivityStackManager {
- private static ActivityStackManager mInstance;
- private static Stack<Activity> mActivityStack;
- public static ActivityStackManager getInstance() {
- if (null == mInstance) {
- mInstance = new ActivityStackManager();
- }
- return mInstance;
- }
- private ActivityStackManager() {
- mActivityStack = new Stack<Activity>();
- }
- /**
- * 入栈
- *
- * @param activity
- */
- public void addActivity(Activity activity) {
- mActivityStack.push(activity);
- }
- /**
- * 出栈
- *
- * @param activity
- */
- public void removeActivity(Activity activity) {
- mActivityStack.remove(activity);
- }
- /**
- * 彻底退出
- */
- public void finishAllActivity() {
- Activity activity;
- while (!mActivityStack.empty()) {
- activity = mActivityStack.pop();
- if (activity != null) {
- activity.finish();
- }
- }
- }
- /**
- * 结束指定类名的Activity
- *
- * @param cls
- */
- public void finishActivity(Class<?> cls) {
- for (Activity activity : mActivityStack) {
- if (activity.getClass().equals(cls)) {
- finishActivity(activity);
- }
- }
- }
- /**
- * 查找栈中是否存在指定的activity
- *
- * @param cls
- * @return
- */
- public boolean checkActivity(Class<?> cls) {
- for (Activity activity : mActivityStack) {
- if (activity.getClass().equals(cls)) {
- return true;
- }
- }
- return false;
- }
- /**
- * 结束指定的Activity
- *
- * @param activity
- */
- public void finishActivity(Activity activity) {
- if (activity != null) {
- mActivityStack.remove(activity);
- activity.finish();
- activity = null;
- }
- }
- /**
- * finish指定的activity之上所有的activity
- *
- * @param actCls
- * @param isIncludeSelf
- * @return
- */
- public boolean finishToActivity(Class<? extends Activity> actCls, boolean isIncludeSelf) {
- List<Activity> buf = new ArrayList<Activity>();
- int size = mActivityStack.size();
- Activity activity = null;
- for (int i = size - 1; i >= 0; i--) {
- activity = mActivityStack.get(i);
- if (activity.getClass().isAssignableFrom(actCls)) {
- for (Activity a : buf) {
- a.finish();
- }
- return true;
- } else if (i == size - 1 && isIncludeSelf) {
- buf.add(activity);
- } else if (i != size - 1) {
- buf.add(activity);
- }
- }
- return false;
- }}
总结
1、以上就是Activity 的启动模式和应用场景总结,除了 singleTask 稍微有点复杂,其它都很好理解
2、启动模式事实上是实际应用中必须会的知识点,你不去使用而仅仅是学习并不是能够掌握到精髓,仅仅有真正去使用才会将这些变成你自己的;