0%

Android-Activity

在最近的任务栈里面看见的task未必还活着。
在最近的任务栈里面看不见的task未必就死了。

android-launchMode

在这里我推荐大家去看扔物线的课程: Android 面试黑洞

Activity有4种启动模式,分别是:Standard、SingleTop、SingleTask和SingleInstance,它们控制了被启动Activity的启动行为。
Activity任务栈(Task)是一个标准的栈结构
用于在ActivityManagerService侧管理所有的Activity(AMS通过TaskRecord标识一个任务栈,通过ActivityRecord标识一个Activity)。

launchMode Name DES MORE【知识扩展】
Standard 标准模式 标准模式,也是系统的默认模式。该模式下,每次启动Activity,都会创建一个新实例,并且将其加入到启动该Activity的那个Activity所在的任务栈中,所以目标Activity的多个实例可以位于不同的任务栈。
SingleTop 栈顶复用模式 该模式下,若目标Activity的实例已经存在,但是没有位于栈顶,那么仍然会创建新的实例,并添加到任务栈;
SingleTask 栈内复用模式 clearTop的作用,整个任务栈只能有一个activity的实例。 如果此时任务栈内已经存在 Activity C 的实例且未位于栈顶,当启动 Activity C 时,会将 Activity C 上方的实例全部出栈让其位于任务栈顶并 Activity C 中的 onNewIntent() 方法会被调用
SingleInstance 单一实例 要求该Activity所在的Task只有有这么一个Activity, 下面没有旧,上面没有新

MORE【知识扩展】

Standard

当我们使用Application的Context来启动一个Activity的时候,会报AndroidRuntimeException,因为standard模式下启动的Activity,会默认进入启动它的Activity的任务栈中,而非Activity类型的context却并没有所谓的任务栈,所以就会出现问题,解决这个问题的方法是需要为要启动的Activity指定标记位:FLAG_ACTIVITY_NEW_TASK,这个标记的作用是为启动的Activity创建一个新的任务栈。

SingleTop

该模式下,若目标Activity的实例已经存在,但是没有位于栈顶,那么仍然会创建新的实例,并添加到任务栈;

若目标Activity的实例已经存在,且位于栈顶,那么就不会创建新的实例,而是复用已有实例,并依次调用目标Activity的onPause -> onNewIntent -> onResume方法。
知识扩展!!!

singleTop模式的activity特点就是除了外部可以启动它显示信息外,它也可以用同样的方式启动自己更新显示信息,这样就减少了冗余代码,降低了维护成本。

他的onCreate、onStart方法不会被调用,避免多次初始化,应用宝用的就是这套方案。

SingleTask A和B都是不同的2个应用

首先做个假设:A-Activity 启动 B_Activity,launchMode下文进行了标注
A_Activity[Standard] ==> B_Activity[SingleTask]
B_Activity的Activity被启动时,不会进入启动A的Task里,而是创建属于B自己的Task里,置于B-Task栈顶,然后把整个B-Task 压到 A-Task上面,这种方式打开的方式是入场动画是应用间的切换。

如果这时按下返回键,这里回退的顺序是 B-Task 来回退,而不是返回原来的A_Activity[Standard]。原来的A要在前台显示,必须是B-Task的栈的Activity完全关闭才可以,这里场景动画也是场景间的切换。

这里要明确一个概念:不止应用的Activity内部可以叠成栈,不同应用之间的Task也可以叠成栈。

Task的叠加只适用于前台的Task。

SingleTask是保证了一个栈内有且只有被该属性修饰Activity的实例对象,全局单例

SingleInstance

为了描述清晰,我们还是要举个例子:
A_Activity[Standard] ==> B_Activity[SingleInstance]
还是 A 短信应用 打开 B 邮箱应用
邮件App不仅为了当前跳转的Activity创建对象,而且会创建一个单独的B-Task,然后把当前的打开应用的Activity的实例放到B-Task。
第二种情况是当前Activity已经被创建过了,则会回调当前应用的Activity生命周期的**onNewIntent()**方法

这2种情况的变化,这个B-Task都会被拿过来压在A-Task上面,入场动画是切换Task的动画。

假设用户这个时候点返回,上面的Task因为只有一个Activity,手机会直接回到短信App,入场动画是切换Task的动画。

假设用户没有直接点返回,而是选择查看了最近的任务又返回到之B-Task,这个时候短信的A-Task已经被退到后台,所以用户再点返回的话,又因为当前的Task内只有一个B-Activity,所以是直接回退到桌面。

假设用户又没有点返回,也没有选择查看任务,而是选择了在B-Task,又打开了一个C-App的Activity,由于SingleInstance的限制,所以C-task并不会叠在B-Task之上,而是另辟蹊径,而是装到另一个Task中,然后整个Task跌在B-Task上面。

知识点: 我来解释一下另辟蹊径的意思,这个时候相对邮箱App此时有2个Task,一个是独栈的,也就是短信调用的那个,还有一个是后台的Task,C的Activity是被放到了后台的Task。

在最近的任务栈里面看不见的task未必就死了。这句话也是在这里体现。
在延伸出一个属性:Android:taskAffinity,

Android:taskAffinity属性

在android里,一般情况下一个App默认只有一个Task,可以显示在最近的列表里面,但是用来甄别唯一性的不是App,而是taskAffinity,每一个Activity都有属于自己的Activity,相当于每个Activity的预先分组。
taskAffinity
android:taskAffinity是Activity的一个属性,表示该Activity期望的任务栈的名称。
默认情况下,一个应用程序中所有Activity的taskAffinity都是相同的,即应用程序的包名。当然,我们可以在配置文件中为每个Activity指定不同的taskAffinity(只有和已有包名不同,才有意义)。
taskAffinity
另外每一个Task也有taskAffinity,他的值取自栈底的Activity的taskAffinity值,这个栈底Activity也就是第一个启动应用的Activity。

另外一点taskAffinity也可以在androidManifest被定制。
taskAffinity

一般情况下,该属性主要和SingleTask启动模式或者android:allowTaskReparenting属性结合使用(下面会详细介绍),在其他情况下没有意义。

Task - Reparenting

B AndroidManifest属性 设置为android:allowTaskReparenting = True

场景具体化:A 短信应用 打开 B 邮箱应用

B还是会进入到A的Task里,但稍后用户从桌面点击B应用的时候,原先放在A-Task里面的B-Activity会被挪到B应用的Task的栈顶,这个时候你在切回短信Task,你会发现原先的B-Activity已经不见了。
(有争议… Android 8,9是有问题的,10以上正常)

前台Task

前台叠加的多个Task进入后台第一时间就会拆开

1.按Home键回到桌面
2.按最近任务键 查看最近的任务

  • 前台Task 在最近列表显示出来的时候就已经进入到了后台,而不是切到其他任务之后。

但是光知道理论还是不够的,由于我们开发的时候大部分的情况都是多变的,我们在前面的章节可以了解到不同的应用的Activity是启动Task内叠加的。

首先做个假设:A-Activity 启动 B_Activity,launchMode下文进行了标注
A_Activity[Standard] ==> B_Activity[SingleTask]
我们再把场景具体化,A 短信应用 打开 B 邮箱应用,这个时间我们没有按下返回键,而是按最近任务键,再重新回去原来的Task,这个时候看起来没有变化,但是实际上A的Task已经被去除掉了,只留下B的Task,这个时候我们再按下返回键,把B的Task内的Activity全部关闭,这个时候返回的系统的桌面而不是短信的APP。

Android十万个为什么系列

当前 app 正在前台运行,不在栈顶的 Activity 有可能会因为系统资源,例如内存等不足回收吗?

我现在还记得这个问题,那年我毕业,投了心仪公司的简历,过了Hr的面试,来到了技术环节,和蔼的面试官问了:我们日常开发比较多组件的是Activity,假设 App 正在前台运行,不在栈顶的Activity有可能会被回收吗?
那时年少,我感觉我天灵盖被人开了,当场差点直接自闭,懵逼,接下来回答支支吾吾,面试结果是显而易见的。。。 (ಥ_ಥ)
回去的路上越想越不对劲,到家之后立马查源码,好家伙找到了!平时不看源码,关键时刻被别人搞得一愣一愣!

凡事先说结论: 会 !!!
这里看的源码是API30,在网上也看到一些相关答案答案,这里我们提炼一下要点,重点我们日常开发对场景预测,比方说游戏A-APP开启了一个B-App应用的界面,这个时候B-App开启了一个比较消耗耗内存的线程,如果这个内存不断地增大,最后游戏A-App的生命周期onDestroy()就会被调用。

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
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
//ApplicationThread是ActivityThread和AMS沟通的桥梁
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try {
ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
});
}

当 app 处于后台运行,app 进程未被杀死,其内部的 Activity 会被回收吗?

当 app 处于后台运行,app 的进程会被杀死吗?