参考文档:
android:enableOnBackInvokedCallback是什么?
用于选择是否开启预测性返回手势
OnBackPressedCallback是什么?
AndroidX 实现自定义手势导航处理功能 (google推荐方案)AndroidX 1.6.0-alpha05
如果只想迁移onBackPressed至新的api可以不开启enableOnBackInvokedCallback;但是如果想接入预测性返回手势的能力,必须开启enableOnBackInvokedCallback

问:为什么需要使用AndroidX 1.6.0-alpha05以上呢?
因为之前的版本有bug

OnBackInvokedCallback是什么?
非AndroidX但是希望添加对预测性返回手势的支持,google提供的降级方案,AndroidX中也能用

需要配合mainfest属性使用:android:enableOnBackInvokedCallback="true"
application中只对API33(Android 13)及以上生效 activity中只对API34(Android 14)及以上生效
会导致(Activity/dialog/Window等)KeyEvent事件以及onBackPressed方法不回调

Android 13
Activity
代码示例
class BackDemoActivity : AppCompatActivity() {
companion object {
const val TAG = "BackDemoTag"
}
private val onBackPressedCallback by lazy(LazyThreadSafetyMode.NONE) {
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(TAG, "BackDemoActivity:handleOnBackPressed")
//需要自己实现页面关闭逻辑
//先关闭onBackPressedCallback可用
isEnabled = false
//然后手动执行onBackPressedDispatcher.onBackPressed()
//最后实际调用的是Activity.onBackPressed(),就实现了页面关闭,finish()也行
onBackPressedDispatcher.onBackPressed()
}
}
}
/**
* 设置onBackPressedCallback是否可用
*/
fun setOnBackPressedCallbackEnabled(enabled: Boolean) {
onBackPressedCallback.isEnabled = enabled
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_back_demo)
//这里传入LifecycleOwner,会自动判断生命周期,不用担心内存泄漏
//如果不传入LifecycleOwner,需要手动调用停用onBackPressedCallback,否则会造成内存泄漏
//注意:activity添加多个onBackPressedCallback,只会回调最后一个可用的onBackPressedCallback
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
registerOnBackInvokedCallback()
}
override fun onBackPressed() {
super.onBackPressed()
Log.d(TAG, "BackDemoActivity:onBackPressed")
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
Log.d(TAG, "BackDemoActivity:onKeyDown: event = $event")
return super.onKeyDown(keyCode, event)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
Log.d(TAG, "BackDemoActivity;onKeyUp: event = $event")
return super.onKeyUp(keyCode, event)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
Log.d(TAG, "BackDemoActivity:dispatchKeyEvent: event = $event")
return super.dispatchKeyEvent(event)
}
private var onBackInvokedCallback: OnBackInvokedCallback? = null
private fun registerOnBackInvokedCallback() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedCallback = OnBackInvokedCallback {
Log.d(TAG, "BackDemoActivity:OnBackInvokedCallback")
}.also {
//priority 0-1000000 数字最大的回调会被调用
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
it
)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun unregisterOnBackInvokedCallback() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedCallback?.let {
onBackInvokedDispatcher.unregisterOnBackInvokedCallback(it)
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
override fun onDestroy() {
//onBackInvokedDispatcher需要移除对应的onBackInvokedCallback,防止内存泄漏
unregisterOnBackInvokedCallback()
super.onDestroy()
}
}
场景一
OnBackPressedCallback:不设置
OnBackInvokedCallback:不设置
android:enableOnBackInvokedCallback="false"
返回:页面关闭,依次回调KeyEvent事件方法以及onBackPressed方法

场景二
OnBackPressedCallback:设置
OnBackInvokedCallback:不设置/设置
android:enableOnBackInvokedCallback="false"
返回:页面不会关闭,依次回调用KeyEvent事件方法以及handleOnBackPressed、onBackPressed方法

问:页面为什么不会关闭?是什么影响了页面关闭行为?
因为api中做了判断,OnBackPressedCallback回调的话就不会再执行Activity.onBackPressed().onBackInvoked(),所以页面不会关闭



场景三
OnBackPressedCallback:不设置
OnBackInvokedCallback:不设置
android:enableOnBackInvokedCallback="true"
返回:页面会关闭,之前的所有事件以及方法都不会回调
问:这里打开了enableOnBackInvokedCallback,但是OnBackInvokedCallback没有注册,为什么页面会关闭呢?
因为在Activity中的onCreate中OnBackInvokedCallback默认注册了一个系统的OnBackInvokedCallback回调,执行Activity.onBackInvoked(),所以页面会关闭


场景四
OnBackPressedCallback:不设置
OnBackInvokedCallback:设置
android:enableOnBackInvokedCallback="true"
返回:页面不会关闭,只会回调OnBackInvokedCallback方法

问:页面为什么不会关闭?是什么影响了页面关闭行为?
因为我们在onCreate中注册了自定义OnBackInvokedCallback,按照OnBackInvokedCallback回调优先级,则会调用自定义OnBackInvokedCallback,原先Activity中设置的默认OnBackInvokedCallback将不会回调
注册多个OnBackInvokedCallback,会按照优先级以及注册顺序判断调用哪一个
-
优先级:回调的优先级决定了调用顺序。优先级高的回调会先于优先级低的回调被调用。
自定义OnBackInvokedCallback的优先级的取值位0-1000000
PRIORITY_SYSTEM(值为-1)为系统级的,自定义是无法使用的
image.png 逆序调用:在同一优先级内,回调会按照注册的逆序被调用。
重复注册:如果同一个回调实例已经被注册过,无论其优先级如何,都会先被注销,然后再重新注册。

场景五
OnBackPressedCallback:设置
OnBackInvokedCallback:设置
android:enableOnBackInvokedCallback="true"
如果OnBackInvokedCallback的优先级是0
返回:页面不会关闭,只会回调handleOnBackPressed方法

如果OnBackInvokedCallback的优先级大于0
返回:页面不会关闭,只会回调OnBackInvokedCallback方法

问:为什么OnBackInvokedCallback优先级是0调用OnBackPressedCallback.handleOnBackPressed,优先级大于0调OnBackInvokedCallback?
ComponentActivity中判断了api33(Android 13)及以上版本,会设置新的OnBackInvokedCallback(优先级0),这个新的OnBackInvokedCallback实际就是调用的OnBackPressedDispatcher.onBackPressed();所以最后如果你注册的OnBackInvokedCallback的优先级大于0,那么就会回调你注册的OnBackInvokedCallback方法;如果等于0就只会调用OnBackPressedCallback.handleOnBackPressed了,当前这个前提是你的自定义OnBackInvokedCallback实在onCreate中或者之前注册的,如果在例如onResume中注册,那还是回调自定义OnBackInvokedCallback;一切调用循序都是遵循优先级+注册逆序
api33(Android 13)及以上版本会设置新的OnBackInvokedCallback(优先级0,onCreate生命周期后注册)





小结一下
-
OnBackInvokedCallback调用顺序逻辑
优先级:回调的优先级决定了调用顺序。优先级高的回调会先于优先级低的回调被调用。
逆序调用:在同一优先级内,回调会按照注册的逆序被调用。
重复注册:如果同一个回调实例已经被注册过,无论其优先级如何,都会先被注销,然后再重新注册。
-
OnBackPressedCallback调用顺序逻辑
- 调用最后一个可用的
在Activity中关闭enableOnBackInvokedCallback,是否设置OnBackPressedCallback不会影响到KeyEvent事件方法以及onBackPressed方法的回调;但是要注意的是会在onBackPressed方法前先回调OnBackPressedCallback.handleOnBackPressed且不再执行Activity.onBackPressed而导致页面不会主动关闭
-
在Activity中开启enableOnBackInvokedCallback,会导致不再回调KeyEvent事件方法以及onBackPressed方法
如果你设置了OnBackInvokedCallback且优先级是0,会回调OnBackPressedCallback.handleOnBackPressed,因为你设置的OnBackInvokedCallback的优先级跟OnBackPressedCallback中默认实现的OnBackInvokedCallback方法优先级一样,但是OnBackPressedCallback中默认实现的OnBackInvokedCallback方法后注入(在onCreate后)调用注册上去,这个方法最后实际会调用在OnBackPressedCallback.handleOnBackPressed;(注意:如果你设置OnBackInvokedCallback的生命周期不在onCreate中,而在onResume,那么回调用你设置的OnBackInvokedCallback方法)
如果你设置了OnBackInvokedCallback且优先级大于0,会回调OnBackInvokedCallback,因为你设置的优先级高于OnBackPressedCallback中实现的OnBackInvokedCallback方法的优先级
Activity+Fragment
class BackDemoFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//停用activity中的onBackPressedCallback,否则fragment中接受不到handleOnBackPressed事件
//具体业务的时候不建议直接关闭,可能会影响activity中的业务,应该让activity告诉fragment接受OnBackPressedCallback事件了
(activity as? BackDemoActivity)?.setOnBackPressedCallbackEnabled(false)
//这里传入LifecycleOwner,会自动判断生命周期,不用担心内存泄漏
//如果不传入LifecycleOwner,需要手动调用停用onBackPressedCallback,否则会造成内存泄漏
//注意:activity添加多个onBackPressedCallback,只会回调最后一个可用的onBackPressedCallback
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(BackDemoActivity.TAG, "BackDemoFragment:handleOnBackPressed")
}
})
registerOnBackInvokedCallback()
}
private var onBackInvokedCallback: OnBackInvokedCallback? = null
private fun registerOnBackInvokedCallback() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedCallback = OnBackInvokedCallback {
Log.d(BackDemoActivity.TAG, "BackDemoFragment:OnBackInvokedCallback")
}.also {
//priority 0-1000000 数字最大的回调会被调用
activity?.onBackInvokedDispatcher?.registerOnBackInvokedCallback(
10, it
)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun unregisterOnBackInvokedCallback() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedCallback?.let {
activity?.onBackInvokedDispatcher?.unregisterOnBackInvokedCallback(it)
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
override fun onDestroy() {
//onBackInvokedDispatcher需要移除对应的onBackInvokedCallback,防止内存泄漏
unregisterOnBackInvokedCallback()
super.onDestroy()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_back_demo, container, false)
}
}
场景一
OnBackPressedCallback:activity设置,fragment设置
OnBackInvokedCallback:activity不设置/设置,fragment不设置/设置
android:enableOnBackInvokedCallback="false"
fragment关闭activity:OnBackPressedCallback可用
返回:页面不会关闭,依次回调用KeyEvent事件方法以及fragment.handleOnBackPressed、onBackPressed方法

场景二
OnBackPressedCallback:activity设置,fragment设置
OnBackInvokedCallback:activity不设置/设置,fragment不设置/设置
android:enableOnBackInvokedCallback="false"
fragment未关闭activity:OnBackPressedCallback可用
返回:页面不会关闭,依次回调用KeyEvent事件方法以及activity.handleOnBackPressed、onBackPressed方法

问:为什么这里的addCallback跟以往理解不一样,一般情况下add的回调都是添加多少回调多少
这里是因为api里面做了限制,只会回调最后一个isEnable的

场景三
OnBackPressedCallback:activity不设置,fragment不设置
OnBackInvokedCallback:activity设置,fragment设置 (优先级一样)
android:enableOnBackInvokedCallback="true"
返回:页面不会关闭,只会回调Activity.OnBackInvokedCallback方法

问:Activity/Fragment都注册了OnBackInvokedCallback(优先级一样),为什么只会掉了Activity.OnBackInvokedCallback的?
跟Activty中OnBackInvokedCallback注册回调原理一致,先判断优先级后判断注册顺序;这里activity跟fragment中注册的OnBackInvokedCallback优先级一样,fragment中会先注册,所以调用fragment.OnBackInvokedCallback
场景四
OnBackPressedCallback:activity设置,fragment不设置/设置
OnBackInvokedCallback:activity设置,fragment设置 (优先级都是1)
android:enableOnBackInvokedCallback="true"
返回:页面不会关闭,只会回调Activity.OnBackPressedCallback.handleOnBackPressed方法

问:为什么调用的是Activity.handleOnBackPressed?
Activity/Fragment都注册了OnBackInvokedCallback且优先级一样,调用Fragment.OnBackInvokedCallback;但是Activity注册了OnBackPressedCallback且Fragment的OnBackInvokedCallback优先级是0,所以调用Activity.OnBackPressedCallback.handleOnBackPressed
问:如果只把Fragment.OnBackInvokedCallback优先级改成大于0,会怎么样?
会调用Fragment.OnBackInvokedCallback

问:如果只把Activity.OnBackInvokedCallback优先级改成大于0,会怎么样?
会调用Activity.OnBackInvokedCallback

小结一下
- Activity+Fragment时,对于OnBackInvokedCallback/OnBackPressedCallback的回调结果会比较复杂一下,需要深刻理解其调用原理,否则很容易出现预期外的结果
Dialog
class BackDemoDialog(context: Context) : AppCompatDialog(context) {
init {
setOnKeyListener { dialog, keyCode, event ->
Log.d(BackDemoActivity.TAG, "BackDemoDialog;onKeyUp: event = $event")
false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(BackDemoActivity.TAG, "BackDemoDialog:handleOnBackPressed")
}
})
registerOnBackInvokedCallback()
}
override fun onStart() {
super.onStart()
registerOnBackInvokedCallback()
}
private var onBackInvokedCallback: OnBackInvokedCallback? = null
private fun registerOnBackInvokedCallback() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedCallback = OnBackInvokedCallback {
Log.d(BackDemoActivity.TAG, "BackDemoDialog:OnBackInvokedCallback")
}.also {
//priority 0-1000000 数字最大的回调会被调用
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, it
)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun unregisterOnBackInvokedCallback() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedCallback?.let {
onBackInvokedDispatcher.unregisterOnBackInvokedCallback(it)
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
Log.d(BackDemoActivity.TAG, "BackDemoDialog;onKeyUp: event = $event")
return super.onKeyUp(keyCode, event)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
Log.d(BackDemoActivity.TAG, "BackDemoDialog;onKeyUp: event = $event")
return super.onKeyDown(keyCode, event)
}
override fun onBackPressed() {
super.onBackPressed()
Log.d(BackDemoActivity.TAG, "BackDemoDialog:onBackPressed")
}
override fun onStop() {
//onBackInvokedDispatcher需要移除对应的onBackInvokedCallback,防止内存泄漏
unregisterOnBackInvokedCallback()
super.onStop()
}
}
场景一
OnBackPressedCallback:不设置
OnBackInvokedCallback:不设置
android:enableOnBackInvokedCallback="false"
返回:页面关闭,依次回调KeyEvent事件方法以及onBackPressed方法

场景二
OnBackPressedCallback:设置
OnBackInvokedCallback:不设置
android:enableOnBackInvokedCallback="false"
返回:页面不关闭,依次回调用KeyEvent事件方法以及handleOnBackPressed、onBackPressed方法

场景三
OnBackPressedCallback:设置
OnBackInvokedCallback:设置
android:enableOnBackInvokedCallback="true"
返回:页面不关闭,只回调Dialog.OnBackInvokedCallback

场景四
OnBackPressedCallback:不设置
OnBackInvokedCallback:不设置
android:enableOnBackInvokedCallback="true"
返回:页面关闭,只回调Dialog.onBackPressed

问:这里开启了enableOnBackInvokedCallback,为什么是调用了Dialog.onBackPressed
跟Activity中一样,系统注册了一个默认的OnBackInvokedCallback,执行的就是Dialog.onBackPressed

小结一下
- 基本跟Activity中使用时一致的
Android 8
Android 13 以下enableOnBackInvokedCallback是不生效的,不用考虑OnBackInvokedCallback的影响,设置了也是不执行的
Activity
OnBackPressedCallback:设置
返回:页面不关闭,依次回调KeyEvent事件方法以及handleOnBackPressed,onBackPressed方法

Activity+Fragment
OnBackPressedCallback:Activity设置/Fragment设置
返回:页面不关闭,依次回调KeyEvent事件方法以及handleOnBackPressed,onBackPressed方法
handleOnBackPressed是调用Activity/Frament的则看哪个是最后一个注册且可用的

Dialog
OnBackPressedCallback:设置
返回:页面不关闭,依次回调KeyEvent事件方法以及handleOnBackPressed,onBackPressed方法

小结一下
- 对于Android13以下影响较小,只需要注意OnBackPressedCallback的回调逻辑即可
总结
-
关闭enableOnBackInvokedCallback我们需要做什么?
- 关闭的话可以维持以往api的特性,对KeyEvent事件、onBackPressed方法无影响,只是新特性预见性动画不支持
-
关闭enableOnBackInvokedCallback的话OnBackPressedCallback可以用么?
- 只有handleOnBackPressed可用,另外的跟预见性动画相关的方法(handleOnBackProgressed、handleOnBackStarted、handleOnBackCancelled)不会调用;但是需要自己处理页面关闭事件
-
打开enableOnBackInvokedCallback我们需要做什么?
如果不需要支持新特性预见性动画可以不开启
如果需要支持新特性预见性动画,那么打开后KeyEvent事件、onBackPressed方法将不再回调,必须对相关逻辑代码进行迁移,建议是迁移到OnBackPressedCallback中;不建议使用OnBackInvokedCallback,更不要OnBackPressedCallback+OnBackInvokedCallback混合使用,回调逻辑很复杂,对api理解要求很高,否则无法执行到预期效果
如果没有对开启enableOnBackInvokedCallback做适配,manifest文件中需要显示的关闭enableOnBackInvokedCallback属性,避免三方sdk开启该属性后影响到业务逻辑,例如zendesk
-
onBackPressed已经被官方废弃了,我们需要迁移么?
- 可以进行迁移,将onBackPressed中的逻辑迁移使用OnBackPressedCallback,但是需要注意的是页面关闭需要业务主动处理
