关于activity:明修栈道越过Android启动栈陷阱

6次阅读

共计 12853 个字符,预计需要花费 33 分钟才能阅读完成。

作者:vivo 互联网大前端团队 - Zhao Kaiping

本文从一例业务中遇到的问题登程,以 FLAG_ACTIVITY_NEW_TASK 这一 flag 作为切入点,带大家探索 Activity 启动前的一项重要的工作——栈校验。

文中列举一系列业务中可能遇到的异样情况,详细描述了应用 FLAG_ACTIVITY_NEW_TASK 时可能遇到的“坑”,并从源码中探究其根源。只有正当应用 flag、launchMode,能力防止因为栈机制的特殊性,导致一系列与预期不符的启动问题。

一、问题及背景

利用间互相联动、互相跳转,是实现零碎整体性、体验一致性的重要伎俩,也是最简略的一种办法。

当咱们用最罕用的办法去 startActivity 时,竟也会遇到失败的状况。在实在业务中,就遇到了这样一例异样:用户点击某个按钮时,想要“简简单单”跳转另一个利用,却没有任何反馈。

经验丰富的你,脑海中是否涌现出了各种猜测:是不是指标 Activity 甚至指标 App 不存在?是不是指标 Activty 没有对外开放?是不是有权限的限度或者跳转的 action/uri 错了……

实在的起因被 flag、launchMode、Intent 等个性层层隐匿,可能超出你此时的思考。

本文将从源码登程,探索前因后果,开展讲讲在 startActivity()真正筹备启动一个 Activity 前,须要通过哪些“磨难”,怎么有据可依地解决由栈问题导致的启动异样。

1.1 业务中遇到的问题

业务中的场景是这样的,存在 A、B、C 三个利用。

(1)从利用 A -Activity1 跳转至利用 B -Activity2;

(2)利用 B -Activity2 持续跳转到利用 C -Activity3;

(3)C 内某个按钮,会再次跳转 B -Activity2,但点击后没有任何反馈。如果不通过后面 A 到 B 的跳转,C 间接跳到 B 是能够的。

1.2 问题代码

3 个 Activity 的 Androidmanifest 配置如下,均可通过各自的 action 拉起,launchMode 均为规范模式。

<!-- 利用 A --> 
      <activity
            android:name=".Activity1"
            android:exported="true">
            <intent-filter>
                <action android:name="com.zkp.task.ACTION_TO_A_PAGE1" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
 
<!-- 利用 B -->
        <activity
            android:name=".Activity2"
            android:exported="true">
            <intent-filter>
                <action android:name="com.zkp.task.ACTION_TO_B_PAGE2" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
 
<!-- 利用 C -->
        <activity
            android:name=".Activity3"
            android:exported="true">
            <intent-filter>
                <action android:name="com.zkp.task.ACTION_TO_C_PAGE3" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

A- 1 到 B - 2 的代码,指定 flag 为 FLAG_ACTIVITY_NEW_TASK

private void jumpTo_B_Activity2_ByAction_NewTask() {Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

B- 2 到 C - 3 的代码,未指定 flag

private void jumpTo_C_Activity3_ByAction_NoTask() {Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_C_PAGE3");
    startActivity(intent);
}

C- 3 到 B - 2 的代码,与 A - 1 到 B - 2 的完全一致,指定 flag 为 FLAG_ACTIVITY_NEW_TASK

private void jumpTo_B_Activity2_ByAction_NewTask() {Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

1.3 代码初步剖析

认真查看问题代码,在实现上非常简单,有两个特色:

(1)如果间接通过 C - 3 跳 B -2,没有任何问题,但 A - 1 曾经跳过 B - 2 后,C- 3 就失败了。

(2)在 A - 1 和 C - 3 跳到 B - 2 时,都设置了 flag 为 FLAG_ACTIVITY_NEW_TASK。

根据教训,咱们揣测与栈无关,尝试将跳转前栈的状态打印进去,如下图。

因为 A - 1 跳到 B - 2 时设置了 FLAG_ACTIVITY_NEW_TASK,B- 2 跳到 C - 3 时未设置,所以 1 在独立栈中,2、3 在另一个栈中。示意如下图。

C- 3 跳转 B - 2 个别有 3 种可能的预期,如下图:料想 1,新建一个 Task,在新 Task 中启动一个 B -2;料想 2,复用曾经存在的 B -2;料想 3,在已有 Task 中新建一个实例 B -2。

但实际上 3 种预期都没有实现,所有 Activity 的任何申明周期都没有变动,界面始终停留在 C -3。

看一下 FLAG_ACTIVITY_NEW_TASK 的官网正文和代码正文,如下图:

重点关注这一段:

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.

应用此 flag 时,如果你正在启动的 Activity 曾经在一个 Task 中运行,那么一个新 Activity 不会被启动;相同,以后 Task 将简略地显示在界面的后面,并显示其最初的状态。

——显然,官网文档与代码正文的表述与咱们的异常现象是统一的,指标 Activity2 曾经在 Task 中存在,则不会被启动;Task 间接显示在后面,并展现最初的状态。因为指标 Activty3 就是起源 Activity3,所以页面没有任何变动。

看起来官网还是很靠谱的,但实际效果真的能始终与官网形容统一吗?咱们通过几个场景来看一下。

二、场景拓展与验证

2.1 场景拓展

在笔者根据官网形容进行调整、复现的过程中,发现了几个比拟有意思的场景。

PS:下面业务的案例中,B- 2 和 C - 3 在不同利用内,又在雷同的 Task 内,但实际上是否是同一个利用,对后果的影响并不大。为了防止不同利用和不同 Task 造成浏览凌乱,同一个栈的跳转,咱们都在本利用内进行,故业务中的场景等价于上面的【场景 0】

【场景 0】把业务中 B - 2 到 C - 3 的利用间跳转改为 B - 2 到 B - 3 的利用内跳转

// B- 2 跳转 B -3
public static void jumpTo_B_3_ByAction_Null(Context context) {Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3");
    context.startActivity(intent);
}

如下图,A- 1 设置 NEW_TASK 跳转 B -2,再跳转 B -3,最终设置 NEW_TASK 想跳转 B -2。尽管跳 C - 3 改为了跳 B -3,但与之前问题的体现统一,没有反馈,停留在 B -3。

有的读者会指出这样的问题:如果同一个利用内应用 NEW_TASK 跳转,而不指定指标的 taskAffinity 属性,理论是无奈在新 Task 中启动的。请大家疏忽该问题,能够认为笔者的操作是曾经加了 taskAffinity 的,这对最终后果并没有影响。

【场景 1】如果指标 Task 和起源 Task 不是同一个,状况是否会如官网文档所说复用已有的 Task 并展现最近状态?咱们改为 B - 3 启动一个新 Task 的新 Activity C-4,再通过 C - 4 跳回 B -2

// B- 3 跳转 C -4
public static void jumpTo_C_4_ByAction_New(Context context) {Intent intent = new Intent("com.zkp.task.ACTION_TO_C_PAGE4");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}
// C- 4 跳转 B -2
public static void jumpTo_B_2_ByAction_New(Context context) {Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

如下图,A- 1 设置 NEW_TASK 跳转 B -2,再跳转 B -3,再设置 NEW_TASK 跳转 C -4,最终设置 NEW_TASK 想跳转 B -2。

料想的后果是:不会跳到 B -2,而是跳到它所在 Task 的顶层 B -3。

理论的后果是:与预期统一,的确是跳到了 B -3。

【场景 2】把场景 1 稍做批改:C- 4 到 B - 2 时,咱们不通过 action 来跳,改为通过setClassName 跳转

// C- 4 跳转 B -2
public static void jumpTo_B_2_ByPath_New(Context context) {Intent intent = new Intent();
    intent.setClassName("com.zkp.b", "com.zkp.b.Activity2"); // 间接设置 classname, 不通过 action
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

如下图,A- 1 设置 NEW_TASK 跳转 B -2,再跳转 B -3,再设置 NEW_TASK 跳转 C -4,最终设置 NEW_TASK 想跳转 B -2。

料想的后果是:与场景 0 统一,会跳到 B - 2 所在 Task 的已有顶层 B -3。

理论的后果是:在已有的 Task2 中,产生了一个新的 B - 2 实例。

仅仅是扭转了一下从新跳转 B - 2 的形式,成果就齐全不一样了!这与官网文档中提到该 flag 与 ”singleTask” launchMode 值产生的行为并不统一!

【场景 3】把场景 1 再做批改:这次 C - 4 不跳栈底的 B -2,改为 跳转 B -3,且还是通过 action 形式。

// C- 4 跳转 B -3
public static void jumpTo_B_3_ByAction_New(Context context) {Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

如下图,A- 1 设置 NEW_TASK 跳转 B -2,再跳转 B -3,再设置 NEW_TASK 跳转 C -4,最终设置 NEW_TASK 想跳转 B -3。

料想的后果是:与场景 0 统一,会跳到 B - 2 所在 Task 的顶层 B -3。

理论的后果是:在已有的 Task2 中,产生了一个新的 B - 3 实例。

不是说好的,Activity 曾经存在时,展现其所在 Task 的最新状态吗?明明 Task2 中曾经有了 B -3,并没有间接展现它,而是生成了新的 B - 3 实例。

【场景 4】既然 Activity 没有被复用,那 Task 肯定会被复用吗?把场景 3 稍做批改,间接给 B - 3 指定一个独自的 affinity。

<activity
    android:name=".Activity3"
    android:exported="true"
    android:taskAffinity="b3.task"><!-- 指定了亲和性标识 -->
    <intent-filter>
        <action android:name="com.zkp.task.ACTION_TO_B_PAGE3" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

如下图,A- 1 设置 NEW_TASK 跳转 B -2,再跳转 B -3,再设置 NEW_TASK 跳转 C -4,最终设置 NEW_TASK 想跳转 B -3。

——这次,连 Task 也不会再被复用了……Activity3 在一个新的栈中被实例化了。

再回看官网的正文,就会显得十分不精确,甚至会让开发者对该局部的认知产生严重错误!略微扭转过程中的某个毫无关联的属性(如跳转指标、跳转形式……),就会产生很大差别。

在看 flag 相干正文时,咱们要建立一个意识:Task 和 Activity 跳转的实际效果,是 launchMode、taskAffinity、跳转形式、Activity 在 Task 中的层级等属性综合作用的后果,不要置信“一面之词”。

回到问题自身,到底是哪些起因造就了下面的不同成果呢?只有源码最值得信赖了。

三、场景剖析与源码摸索

本文以 Android 12.0 源码为根底,进行探索。上述场景在不同 Android 版本上的体现是统一的。

3.1 源码调试注意事项

源码的调试办法,许多文章曾经有了具体的教学,本文不再赘述。此处只简略总结其中 须要留神的事项

  1. 下载模拟器时,不要应用 Google Play 版本,该版本相似 user 版本,无奈抉择 system_process 过程进行断点。
  2. 即便是 Google 官网模拟器和源码,在断点时,也会有行数重大不对应的状况(比方:模拟器理论会运行到办法 A,但在源码中打断点时,理论不能定位到办法 A 的对应行数),该问题并没有很好的解决办法,只能尽量躲避,如使模拟器版本与源码版本保持一致、多打一些断点减少要害行数被定位到的几率。

3.2 初步断点,明确启动后果

以【场景 0】为例,咱们初步确认一下,为什么 B - 3 跳转 B - 2 会无反馈,零碎是否告知了起因。

3.2.1 明确启动后果及其起源

在 Android 源码的断点调试中,常见的有两类过程:利用过程和 system_process 过程。

在利用过程中,咱们能获取到利用启动后果的状态码 result,这个 result 用来通知咱们启动是否胜利。波及堆栈如下图(标记 1)所示:

Activity 类::startActivity() → startActivityForResult() → Instrumentation 类::execStartActivity(),返回值 result 则是 ATMS(ActivityTaskManagerService)执行的后果。

如上图(标记 2)标注,ATMS 类 ::startActivity() 办法,返回了 result=3。

在 system_process 过程中,咱们看一下这个 result= 3 是怎么被赋值的。略去具体断点步骤,理论堆栈如下图(标注 1)所示:

ATMS 类 ::startActivity() → startActivityAsUser() → ActivityStarter 类::execute() → executeRequest() → startActivityUnchecked() → startActivityInner() → recycleTask(),在 recycleTask() 中返回了后果。

如上图(标注 2)所示,result 在 mMovedToFront=false 时被赋值,即 result=START_DELIVERED_TO_TOP=3,而 START_SUCCESS= 0 才代表创立胜利。

看一下源码中对 START_DELIVERED_TO_TOP 的阐明,如下图:

Result for IActivityManaqer.startActivity: activity wasn’t really started, but the given Intent was given to the existing top activity.

(IActivityManaqer.startActivityActivity 的后果:Activity 并未真正启动,但给定的 Intent 已提供给现有的顶层 Activity。)

“Activity 并未真正启动”——是的,因为能够复用

“给定的 Intent 已提供给现有的顶层 Activity”——理论没有,顶层 Activity3 并没有收到任何回调,onNewIntent()未执行,甚至尝试通过 Intent::putExtra() 传入新的参数,Activity3 也没有收到。官网文档又带给了咱们一个疑难点?咱们把这个问题记录下来,在前面剖析。

满足什么条件,才会造成 START_DELIVERED_TO_TOP 的后果呢?笔者的思路是,通过与失常启动流程比照,找出差别点。

3.3 过程断点,摸索启动流程

一般来说,在定位问题时,咱们习惯通过后果反推起因,但反推的过程只能关注到与问题强关联的代码分支,并不能能使咱们很好地理解全貌。

所以,本节内容咱们通过程序浏览的办法,正向介绍 startActivity 过程中与上述【场景 01234】强相干的逻辑。再次简述一下:

  1. 【场景 0】同一个 Task 内,从顶部 B - 3 跳转 B -2——停留在 B -3
  2. 【场景 1】从另一个 Task 内的 C -4,跳转 B -2——跳转到 B -3
  3. 【场景 2】把场景 1 中,C- 4 跳转 B - 2 的形式改为 setClassName()——创立新 B - 2 实例
  4. 【场景 3】把场景 1 中,C- 4 跳转 B - 2 改为跳转 B -3——创立新 B - 3 实例
  5. 【场景 4】给场景 3 中的 B -3,指定 taskAffinity——创立新 Task 和新 B - 3 实例

3.3.1 流程源码概览

源码中,整个启动流程很长,波及的办法和逻辑也很多,为了便于大家理清办法调用程序,不便后续内容的浏览,笔者将本文波及到的要害类及办法调用关系整顿如下。

后续浏览中如果不分明调用关系,能够返回这里查看:

// ActivityStarter.java
 
    ActivityStarter::execute() {executeRequest(intent) {startActivityUnchecked() {startActivityInner();
        }
    }
    ActivityStarter::startActivityInner() {setInitialState();
        computeLaunchingTaskFlags();
        Task targetTask = getReusableTask(){findTask();
        }
        ActivityRecord targetTaskTop = targetTask.getTopNonFinishingActivity();
        if (targetTaskTop != null) {startResult = recycleTask() {setTargetRootTaskIfNeeded();
                complyActivityFlags();
                if (mAddingToTask) {return START_SUCCESS; //【场景 2】【场景 3】从 recycleTask()返回
                }
                resumeFocusedTasksTopActivities()
                return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;//【场景 1】【场景 0】从 recycleTask()返回}
        } else {mAddingToTask = true;}
        if (startResult != START_SUCCESS) {return startResult;//【场景 1】【场景 0】从 startActivityInner()返回
        }
        deliverToCurrentTopIfNeeded();
        resumeFocusedTasksTopActivities();
        return startResult;
    }

3.3.2 要害流程剖析

(1)初始化

startActivityInner()是最次要的办法,如下列几张图所示,该办法会率先调用 setInitialState(),初始化各类全局变量,并调用 reset(),重置 ActivityStarter 中各种状态。

在此过程中,咱们记下两个要害变量 mMovedToFront 和 mAddingToTask,它们均在此被重置为 false。

其中,mMovedToFront 代表当 Task 可复用时,是否须要将指标 Task 挪动到前台;mAddingToTask 代表是否要将 Activity 退出到 Task 中。

(2)计算确认启动时的 flag

该步骤会通过 computeLaunchingTaskFlags()办法,依据 launchMode、起源 Activity 的属性等进行初步计算,确认 LaunchFlags。

此处重点解决起源 Activity 为空的各类场景,与咱们上文中的几种场景无关,故不再开展解说。

(3)获取能够复用的 Task

该步骤通过调用 getReusableTask()实现,用来查找有没有能够复用的 Task。

先说论断:场景 0123 中,都能获取到能够复用的 Task,而场景 4 中,未获取到可复用的 Task。

为什么场景 4 不能够复用?咱们看一下 getReusableTask()的要害实现。

上图(标注 1)中,putIntoExistingTask 代表是否能放入曾经存在的 Task。当 flag 含有 NEW_TASK 且不含 MULTIPLE_TASK 时,或指定了 singleInstance 或 singleTask 的 launchMode 等条件,且没有指定 Task 或要求返回后果 时,场景 01234 均满足了条件。

而后,上图(标注 2)通过 findTask()查找能够复用的 Task,并将过程中找到的栈顶 Activity 赋值给 intentActivity。最终,上图(标注 3)将 intentActivity 对应的 Task 作为后果。

findTask()是怎么查找哪个 Task 能够复用呢?

次要是确认两种后果 mIdealRecord——“现实的 ActivityRecord”和 mCandidateRecord——” 候选的 ActivityRecord”,作为 intentActivity,并取 intentActivity 对应的 Task 作为复用 Task。

什么 ActivityRecord 才是现实或候选的 ActivityRecord 呢?在 mTmpFindTaskResult.process()中确认。

程序会将以后零碎中所有的 Task 进行遍历,在每个 Task 中,进行如上图所示的工作——将 Task 的底部 Activity realActivity 与指标 Activity cls 进行比照。

场景 012 中,咱们想跳转 Activity2,即 cls 是 Activity2,与 Task 底部的 realActivity2 雷同,则将该 Task 顶部的 Activity3 r 作为“现实的 Activity”;

场景 3 中,咱们想跳转 Activity3,即 cls 是 Activity3,与 Task 底部的 realActivity2 不同,则进一步判断 task 底部 Activity2 与指标 Activity3 的栈亲和行,具备雷同亲和性,则将 Task 的顶部 Activity3 作为“候选 Activity”;

场景 4 中,所有条件都不满足,最终没能找到可复用的 Task。在执行完 getReusableTask()后将 mAddingToTask 赋值为 true

由此,咱们就能解释【场景 4】中,新建了 Task 的景象。

(4)确定是否须要将指标 Task 挪动到前台

如果存在可复用的 Task,场景 0123 会执行 recycleTask(),该办法中会相继进行几个操作:setTargetRootTaskIfNeeded()、complyActivityFlags()。

首先,程序会执行 setTargetRootTaskIfNeeded(),用来确定是否须要将指标 Task 挪动到前台,应用 mMovedToFront 作为标识。

在【场景 123】中,起源 Task 和指标 Task 是不同的,differentTopTask 为 true,再通过一系列 Task 属性比照,可能得出 mMovedToFront 为 true;

而场景 0 中,起源 Task 和指标 Task 雷同,differentTopTask 为 false,mMovedToFront 放弃初始的 false。

由此,咱们就能解释【场景 0】中,Task 不会产生切换的景象。

(5)通过比照 flag、Intent、Component 等确认是否要将 Activity 退出到 Task 中

还是在【场景 0123】中,recycleTask()会继续执行 complyActivityFlags(),用来确认是否要将 Activity 退出到 Task 中,应用 mAddingToTask 作为标识。

该办法会对 FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TASK、FLAG_ACTIVITY_CLEAR_TOP 等诸多 flag、Intent 信息进行一系列判断。

上图(标注 1)中,会先判断后续是否须要重置 Task,resetTask,判断条件则是 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,显然,场景 0123 的 resetTask 都为 false。继续执行。

接着,会有多种条件判断按程序执行。

在【场景 3】中,指标 Component(mActivityComponent)是 B -3,指标 Task 的 realActivity 则是 B -2,两者不雷同,进入了 resetTask 相干的判断(标注 2)。

之前 resetTask 曾经是 false,故【场景 3】的 mAddingToTask 脱离原始值,被置为 true。

在【场景 012】中,绝对比的两个 Activity 都是 B -2(标注 3),能够进入下一级判断——isSameIntentFilter()。

这一步判断的内容就很显著了,指标 Activity2 的已有 Intent 与 新的 Intent 做比照。很显然,场景 2 中因为改为了 setClassName 跳转,Intent 天然不一样了。

故【场景 2】的 mAddingToTask 脱离原始值,被置为 true。

总结看一下:

【场景 123】的 mMovedToFront 最先被置为 true,而【场景 0】经验重重考验,放弃初始值为 false。

——这意味着当有可复用 Task 时,【场景 0】不须要把 Task 切换到前列;【场景 123】须要切换到指标 Task。

【场景 234】的 mAddingToTask 别离在不同阶段被置为 true,而【场景 01】,始终保持初始值 false。

——这意味着,【场景 234】须要将 Activity 退出到 Task 中,而【场景 01】不再须要。

(6)理论启动 Activity 或间接返回后果

被启动的各个 Activity 会通过 resumeFocusedTasksTopActivities()等一系列操作,开始真正的启动与生命周期的调用。

咱们对于上述各个场景的摸索曾经失去答案,后续流程便不再关注。

四、问题修复及遗留问题解答

4.1 问题修复

既然后面总结了这么多必要条件,咱们只须要毁坏其中的某些条件,就能够修复业务中遇到的问题了,简略列举几个的计划。

  • 计划一:批改 flag。B- 3 跳转 B - 2 时,减少 FLAG_ACTIVITY_CLEAR_TASK 或 FLAG_ACTIVITY_CLEAR_TOP,或者间接不设置 flag。教训证可行。
  • 计划二:批改 intent 属性,即【场景 2】。A- 1 通过 action 形式隐式跳转 B -2,则 B - 3 能够通过 setClassName 形式,或批改 action 内属性的形式跳转 B -2。教训证可行。
  • 计划三:提前移除 B -2。B- 2 跳转 B - 3 时,finish 掉 B -2。须要留神的是,finish()要在 startActivity()之前执行,以防止遗留的 ActivityRecord 和 Intent 信息对后续跳转的影响。尤其是当你把 B - 2 作为本人利用的 deeplink 散发 Activity 时,更值得警觉。

4.2 遗留问题

还记得咱们在文章开始的某个纳闷吗,为什么没有回调 onNewIntent()?

onNewIntent() 会通过 deliverNewIntent()触发,而 deliverNewIntent()仅通过以下两个办法调用。

complyActivityFlags()就是上文 3.3.1.5 中咱们着重探讨的办法,能够发现 complyActivityFlags()中所有可能调用 deliverNewIntent()的条件均被完满避开了。

而 deliverToCurrentTopIfNeeded()办法则如下图所示。

mLaunchFlags 和 mLaunchMode,无奈满足条件,导致 dontStart 为 false,无缘 deliverNewIntent()。

至此,onNewIntent()的问题失去解答。

五、结语

通过一系列场景假如,咱们发现了许多出其不意的景象:

  1. 文档提到 FLAG_ACTIVITY_NEW_TASK 等价于 singleTask,与事实并不齐全如此,只有与其余 flag 搭配能力达到类似的成果。这一 flag 的正文十分全面,甚至会引发误会,繁多因素无奈决定整体体现。
  2. 官网文档提到
  3. START_DELIVERED_TO_TOP 会将新的 Intent 传递给顶层 Activity,但事实上,并不是每一种 START_DELIVERED_TO_TOP 都会把新的 Intent 从新散发。
  4. 同一个栈底 Activity,前后两次都通过 action 或都通过 setClassName 跳转到时,第二次跳转居然会失败,而两次用不同形式跳转时,则会胜利。
  5. 单纯应用 FLAG_ACTIVITY_NEW_TASK 时,跳栈底 Activity 和跳同栈内其余 Activity 的成果天壤之别。

业务中遇到的问题,归根结底就是对 Android 栈机制不够理解造成的。

在面对栈相干的编码时,开发者务必要想分明,承当新开利用栈的 Activty 在利用全局承当怎么的使命,要对 Task 历史、flag 属性、launchMode 属性、Intent 内容等全面评估,审慎参考官网文档,能力防止栈陷阱,达成现实牢靠的成果。

正文完
 0