前言
在开发小App的时候,用到了Android中Support Actionbar并且为了方便用户返回上层Activity,因此开启了Up键。在本人的认知中,原先认为Up键与手势返回键或者系统导航栏都能够达到相同的返回上一级Activity的效果,即两者的内部函数调用逻辑和达到的效果是统一的。但在真实的开发过程中,也证明了本人的理解是错误的,正确的理解应为:ActionBar的Up键会直接重新创建与之相关联的Parent Activity,而系统返回键则会重新唤醒返回栈中的Activity,而非重新创建。
之所以会对这两者产生较大的理解错误,原因在于两个Activity相互切换的过程中,所使用的Live Data出现了重复执行的情况。假设我们现在有两个Activity A和B,现在有以下切换流程:A->B->A,那么,Activity A中的Live Data应该只会有两种情况进行更新逻辑代码的执行:
Activity A第一次创建时
Activity B产生了数据对Activity A中的Live Data数据进行了更新
但是,在本人的实验中发现即使Activity B中没有执行任何操作,使用Up键返回Activity A同样会触发A中的Live Data更新逻辑代码。因此,这证明了Up键的返回逻辑与常规的系统返回键不同,本文也以此对两者的区别进行阐述与分析。
生命周期的不同
由于在发现问题的初期,本人并不确定问题的原因,因此,我们先从两个Activity的生命周期出发进行问题的排查,通过对Up键和Back键进行返回操作,记录两者返回Activity A时,A的生命周期函数调用,就可以确定问题。
首先是Back键,在执行A->B->A这两次操作的时候,生命周期的调用过程为:onCreate()
->onStart()
->onResume()
->onPause()
->onStop()
->从B返回A->onRestart()
->onStart()
->onResume()
。
其次是Up键,生命周期的调用过程为:onCreate()
->onStart()
->onResume()
->onPause()
->onStop()
->从B返回A->onDestroy()
->onCreate()
->onStart()
->onResume()
。
可以看出,Up键调用时会让原先存在于返回栈中的Activity A销毁,而后重新创建。而返回键则是直接调用返回栈中存在的Activity A,并非销毁原有的A重新创建。
系统返回键Back的调用逻辑
当用户按下系统返回键或者订制UI的返回手势时,当前Activity会调用ComponentActivity
类中的onBackPressed()
方法,代码如下:
1 | /** |
从注释中能够清晰的看出这一函数的作用,提供给开发者一个处理默认返回动作逻辑的机会,也就是在返回前,开发者可以在这一函数中进行相关的代码逻辑编写,但需要注意,这一操作是在主线程中执行的。
这里由于本人没有特殊的需求,所以就没有重载这一函数,那么按照对应的逻辑,用户的返回动作会由mOnBackPressedDispatcher
调度器来完成返回动作的分发,在调度器所在的类OnBackPressedDispatcher
可以看到如下关于调度器的解释:
1 | /** |
调度器主要用于处理注册了返回回调函数组件的实例,并提供给这些组件进行回调函数的分发。
1 | /** |
在调度器执行onBackPressed
函数时,它会对已经注册过回调事件的实例进行迭代查找,当找到组件能够处理回调时间时,则会将返回实践交由当前回调事件所在的组件来继续处理返回操作。
之所以这样做,在调度器开头的注释中也有给出对应的使用场景,即在Fragment中,如果需要对用户返回动作进行处理,则可以添加回调事件从而达到这一目的。
1 | /** |
上述的内容主要阐述了当用户按下系统返回键后,我们需要进行返回前逻辑操作时的主要处理方式。而当我们没有这样的需求时,Activity又是如何进行返回操作的呢?相信看到调度器中的mFallbackOnBackPressed
变量的读者一定会有疑惑,这一个Runnable
对象是用于创建一个新的返回键分发器所设计的,那么又是谁持有了这个分发器对象呢?
首先,我们可以确定一下mFallbackOnBackPressed
的生成函数:
1 | /** |
可以看到mFallbackOnBackPressed
是作为调度器的构造函数传入进来的,所以我们可以肯定传入这一对象的一定是创建了调度器的组件,那么,一般情况下都是当前页面对应的Activity。通过ComponentActivity
的源码可以看到Activity生成调度器的方法:
1 | public class ComponentActivity extends androidx.core.app.ComponentActivity implements |
当我们理解了整个返回事件的处理思路后,最后我们来完善对于Activity默认处理返回事件逻辑的理解。
1 | /** |
在Activity
类中我们可以找到Activity关闭的逻辑,如上代码所示,系统的返回函数考虑了Fragment和普通Activity两种情况,并且,对当前页是否为App首页进行了区分,常规的非首页Activity会使用finishAfterTransition()
进行Activity的finish()
操作,首页则会通知Activity的任务管理器进行当前App的拦截或关闭操作。
由此,这一个连环的返回事件调用终于被我们所理清,如果调度器中有组件注册了回调事件来处理返回动作所对应的逻辑,那么,返回动作将会由这一回调事件所对应的函数来进行消费;如果没有注册的组件进行返回事件的处理,系统会自动将返回事件交给当前Activity来进行处理。
上述便是整个系统返回键触发后的整个系统操作流程。接下来,我们将对Up键的逻辑进行整理,以便于我们对二者的特点进行掌握。
ActionBar中的Up键调用逻辑
与系统返回键的触发逻辑不同,当用户点击了ActionBar中的Up键时,系统会默认调用supportNavigateUpTo()
方法。
1 | /** |
通过注释的描述解开了我自己的疑问,Up键的设计与系统返回键存在着本质上的不同,Up键的跳转并非上一个Activity,它类似于站点导航的作用,我们可以通过自行定义导航关系来达到跳跃返回的功能,但也需要注意遵循对应的设计原则。
在源码中,可以清晰的看出使用Up键返回会携带者Intent对象往NavUtils
走。在注释中,也解释了这个Intent的作用——Up键所指向的目的地(目标Activity)。
在进入到NavUtils.navigateUpTo()
方法后,可以看到Up键会启动目标Activity,并且将当前Activity之上的所有Activity都清空,使目标Activity存在于顶部。例如,当前的返回栈中有ABCD这四个Activity对象,当Up键执行创建B实例后,会将CD弹栈,并且重新创建B对象,这也就是FLAG_ACTIVITY_CLEAR_TOP
的作用。
1 | /** |
相比于Back键,Up键的调用逻辑清晰且简单。这也解释了为何使用Up键时会存在Live Data重新执行更新逻辑的原因。
总结
系统返回键的功能更为强大,同时,其处理逻辑也更为复杂。而Up键则能够更快速的构建App中的路由导航功能,帮助用户快速从众多不重要的Activity返回主要的功能页面,减少用户的操作。二者重要的区别在于:
系统返回键Back在默认情况下返回上一个Activity不会重新创建对象,而Up键则会默认创建新对象,并且会清空目标Activity上的所有Activity对象。
系统返回键的功能更强大,对于Fragment处理返回情况的处理更有帮助,而Up键更多的作用在于锚定App中重要的功能页面,并完成页面间的导航关系,可以理解为前端页面中的面包屑组件。