参考:
Activity:https://developer.android.com/guide/components/activities.html
任务和返回栈:https://developer.android.com/guide/components/tasks-and-back-stack.html
Activity
是 Android
系统中使用最频繁的组件,刚入门 Android
就需要使用它。Activity
包含了许多隐藏的知识,需要深入了解才能更好的使用它
Activity
浅析(Android Activity - 1
)- 生命周期
- 退出情况分析(
Android Activity - 2
) - 任务与返回栈
- 启动模式
BaseActivity.java
(Android Activity - 3
)- 清理返回栈
- 屏幕方向
退出情况分析
在实际运行过程中,有时候需要对 Activity
进行自动跳转和手动退出,下面讨论这些情况下回调函数的使用
情况一:启动 MainActivity
,在 onCreate
方法中调用 startActivity
跳转到 Main2Activity
首先经过 MainActivity
的 onCreate -> onStart -> onResume -> onPause
然后经过 Main2Activity
的 onCreate -> onStart -> onResume
最后经过 MainActivity
的 onStop
其生命周期运行情况和点击跳转一致
情况二:启动 MainActivity
,在 onCreate
方法中调用 finish
方法退出
MainActivity
经调用两个方法:onCreate -> onDestroy
情况三:启动 MainActivity
,在 onCreate
方法中调用 startActivity
跳转到 Main2Activity
,然后调用方法 finish
退出
首先经过 MainActivity
的 onCreate
然后经过 Main2Activity
的 onCreate -> onStart -> onResume
最后经过 Main2Activity
的 onDestroy
情况四:启动 MainActivity
,在 onStart
方法中调用 finish
方法退出
经过回调函数为 onCreate -> onStart -> onStop -> onDestroy
由上面的测试可知,onCreate / onDestroy
和 onStart / 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
测试
测试一:设置启动 Activity
为 singleTop
模式,点击按钮调用该 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
:
测试二:设置启动 Activity
为 singleTask
模式,点击按钮跳转到 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
,回调过程如下
首先执行 MainActivity
的 onPause
方法
接着执行 Main2Activity
的 onCreate -> onStart -> onResume
方法
最后执行 MainActivity
的 onStop
方法
同时可知
当前任务已有 2
个 Activity
实例,当前 Activity
处于栈顶
点击按钮,调用 MainActivity
,回调过程如下
首先执行 Main2Activity
的 onPause
方法
接着执行 MainActivity
的 onNewIntent -> onRestart -> onStart -> onResume
方法
最后执行 Main2Activity
的 onStop -> onDestroy
方法
两个 Activity
均处于同一任务栈中,所以需要退出 Main2Activity
,此时,任务栈仅剩单个实例
点击返回按钮,应用退出,执行回调函数 onPause -> onStop -> onDestroy
测试三:设置启动 MainActivity
和 Main2Activity
均为 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" />
启动应用,调用 MainActivity
的 onCreate -> onStart -> onResume
方法
点击按钮,跳转到 Main2Activity
首先调用 MainActivity
的 onPause
接着调用 Main2Activity
的 onCreate -> onStart -> onResume
最后调用 MainActivity
的 onStop
Note:由于没有指定 Activity
所属任务名,所以 3
个 Activity
均处于同一任务中
点击按钮,跳转到 Main3Activity
下面分为 3
种情形,
- 当前
Activity
为Main3Activity
,点击屏幕home
键退出,再次点击启动图标,启动应用 - 从
Main3Activity
跳转到Main2Activity
- 从
Main3Activity
跳转到MainActivity
第一种情形
如上面所言,这里会有一种特殊情况,其它 Activity
均会退出,仅剩启动 Activity
点击屏幕 home
键,回调系统页面,调用 Main3Activity
的 onPause -> onStop
回调方法
点击应用图标,返回
首先执行 Main2Activity
和 Main3Activity
的 onDestroy
方法
然后调用 MainActivity
的 onNewIntent -> onRestart -> onStart -> onResume
方法
待解:为什么先退出 Main2Activity
第二种情形
点击按钮,跳转到 Main2Activity
因为 3
个 Activity
均处于同一任务中,Main2Activity
设置为 singleTask
启动模式,所以再次调用该 Activity
,须将 Main3Activity
退出
首先执行 Main3Activity
的 onPause
接着执行 Main2Activity
的 onNewIntent -> onRestart -> onStart -> onResume
最后执行 Main3Activity
的 onStop -> onDestroy
第三种情形
点击按钮,跳转到 MainActivity
和第二种情形类似,需要退出 Main2Activity
和 Main3Activity
调用顺序如下
首先调用 Main2Activity
的 onDestroy
接着执行 Main3Activity
的 onPause
再执行 MainActivity
的 onNewIntent -> onRestart -> onStart -> onResume
最后执行 Main3Activity
的 onStop -> onDestroy
待解:为什么对于 Main2Activity
仅执行 onDestroy
方法
测试四:设置启动 Activity
为 singleInstance
模式,点击按钮,跳转 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();
}
}
增加 Activity
的 taskAffinity
和 launchMode
属性值的记录
对于 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
情况一:设置启动 Activity
为 singleTask
并修改其 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
由图可知,虽然两个 Activity
的 taskAffinity
属性值不同,但是仍属于同一任务
点击按钮,跳转 MainActivity
因为该任务中已经存在 MainActivity
实例对象,所以退出 Main2Activity
,调用 MainActivity
的 onNewIntent
方法
情况二:设置 MainActivity
和 Main2Activity
均为 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
由上图可知,此时 MainActivity
和 Main2Activity
均属于不同任务
点击按钮,跳转 Main3Activity
由于 Main3Activity
为默认模式,所以它和 Main2Activity
属于同一个任务
点击按钮,从 Main3Activity
跳转回 MainActivity
因为 MainActivity
和 Main3Activity
属于不同任务,并且已存在实例,所以仅需调用 onNewIntent
方法即可
点击按钮,从 MainActivity
跳转到 Main2Activity
因为 Main2Activity
和 Main3Activity
处于同一任务,并且 Main2Activity
位于栈底。所以,需要先退出 Main3Activity
,然后调用 Main2Activity
的 onNewIntent
方法