0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

带大家探究Activity启动前的一项重要的工作—栈校验

OSC开源社区 来源:vivo互联网威廉希尔官方网站 2023-04-19 09:36 次阅读

本文从一例业务中遇到的问题出发,以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是可以的。

8596f8dc-ddf5-11ed-bfe3-dac502259ad0.gif

1.2 问题代码

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

  
      
            
                
                
            
        
 
 
        
            
                
                
            
        
 
 
        
            
                
                
            
        

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。

依据经验,我们推测与栈有关,尝试将跳转前栈的状态打印出来,如下图。

85bf2226-ddf5-11ed-bfe3-dac502259ad0.png

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

85ccfef0-ddf5-11ed-bfe3-dac502259ad0.png

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

85d358ae-ddf5-11ed-bfe3-dac502259ad0.png

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

看一下FLAG_ACTIVITY_NEW_TASK的官方注释和代码注释,如下图:

85d9cfcc-ddf5-11ed-bfe3-dac502259ad0.png

85e44588-ddf5-11ed-bfe3-dac502259ad0.png

重点关注这一段:

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。

85ee18c4-ddf5-11ed-bfe3-dac502259ad0.png

有的读者会指出这样的问题:如果同一个应用内使用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。

85f5b91c-ddf5-11ed-bfe3-dac502259ad0.png

预想的结果是:不会跳到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。

85fe5db0-ddf5-11ed-bfe3-dac502259ad0.png

预想的结果是:与场景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。

860403b4-ddf5-11ed-bfe3-dac502259ad0.png

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

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

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

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

 
    
        
        
    

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

8611a4f6-ddf5-11ed-bfe3-dac502259ad0.png

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

再回看官方的注释,就会显得非常不准确,甚至会让开发者对该部分的认知产生严重错误!稍微改变过程中的某个毫无关联的属性(如跳转目标、跳转方式……),就会产生很大差异。

在看flag相关注释时,我们要树立一个意识:Task和Activity跳转的实际效果,是launchMode、taskAffinity、跳转方式、Activity在Task中的层级等属性综合作用的结果,不要相信“一面之词”。

回到问题本身,究竟是哪些原因造就了上面的不同效果呢?只有源码最值得信赖了。

三、场景分析与源码探索

本文以Android 12.0源码为基础,进行探究。上述场景在不同Android版本上的表现是一致的。

3.1 源码调试注意事项

源码的调试方法,许多文章已经有了详细的教学,本文不再赘述。此处只简单总结其中需要注意的事项

下载interwetten与威廉的赔率体系 器时,不要使用Google Play版本,该版本类似user版本,无法选择system_process进程进行断点。

即使是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)执行的结果。

8618e9aa-ddf5-11ed-bfe3-dac502259ad0.png

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

在system_process进程中,我们看一下这个result=3是怎样被赋值的。略去详细断点步骤,实际堆栈如下图(标注1)所示:

ATMS类::startActivity() →startActivityAsUser()

ActivityStarter类::execute()

→executeRequest()

→startActivityUnchecked()

→ startActivityInner()

→ recycleTask(),在recycleTask()中返回了结果。

862c2d1c-ddf5-11ed-bfe3-dac502259ad0.png

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

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

863cd3d8-ddf5-11ed-bfe3-dac502259ad0.png

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】强相关的逻辑。再次简述一下:

【场景0】同一个Task内,从顶部B-3跳转B-2——停留在B-3

【场景1】从另一个Task内的C-4,跳转B-2——跳转到B-3

【场景2】把场景1中,C-4跳转B-2的方式改为setClassName()——创建新B-2实例

【场景3】把场景1中,C-4跳转B-2改为跳转B-3——创建新B-3实例

【场景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中。

864253e4-ddf5-11ed-bfe3-dac502259ad0.png

864919c2-ddf5-11ed-bfe3-dac502259ad0.png

8653659e-ddf5-11ed-bfe3-dac502259ad0.png

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

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

此处重点处理来源Activity为空的各类场景,与我们上文中的几种场景无关,故不再展开讲解。

(3)获取可以复用的Task

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

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

为什么场景4不可以复用?我们看一下getReusableTask()的关键实现。

8659d1b8-ddf5-11ed-bfe3-dac502259ad0.png

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

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

findTask()是怎样查找哪个Task可以复用呢?

866f43a4-ddf5-11ed-bfe3-dac502259ad0.png

主要是确认两种结果mIdealRecord——“理想的ActivityRecord” 和 mCandidateRecord——"候选的ActivityRecord",作为intentActivity,并取intentActivity对应的Task作为复用Task。

什么ActivityRecord才是理想或候选的ActivityRecord呢?

在mTmpFindTaskResult.process()中确认。

868a0702-ddf5-11ed-bfe3-dac502259ad0.png

程序会将当前系统中所有的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作为标识。

86b002a4-ddf5-11ed-bfe3-dac502259ad0.png

86b80166-ddf5-11ed-bfe3-dac502259ad0.png

在【场景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信息进行一系列判断。

86d388e6-ddf5-11ed-bfe3-dac502259ad0.png

上图(标注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()。

86ecde0e-ddf5-11ed-bfe3-dac502259ad0.png

86ecde0e-ddf5-11ed-bfe3-dac502259ad0.png

86fb41a6-ddf5-11ed-bfe3-dac502259ad0.png

这一步判断的内容就很明显了,目标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()仅通过以下两个方法调用。

870a4ba6-ddf5-11ed-bfe3-dac502259ad0.png

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

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

8713bd26-ddf5-11ed-bfe3-dac502259ad0.png

mLaunchFlags和mLaunchMode,无法满足条件,导致dontStart为false,无缘

deliverNewIntent()。

至此,onNewIntent()的问题得到解答。

五、结语

通过一系列场景假设,我们发现了许多出乎意料的现象:

文档提到FLAG_ACTIVITY_NEW_TASK等价于singleTask,与事实并不完全如此,只有与其他flag搭配才能达到相似的效果。这一flag的注释非常片面,甚至会引发误解,单一因素无法决定整体表现。

官方文档提到

START_DELIVERED_TO_TOP会将新的Intent传递给顶层Activity,但事实上,并不是每一种START_DELIVERED_TO_TOP都会把新的Intent重新分发。

同一个栈底Activity,前后两次都通过action或都通过setClassName跳转到时,第二次跳转竟然会失败,而两次用不同方式跳转时,则会成功。

单纯使用FLAG_ACTIVITY_NEW_TASK时,跳栈底Activity和跳同栈内其他Activity的效果大相径庭。

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

在面对栈相关的编码时,开发者务必要想清楚,承担新开应用栈的Activty在应用全局承担怎样的使命,要对Task历史、flag属性、launchMode属性、Intent内容等全面评估,谨慎参考官方文档,才能避免栈陷阱,达成理想可靠的效果。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Android
    +关注

    关注

    12

    文章

    3937

    浏览量

    127470
  • Flag
    +关注

    关注

    0

    文章

    12

    浏览量

    8149
  • 模拟器
    +关注

    关注

    2

    文章

    877

    浏览量

    43248
  • ATMS
    +关注

    关注

    0

    文章

    4

    浏览量

    8524

原文标题:明修"栈"道——越过Android启动栈陷阱

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    带大家认识什么是USB.PD协议

    带大家认识 USB PD协议
    的头像 发表于 08-19 11:29 2.4w次阅读
    <b class='flag-5'>带大家</b>认识什么是USB.PD协议

    下图中的与打开文件相连的枚举常量是自己一项一项编辑...

    下图中的与打开文件相连的枚举常量是自己一项一项编辑的还是自动就有的?
    发表于 03-17 21:39

    请教Ring控件删除最后一项

    请问:Ring控件,删除其下拉内容最后一项,显示不正常(如显示为:),怎样纠正?
    发表于 04-12 17:09

    关于电机驱动原理的动画,哪位大佬可以分享一项吗?

    关于电机驱动原理的动画,哪位大佬可以分享一项吗?
    发表于 10-13 06:17

    利用stm32来探究下程序运行时的空间是怎么分布的

    今天我们利用stm32来探究下程序运行时的空间是怎么分布的,为什么空间设置不合理时会有溢出导致程序崩溃下面是我们要使用的测试代码,先
    发表于 01-20 08:20

    漆包线标准中的一项差距

    漆包线标准中的一项差距:我国漆包线标准是根据IEC标准制定的, 而IEC标准在国际上并不是最先进的标准。在这里只讨论IEC漆包线标准与先进标准相比的一项差距在西德的些公司
    发表于 06-12 20:55 13次下载

    节能减排是一项持续性工作

    节能减排是一项持续性工作 PCB行业是个有污染的行业,生产中会消耗大量的水、电等资源和能源,产生废水、废气和废渣,这些污染物只有得到很好的处理,才能减少
    发表于 11-26 10:47 464次阅读

    Android Activity启动模式的详解

    singleInstance:和singleTask差不多,唯不同的是singleInstance Activity实例的Task只能存放个该模式的Activity实例,例如Qac
    的头像 发表于 04-18 15:47 3977次阅读

    全自动并联水表耐压校验检定装置的工作原理及设计

    今天为大家介绍一项国家发明授权专利——全自动并联水表耐压校验检定装置。该专利由杭州水表有限公司申请,并于2017年5月24日获得授权公告。
    发表于 08-26 09:51 2532次阅读
    全自动并联水表耐压<b class='flag-5'>校验</b>检定装置的<b class='flag-5'>工作</b>原理及设计

    如何进行Android中Task任务的分配

    只是针对Activity而言的。 Activity有不同的启动模式, 可以影响到task的分配 Task,简单的说,就是组以的模式聚集在
    发表于 07-03 17:42 0次下载
    如何进行Android中Task任务<b class='flag-5'>栈</b>的分配

    沃尔玛在休斯顿启动一项试点计划 测试无人驾驶送货服务

    作为全美最大的零售商,沃尔玛正在休斯顿启动一项试点计划:使用Nuro的自动驾驶R1车将食品从“特定”商店运送到选择加入该计划的顾客手上。沃尔玛并没有说明客户的注册方式,但是休斯顿市民期望这项服务能在“未来几周”就开始。
    发表于 12-13 14:00 441次阅读

    一项新的研究表明,免费上网应该成为一项基本人权

    一项新的研究表明,免费上网必须被视为一项人权,因为无法上网的人们(尤其是在发展中国家)缺乏有意义的方式来影响全球参与者塑造他们的日常生活。
    的头像 发表于 04-21 17:35 3014次阅读

    关于一项改进Transformer的工作

    NAACL2021中,复旦大学大学数据智能与社会计算实验室(Fudan DISC)和微软亚洲研究院合作进行了一项改进Transformer的工作,论文的题目为:Mask Attention
    的头像 发表于 04-22 10:46 3315次阅读
    关于<b class='flag-5'>一项</b>改进Transformer的<b class='flag-5'>工作</b>

    android的Activity应用

    android的Activity应用(电力电子电源威廉希尔官方网站 及应用课后答案)-android的Activity应用,有需要的可以参考!
    发表于 08-31 13:22 1次下载
    android的<b class='flag-5'>Activity</b>应用

    Activity初学乍练

    本节开始讲解Android的四大组件之Activity(活动),先来看下官方对于Activity的介绍:PS:官网文档:Activity
    的头像 发表于 04-01 22:28 1251次阅读
     <b class='flag-5'>Activity</b>初学乍练