Android进阶之Activity启动模式和应用场景详解

移动开发 Android
Activity 作为 Android 四大组件之一,几乎是被接触得最多的;Android对Activity的管理,Android采用Task来管理多个Activity,当我们启动一个应用时,Android就会为之创建一个Task,然后启动这个应用的入口Activity;

[[416944]]

本文转载自微信公众号「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 没有任务栈可入,会出现异常:

  1. 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配置进行设置

  1. <activity 
  2. android:launchMode="启动模式" 
  3. //属性 
  4. //standard:标准模式 
  5. //singleTop:栈顶复用模式 
  6. //singleTask:栈内复用模式 
  7. //singleInstance:单例模式 
  8. //如不设置,Activity的启动模式默认为**标准模式(standard)** 
  9. </activity> 

2、通过Intent设置标志位

  1. Intent inten = new Intent (ActivityA.this,ActivityB.class); 
  2. intent.addFlags(Intent,FLAG_ACTIVITY_NEW_TASK); 
  3. 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时的生命周期回调*/

  1. @Override     
  2. protected void onNewIntent(Intent intent) {         
  3.     super.onNewIntent(intent);         
  4.     setIntent(intent);         
  5.     initData();         
  6.     initView();     

6、实际中的栈管理类

管理Activity的类,一般在BaseActivity会调用这个类,然后所有的Activity继承BaseActivity,这样管理好整个项目的Activity

  1. /** 
  2.  *  activity堆栈管理 
  3.  */ 
  4. public class ActivityStackManager { 
  5. private static ActivityStackManager mInstance; 
  6. private static Stack<Activity> mActivityStack; 
  7. public static ActivityStackManager getInstance() { 
  8.     if (null == mInstance) { 
  9.         mInstance = new ActivityStackManager(); 
  10.     } 
  11.     return mInstance; 
  12. private ActivityStackManager() { 
  13.     mActivityStack = new Stack<Activity>(); 
  14. /** 
  15.  * 入栈 
  16.  * 
  17.  * @param activity 
  18.  */ 
  19. public void addActivity(Activity activity) { 
  20.     mActivityStack.push(activity); 
  21. /** 
  22.  * 出栈 
  23.  * 
  24.  * @param activity 
  25.  */ 
  26. public void removeActivity(Activity activity) { 
  27.     mActivityStack.remove(activity); 
  28. /** 
  29.  * 彻底退出 
  30.  */ 
  31. public void finishAllActivity() { 
  32.     Activity activity; 
  33.     while (!mActivityStack.empty()) { 
  34.         activity = mActivityStack.pop(); 
  35.         if (activity != null) { 
  36.             activity.finish(); 
  37.         } 
  38.     } 
  39. /** 
  40.  * 结束指定类名的Activity 
  41.  * 
  42.  * @param cls 
  43.  */ 
  44. public void finishActivity(Class<?> cls) { 
  45.     for (Activity activity : mActivityStack) { 
  46.         if (activity.getClass().equals(cls)) { 
  47.             finishActivity(activity); 
  48.         } 
  49.     } 
  50. /** 
  51.  * 查找栈中是否存在指定的activity 
  52.  * 
  53.  * @param cls 
  54.  * @return 
  55.  */ 
  56. public boolean checkActivity(Class<?> cls) { 
  57.     for (Activity activity : mActivityStack) { 
  58.         if (activity.getClass().equals(cls)) { 
  59.             return true
  60.         } 
  61.     } 
  62.     return false
  63. /** 
  64.  * 结束指定的Activity 
  65.  * 
  66.  * @param activity 
  67.  */ 
  68. public void finishActivity(Activity activity) { 
  69.     if (activity != null) { 
  70.         mActivityStack.remove(activity); 
  71.         activity.finish(); 
  72.         activity = null
  73.     } 
  74. /** 
  75.  * finish指定的activity之上所有的activity 
  76.  * 
  77.  * @param actCls 
  78.  * @param isIncludeSelf 
  79.  * @return 
  80.  */ 
  81. public boolean finishToActivity(Class<? extends Activity> actCls, boolean isIncludeSelf) { 
  82.     List<Activity> buf = new ArrayList<Activity>(); 
  83.     int size = mActivityStack.size(); 
  84.     Activity activity = null
  85.     for (int i = size - 1; i >= 0; i--) { 
  86.         activity = mActivityStack.get(i); 
  87.         if (activity.getClass().isAssignableFrom(actCls)) { 
  88.             for (Activity a : buf) { 
  89.                 a.finish(); 
  90.             } 
  91.             return true
  92.         } else if (i == size - 1 && isIncludeSelf) { 
  93.             buf.add(activity); 
  94.         } else if (i != size - 1) { 
  95.             buf.add(activity); 
  96.         } 
  97.     } 
  98.     return false
  99. }} 

总结

1、以上就是Activity 的启动模式和应用场景总结,除了 singleTask 稍微有点复杂,其它都很好理解

2、启动模式事实上是实际应用中必须会的知识点,你不去使用而仅仅是学习并不是能够掌握到精髓,仅仅有真正去使用才会将这些变成你自己的;

3、不懂的随时可以发信息问我。

 

责任编辑:武晓燕 来源: Android开发编程
相关推荐

2021-08-16 17:15:19

设计模式Android适配器模式

2014-05-27 15:36:01

AndroidActivity启动模式

2021-08-10 20:41:33

AndroidApp流程

2017-08-03 10:25:26

AndroidActivity

2021-08-23 06:27:46

AndroidctivitysetContentV

2020-02-12 14:42:00

GPU技术关键参数应用场景

2019-12-30 10:40:31

GPU技术应用

2021-04-27 08:31:10

前端应用场景

2021-04-21 09:21:07

zookeeper集群源码

2018-08-15 09:48:27

数据库Redis应用场景

2011-05-17 15:24:18

Shibboleth认证

2023-08-28 16:49:08

物联网传感器

2015-08-03 13:36:40

Docker技术优势应用场景

2021-08-17 13:41:11

AndroidView事件

2021-09-02 07:00:01

Glide流程Android

2016-03-24 14:02:05

ActivityAndroid启动

2012-10-23 09:32:07

2023-12-09 08:58:30

AndroidActivity启动模式

2021-08-25 07:43:17

AndroidSurfaceViewTextureView

2015-08-04 17:45:54

Docker应用
点赞
收藏

51CTO技术栈公众号