深圳幻海软件技术有限公司 欢迎您!

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

2023-03-01

本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。前言:Activity作为Android四大组件之一,几乎是被接触得最多的;Android对Activity的管理,Android采用Task来管理多个Activity,当我们启动

本文转载自微信公众号「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? 
  • 1.

此时应该为这个 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> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

2、通过Intent设置标志位

Intent inten = new Intent (ActivityA.this,ActivityB.class); 
intent.addFlags(Intent,FLAG_ACTIVITY_NEW_TASK); 
startActivity(intent); 
  • 1.
  • 2.
  • 3.
  • 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();     

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

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.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.

总结

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

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

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