Android Activity - 2

参考:

Activityhttps://developer.android.com/guide/components/activities.html
任务和返回栈https://developer.android.com/guide/components/tasks-and-back-stack.html


ActivityAndroid 系统中使用最频繁的组件,刚入门 Android 就需要使用它。Activity 包含了许多隐藏的知识,需要深入了解才能更好的使用它


主要内容

  1. Activity 浅析Android Activity - 1
  2. 生命周期
  3. 退出情况分析Android Activity - 2
  4. 任务与返回栈
  5. 启动模式
  6. BaseActivity.javaAndroid Activity - 3
  7. 清理返回栈
  8. 屏幕方向

退出情况分析

在实际运行过程中,有时候需要对 Activity 进行自动跳转和手动退出,下面讨论这些情况下回调函数的使用

情况一:启动 MainActivity,在 onCreate 方法中调用 startActivity 跳转到 Main2Activity

首先经过 MainActivityonCreate -> onStart -> onResume -> onPause

然后经过 Main2ActivityonCreate -> onStart -> onResume

最后经过 MainActivityonStop

这里写图片描述

其生命周期运行情况和点击跳转一致

情况二:启动 MainActivity,在 onCreate 方法中调用 finish 方法退出

MainActivity 经调用两个方法:onCreate -> onDestroy

这里写图片描述

情况三:启动 MainActivity,在 onCreate 方法中调用 startActivity 跳转到 Main2Activity,然后调用方法 finish 退出

首先经过 MainActivityonCreate

然后经过 Main2ActivityonCreate -> onStart -> onResume

最后经过 Main2ActivityonDestroy

这里写图片描述

情况四:启动 MainActivity,在 onStart 方法中调用 finish 方法退出

经过回调函数为 onCreate -> onStart -> onStop -> onDestroy

这里写图片描述

由上面的测试可知,onCreate / onDestroyonStart / onStop 都是成对出现的


任务与返回栈

从上面的分析可知,Activity 之间存在某种关联关系,应用的启动和退出也和这种关系有着密切的联系,下面讨论 Android 系统针对 Activity 使用

Android 应用由多任务(task)组成,启动应用,便会创建任务;当所有任务都结束时,应用退出。默认情况下,应用仅包含一个任务

任务由多个 Activity 组成,每个任务都有一个返回栈,任务将 Activity 保存在返回栈(back stack)中

返回栈属于堆栈结构,实行后进先出原则,即新创建的 Activity 总是位于栈顶,并总是先从返回栈中退出

Activity 结束时,将其从返回栈退出(或者说 Activity 从返回栈退出表示结束),由于返回栈中的 Activity 的先后顺序不能改变,只有位于栈顶的 Activity 才能退出,所以如果想要结束不位于栈顶的 Activity,只有将前面的其它 Activity 都结束后,才能结束目标 Activity,比如,返回栈中有 ABCD 四个 Activity,其中 D 位于栈顶,如果想要结束 B,只有先结束 D,再结束 C,最后才能结束 B

官网示例图如下:

这里写图片描述

当每次调用 startActivity / startActivityOnResult 函数启动 Activity,均会创建一个新的 Activity 对象,所以一个返回栈中可能包含多个同一 Activity 对象,即一个任务中可能包含多个同一 Activity 对象

官网示例图如下:

这里写图片描述

当用户点击 主页 按钮离开应用时,任务将进入后台。此时任务中的 Activity 均处于 停止 状态,但是其排列顺序仍旧存在,再次启动时会重新将位于栈顶的 Activity 恢复运行

当返回栈中最后一个 Activity 退出后,该任务结束


启动模式

启动模式(LaunchMode)用来表示调用的 Activity 对象与当前 Activity 的关系

Activity 共有四种启动模式:

  • standard(默认模式)
  • singleTop
  • singleTask
  • singleInstance
standard

默认 Activity 使用 standard 启动模式。应用每次启动 Activity 时均创建实例对象,并将其加入到当前任务。每个实例均可属于不同的任务,并且一个任务可以拥有多个实例

singleTop

这个模式和 standard 模式唯一的区别就是如果当前任务的返回栈中位于栈顶的 Activity 实例和将要调用的 Activity 相同,那么不会再新创建实例,而是直接调用位于栈顶的 Activity 实例,会调用回调方法 onNewIntent当然,如果返回栈中相同的 Activity 实例不位于栈顶,那么一样会新创建实例对象,并加入返回栈

singleTask

参考:

解开Android应用程序组件Activity的”singleTask”之谜

[Andriod] android中singleTask的home键的问题

设置为 singleTask 启动模式的 Activity 在整个应用中仅能存在单个实例。对于设置为该启动模式的 Activity,调用时 Android 系统会执行以下几点:

  • 首先判断该返回栈中是否已存在该 Activity 实例, 如果已存在,那么将其调用到栈顶(将返回栈中位于该实例之上的其它 Activity 退出),调用回调方法 onNewIntent

  • 如果不存在,新建实例,加入栈中

  • 如果已设置该 Activity 所在任务名,并与当前任务不一致,那么创建新任务,将实例加入栈中

Note 1:设置为 singleTask 启动模式的 Activity 同时作为应用主 Activity 的话,按 主页 键退出应用,重新启动,将直接跳转到该 Activity

Note 2:无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按 返回* 按钮始终会转到前一个 Activity。 但是,如果启动指定 singleTask 启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。此时,返回栈包括上移到堆栈顶部的任务中的所有 Activity*

官网示例图如下:

这里写图片描述

Note 3:singleTask 启动模式比较复杂,使用时最好进行真机测试

singleInstance

singleInstance 模式和 singleTask 模式类似,唯一区别是设置为 singleInstance 模式的 Activity 是单独存在于一个任务中,是其所在任务的唯一成员

设置方式

有两种方式可以设置启动模式:使用清单文件(AndroidManifest.xml),或者设置 Intent 标志位

使用清单文件

设置 <activity> 元素的属性 android:launchMode

这里写图片描述

测试

测试一:设置启动 ActivitysingleTop 模式,点击按钮调用该 Activity

设置如下:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTop">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

调用代码如下:

Intent intent = new Intent(MainActivity.this, MainActivity.class);
startActivity(intent);

启动应用,调用 MainActivity 的回掉方法 onCreate -> onStart -> onResume

这里写图片描述

点击按钮,再次调用 MainActivity,经过回调函数 onPause -> onNewIntent -> onResume

这里写图片描述

点击回退按钮,应用退出,执行回调函数 onPause -> onStop -> onDestroy

这里写图片描述

测试二:设置启动 ActivitysingleTask 模式,点击按钮跳转到 Main2Activity,在 Main2Activity 中点击按钮跳转到启动 Activity

设置 清单文件 如下:

    <activity
        android:name=".MainActivity"
        android:launchMode="singleTask">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".Main2Activity" />

修改 BaseActivity.java,增加如下函数,记录当前 Activity 所属任务,以及任务数,并判断当前 Activity 是否位于所属任务的栈底或者栈顶, 在 onCreate 函数中调用:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    taskInfo();

    Log.d(TAG, "onCreate: " + getLocalClassName());
}

private void taskInfo() {
    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    List<ActivityManager.AppTask> appTaskList = manager.getAppTasks();

    for (int i = 0; i < appTaskList.size(); i++) {
        ActivityManager.AppTask appTask = appTaskList.get(i);
        ActivityManager.RecentTaskInfo info = appTask.getTaskInfo();

        if (getTaskId() == info.id) {
            String str = String.format(Locale.CHINA,
                    "taskId = %d numActivities = %d isTaskRoot = %b isTaskTop = %b",
                    getTaskId(), info.numActivities, isTaskRoot(),
                    info.topActivity.getShortClassName().equals(getComponentName().getShortClassName()));
            Log.d(TAG, "taskInfo: " + str);
        }
    }
}

启动应用,调用 MainActivity 的回调方法 onCreate -> onStart -> onResume,此时任务中仅有单个 Activity 实例:

这里写图片描述

点击按钮,跳转到 Main2Activity,回调过程如下

首先执行 MainActivityonPause 方法

接着执行 Main2ActivityonCreate -> onStart -> onResume 方法

最后执行 MainActivityonStop 方法

同时可知

当前任务已有 2Activity 实例,当前 Activity 处于栈顶

这里写图片描述

点击按钮,调用 MainActivity,回调过程如下

首先执行 Main2ActivityonPause 方法

接着执行 MainActivityonNewIntent -> onRestart -> onStart -> onResume 方法

最后执行 Main2ActivityonStop -> onDestroy 方法

两个 Activity 均处于同一任务栈中,所以需要退出 Main2Activity,此时,任务栈仅剩单个实例

这里写图片描述

点击返回按钮,应用退出,执行回调函数 onPause -> onStop -> onDestroy

这里写图片描述

测试三:设置启动 MainActivityMain2Activity 均为 singleTask 启动模式,点击按钮从 MainActivity 跳转到 Main2Activity,再从 Main2Activity 跳转到 Main3Activity

设置 清单文件 如下:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity
    android:name=".Main2Activity"
    android:launchMode="singleTask" />
<activity android:name=".Main3Activity" />

启动应用,调用 MainActivityonCreate -> onStart -> onResume 方法

这里写图片描述

点击按钮,跳转到 Main2Activity

首先调用 MainActivityonPause

接着调用 Main2ActivityonCreate -> onStart -> onResume

最后调用 MainActivityonStop

Note:由于没有指定 Activity 所属任务名,所以 3Activity 均处于同一任务中

这里写图片描述

点击按钮,跳转到 Main3Activity

这里写图片描述

下面分为 3 种情形,

  • 当前 ActivityMain3Activity,点击屏幕 home 键退出,再次点击启动图标,启动应用
  • Main3Activity 跳转到 Main2Activity
  • Main3Activity 跳转到 MainActivity

第一种情形

如上面所言,这里会有一种特殊情况,其它 Activity 均会退出,仅剩启动 Activity

点击屏幕 home 键,回调系统页面,调用 Main3ActivityonPause -> onStop 回调方法

这里写图片描述

点击应用图标,返回

首先执行 Main2ActivityMain3ActivityonDestroy 方法

然后调用 MainActivityonNewIntent -> onRestart -> onStart -> onResume 方法

这里写图片描述

待解:为什么先退出 Main2Activity

第二种情形

点击按钮,跳转到 Main2Activity

因为 3Activity 均处于同一任务中,Main2Activity 设置为 singleTask 启动模式,所以再次调用该 Activity,须将 Main3Activity 退出

首先执行 Main3ActivityonPause

接着执行 Main2ActivityonNewIntent -> onRestart -> onStart -> onResume

最后执行 Main3ActivityonStop -> onDestroy

这里写图片描述

第三种情形

点击按钮,跳转到 MainActivity

和第二种情形类似,需要退出 Main2ActivityMain3Activity

调用顺序如下

首先调用 Main2ActivityonDestroy

接着执行 Main3ActivityonPause

再执行 MainActivityonNewIntent -> onRestart -> onStart -> onResume

最后执行 Main3ActivityonStop -> onDestroy

这里写图片描述

待解:为什么对于 Main2Activity 仅执行 onDestroy 方法

测试四:设置启动 ActivitysingleInstance 模式,点击按钮,跳转 Main2Activity,从 Main2Activity 点击按钮跳转 MainActivity

设置 清单文件 如下:

<activity
    android:name=".MainActivity"
    android:launchMode="singleInstance">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".Main2Activity" />

启动应用

这里写图片描述

点击按钮,启动 Main2Activity

这里写图片描述

由图可知,两个 Activity 处于不同任务中

点击按钮,启动 MainActivity

这里写图片描述

因为 MainActivity 实例已经存在,所以没有新建

再次点击按钮,调用 Main2Activity

这里写图片描述

待解:为什么没有新建实例,而是使用已经存在的

使用 Intent 标志位

使用方式如下:

Intent intent = new Intent(MainActivity.this, Main2Activity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

常用以下几个标志:

  • Intent.FLAG_ACTIVITY_NEW_TASK
  • Intent.FLAG_ACTIVITY_SINGLE_TOP
  • Intent.FLAG_ACTIVITY_CLEAR_TOP

标志位 FLAG_ACTIVITY_NEW_TASK 的作用等同于 singleTask

/**
 * If set, this activity will become the start of a new task on this
 * history stack.  A task (from the activity that started it to the
 * next task activity) defines an atomic group of activities that the
 * user can move to.  Tasks can be moved to the foreground and background;
 * all of the activities inside of a particular task always remain in
 * the same order.  See
 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
 * Stack</a> for more information about tasks.
 *
 * <p>This flag is generally used by activities that want
 * to present a "launcher" style behavior: they give the user a list of
 * separate things that can be done, which otherwise run completely
 * independently of the activity launching them.
 *
 * <p>When using this flag, if a task is already running for the activity
 * you are now starting, then a new activity will not be started; instead,
 * the current task will simply be brought to the front of the screen with
 * the state it was last in.  See {@link #FLAG_ACTIVITY_MULTIPLE_TASK} for a flag
 * to disable this behavior.
 *
 * <p>This flag can not be used when the caller is requesting a result from
 * the activity being launched.
 */
public static final int FLAG_ACTIVITY_NEW_TASK = 0x10000000;

标志位 FLAG_ACTIVITY_SINGLE_TOP 的作用等同于 singleTop

/**
 * If set, the activity will not be launched if it is already running
 * at the top of the history stack.
 */
public static final int FLAG_ACTIVITY_SINGLE_TOP = 0x20000000;

标志位 FLAG_ACTIVITY_CLEAR_TOP 的作用并不等同于四种启动模式

当前任务栈中如果已有该 Activity 实例,那么将位于它之上的其它 Activity 实例退出。

如果仅使用该标志位,那么也会退出 Activity 实例并重新创建

如果该标志位和 FLAG_ACTIVITY_SINGLE_TOP 同时使用,比如:

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

那么会直接使用当前的实例,通过回调函数 onNewIntent

也可以和标志位 FLAG_ACTIVITY_NEW_TASK 一起使用

如果和 FLAG_ACTIVITY_NEW_TASK 一起使用,没明白和单独 singleTask模式 的区别

/**
 * If set, and the activity being launched is already running in the
 * current task, then instead of launching a new instance of that activity,
 * all of the other activities on top of it will be closed and this Intent
 * will be delivered to the (now on top) old activity as a new Intent.
 *
 * <p>For example, consider a task consisting of the activities: A, B, C, D.
 * If D calls startActivity() with an Intent that resolves to the component
 * of activity B, then C and D will be finished and B receive the given
 * Intent, resulting in the stack now being: A, B.
 *
 * <p>The currently running instance of activity B in the above example will
 * either receive the new intent you are starting here in its
 * onNewIntent() method, or be itself finished and restarted with the
 * new intent.  If it has declared its launch mode to be "multiple" (the
 * default) and you have not set {@link #FLAG_ACTIVITY_SINGLE_TOP} in
 * the same intent, then it will be finished and re-created; for all other
 * launch modes or if {@link #FLAG_ACTIVITY_SINGLE_TOP} is set then this
 * Intent will be delivered to the current instance's onNewIntent().
 *
 * <p>This launch mode can also be used to good effect in conjunction with
 * {@link #FLAG_ACTIVITY_NEW_TASK}: if used to start the root activity
 * of a task, it will bring any currently running instance of that task
 * to the foreground, and then clear it to its root state.  This is
 * especially useful, for example, when launching an activity from the
 * notification manager.
 *
 * <p>See
 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
 * Stack</a> for more information about tasks.
 */
public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000;

优先级

当对同一 Activity 设置了这两种方式,Intent 标志位的优先级更高

比如,如果 Activity A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B 应该如何与当前任务关联。如果这两个 Activity 均定义 Activity B 应该如何与任务关联,则 Activity A 的请求(如 Intent 中所定义)优先级要高于 Activity B 的请求(如其清单文件中所定义)

TaskAffinity

可以通过设置 清单文件<activity> 元素的 android:taskAffinity 属性,设置 Activity 的所属任务名

Note:并不是设置过这个属性后就能创建新任务,需要和 singleTask 结合使用

这里写图片描述

修改方法 taskInfo()

private void taskInfo() {
    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    List<ActivityManager.AppTask> appTaskList = manager.getAppTasks();
    PackageManager packageManager = getPackageManager();

    for (int i = 0; i < appTaskList.size(); i++) {
        ActivityManager.AppTask appTask = appTaskList.get(i);
        ActivityManager.RecentTaskInfo info = appTask.getTaskInfo();

        if (getTaskId() == info.id) {
            String str = String.format(Locale.CHINA,
                    "taskId = %d numActivities = %d isTaskRoot = %b isTaskTop = %b",
                    getTaskId(), info.numActivities, isTaskRoot(),
                    info.topActivity.getShortClassName().equals(getComponentName().getShortClassName()));
            Log.d(TAG, "taskInfo: " + str);
        }
    }

    try {
        ActivityInfo activityInfo = packageManager.getActivityInfo(getComponentName(), PackageManager.MATCH_ALL);

        StringBuilder sb = new StringBuilder();
        String str = "taskAffinity = " + activityInfo.taskAffinity + " launchMode = ";

        sb.append(str);
        switch (activityInfo.launchMode) {
            case 0:
                sb.append("standard");
                break;
            case 1:
                sb.append("singleTop");
                break;
            case 2:
                sb.append("singleTask");
                break;
            case 3:
                sb.append("singleInstance");
                break;
        }
        Log.d(TAG, "taskInfo: " + sb.toString());
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
}

增加 ActivitytaskAffinitylaunchMode 属性值的记录

对于 taskAffinity 属性而言,默认情况下,该属性值为应用包名

/**
 * The affinity this activity has for another task in the system.  The
 * string here is the name of the task, often the package name of the
 * overall package.  If null, the activity has no affinity.  Set from the
 * {@link android.R.attr#taskAffinity} attribute.
 */
public String taskAffinity;

对于 launchMode 属性而言

  • 0 表示 LAUNCH_MULTIPLE(默认模式)
  • 1 表示 LAUNCH_SINGLE_TOP
  • 2 表示 LAUNCH_SINGLE_TASK
  • 3 表示 LAUNCH_SINGLE_INSTANCE

    /**
     * The launch mode style requested by the activity.  From the
     * {@link android.R.attr#launchMode} attribute, one of
     * {@link #LAUNCH_MULTIPLE},
     * {@link #LAUNCH_SINGLE_TOP}, {@link #LAUNCH_SINGLE_TASK}, or
     * {@link #LAUNCH_SINGLE_INSTANCE}.
     */
    public int launchMode;
    

TaskAffinity 需要和 singleTask 启动模式或者 allowTaskReparenting 搭配使用

TaskAffinity + singleTask

情况一:设置启动 ActivitysingleTask 并修改其 taskAffinity 属性,点击按钮,跳转 Main2Activity

清单文件设置如下:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.zj.activitydemo.MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".Main2Activity" />

启动应用,创建 MainActivity

这里写图片描述

点击按钮,跳转 Main2Activity

这里写图片描述

由图可知,虽然两个 ActivitytaskAffinity 属性值不同,但是仍属于同一任务

点击按钮,跳转 MainActivity

这里写图片描述

因为该任务中已经存在 MainActivity 实例对象,所以退出 Main2Activity,调用 MainActivityonNewIntent 方法

情况二:设置 MainActivityMain2Activity 均为 singleTask 模式

清单文件设置如下:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.zj.activitydemo.MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity
    android:name=".Main2Activity"
    android:launchMode="singleTask" />
<activity android:name=".Main3Activity" />

启动应用,创建 MainActivity

这里写图片描述

点击按钮,跳转 Main2Activity

这里写图片描述

由上图可知,此时 MainActivityMain2Activity 均属于不同任务

点击按钮,跳转 Main3Activity

这里写图片描述

由于 Main3Activity 为默认模式,所以它和 Main2Activity 属于同一个任务

点击按钮,从 Main3Activity 跳转回 MainActivity

这里写图片描述

因为 MainActivityMain3Activity 属于不同任务,并且已存在实例,所以仅需调用 onNewIntent 方法即可

点击按钮,从 MainActivity 跳转到 Main2Activity

这里写图片描述

因为 Main2ActivityMain3Activity 处于同一任务,并且 Main2Activity 位于栈底。所以,需要先退出 Main3Activity,然后调用 Main2ActivityonNewIntent 方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值