赞
踩
Android 10 首次引入了全局返回手势,但直到返回触发才能看到目标上层画面。13
针对该特性进行了优化,即返回触发之前可以预览上层画面。同时彻底废弃了返回键相关的 API,这将对现有的 App 逻辑产生巨大的影响!
Android 13 针对包括手机、大屏、折叠屏等 Android 设备推出了可预见型返回手势(Predictive Back Gesture
)特性。该特性将便于用户在返回完成之前可以先预览到目标画面或结果,这样的话可以允许他们决定是否要继续返回或者放弃并停留在当前画面。
另外引入关于 KEYCODE_BACK
KeyEvent 相关的一系列变更。
为节省篇幅和统一认识,后续的相关描述将按照如下规则简称:
新返回导航
Back KeyButton
Back Gesture
Back KeyButton | Back Gesture |
后续将按照如下几个方面去阐述:
简单来说会产生如下影响:
返回手势的可预见型 UI 的增强:展示返回触发前上层画面
原有 API 废弃:
KEYCODE_BACK
:详述见小章节onBackPressed()
引入全新的 SDK 返回相关 API:
enableOnBackInvokedCallback
属性getOnBackInvokedDispatcher()
OnBackInvokedDispatcher
OnBackInvokedCallback
备注:无关TargetSDKVersion
,运行在 13 上只要支持新返回导航均会受收到如上的影响。
KEYCODE_BACK 非推荐
准确含义是 13 上一旦开启新返回导航支持,无论是 Back Gesture 的触发还是 Back KeyButton 的点击,App 均无法监听到 KEYCODE_BACK 事件。即相关的如下 API 将无法被回调:
dispatchKeyEvent()
onKeyDown()
onKeyUp()
onBackPressed()
除了上述提到的具体变更以外,所有 KEYCODE_BACK 的相关逻辑都得测试一下是否存在问题,比如容易忽略的 View、Dialog$Builder。
简单来说,检查下现有代码是否用到了如下 API:
大多数 App 都会选择自定义返回导航,可选的方式包括 SDK 的原生 API 和 AndroidX 的 Callback API。依据这些情况的不同、App 适配的意愿不同,适配的方案也不一样。
没有自定义返回导航的场景
加入新返回导航的支持即可,具体见《4.1 加入新返回导航的支持》章节。
自定义返回导航的场景
需要按照现有 API 是否接入了 AndroidX 的 OnBackPressedDispatcher 进行分情况适配。
是否使用了AndroidX | 如何处理返回导航 | 推荐的适配策略 | 备注 |
---|---|---|---|
Yes | AndroidX APIs | 升级已有的 AndroidX 返回 API | 3rd Party App 的大多数 case |
Unsupported SDK APIs | 迁移非推荐 SDK 返回 API 到 AndroidX API | 少部分 App | |
No | Unsupported SDK APIs,但我愿意迁移 | 迁移非推荐 SDK 返回 API 到新 SDK 返回 API | 系统 App 的大多数 case |
Unsupported SDK APIs,但我不愿意迁移 | 延迟加入新返回导航的支持,直到它成为必须特性 | 少部分 App |
4.1 加入新返回导航的支持
Manifest 中针对新返回导航特性引入的属性 enableOnBackInvokedCallback
默认是 false,即默认不支持该特性,支持的话需要声明为 true。
<application
...
android:enableOnBackInvokedCallback="true"
... >
...
</application>
实测发现:即便声明成了 false,但如果代码中残存了 13 的新 API(比如 OnBackInvokedCallback)的使用,仍会导致新返回导航发生作用。
也就是说,不支持的话,就不要使用任何新的返回相关 API。
4.2 关闭新返回导航的支持
正如上面所述,按照如下即可关闭对新返回导航的支持:
4.3 升级已有的 AndroidX 返回 API
对于已使用 AndroidX 返回 API 的 App 只需开启新返回导航的支持,其他的适配工作交由 AndroidX 框架来完成。
Supporting the predictive back gesture requires updating your app,
using the OnBackPressedCallback AppCompat 1.6.0-alpha03 (AndroidX) or
higher API.
笔者按照官方说明将 AppCompat 包升级到了 1.6.0-alpha03
。
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.0-alpha03'
}
使用其提供的 OnBackPressedCallback
API 监听 Activity 的 Back 操作如下:
class BackKeyTestActivityAppCompat : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ... onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { Log.d("BackGesture", "Activity#handleOnBackPressed()") } }) } override fun dispatchKeyEvent(event: KeyEvent): Boolean { Log.d("BackGesture", "Activity#dispatchKeyEvent() event:$event") return super.dispatchKeyEvent(event) } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { Log.d("BackGesture", "Activity#onKeyDown() event:$event") return super.onKeyDown(keyCode, event) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { Log.d("BackGesture", "Activity#onKeyUp() event:$event") return super.onKeyUp(keyCode, event) } override fun onBackPressed() { Log.d("BackGesture", "onBackPressed()") super.onBackPressed() } }
可是实测发现:
12-Back Gesture 的执行日志:
05-31 10:35:28.732 11267 11267 D BackGesture: Activity#dispatchKeyEvent() event:KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, ... }
05-31 10:35:28.733 11267 11267 D BackGesture: Activity#onKeyDown() event:KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, ... }
05-31 10:35:28.733 11267 11267 D BackGesture: Activity#dispatchKeyEvent() event:KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, ... }
05-31 10:35:28.733 11267 11267 D BackGesture: Activity#onKeyUp() event:KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, ... }
05-31 10:35:28.733 11267 11267 D BackGesture: onBackPressed()
05-31 10:35:28.734 11267 11267 D BackGesture: Activity#handleOnBackPressed()
12-Back KeyButton 的执行日志:
05-31 10:37:21.724 11267 11267 D BackGesture: Activity#dispatchKeyEvent() event:KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK... }
05-31 10:37:21.724 11267 11267 D BackGesture: Activity#onKeyDown() event:KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK... }
05-31 10:37:21.846 11267 11267 D BackGesture: Activity#dispatchKeyEvent() event:KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK... }
05-31 10:37:21.846 11267 11267 D BackGesture: Activity#onKeyUp() event:KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK... }
05-31 10:37:21.846 11267 11267 D BackGesture: onBackPressed()
05-31 10:37:21.846 11267 11267 D BackGesture: Activity#handleOnBackPressed()
调试了一下,发现 AppCompat 框架里使用 13 的新 SDK API 前的版本判断有问题:
public class ComponentActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { ... if (Build.VERSION.SDK_INT >= 33) { mOnBackPressedDispatcher.setOnBackInvokedDispatcher(getOnBackInvokedDispatcher()); } ... } } public final class OnBackPressedDispatcher { Cancellable addCancellableCallback(@NonNull OnBackPressedCallback onBackPressedCallback) { ... if (Build.VERSION.SDK_INT >= 33) { updateBackInvokedCallbackState(); onBackPressedCallback.setIsEnabledConsumer(mEnabledConsumer); } return cancellable; } }
Beta
版的 SDK_INT
常量仍然是 12L
的 32,到正式发布才会改为 33,所以版本判断应当使用 BuildCompat
的如下 API:
// BuildCompat.java
public static boolean isAtLeastT() {
return VERSION.SDK_INT >= 33
|| (VERSION.SDK_INT >= 32
&& isAtLeastPreReleaseCodename("Tiramisu", VERSION.CODENAME));
}
官方文档提示说的是使用 1.6.0-alpha03 及以上,那么 03 应该是首次引入上述适配的版本,可能还没做好。查了下 AppCompat 包是否出现最新版本,果然有个 1.6.0-alpha04
。
Version 1.6.0-alpha04
May 18, 2022
更新了后确实好了,即 13 上开启支持的话,无论是 Back Gesture 还是 Back KeyButton,能像预期的那样都只会输出 androidX 版本的 Callback,Back 相关 KeyEvent 回调将不再执行。
05-31 10:55:10.773 5041 5041 D BackGesture: Activity#handleOnBackPressed()
但仍有一点未达预期:
4.4 迁移非推荐 SDK 返回 API 到 AndroidX API
适配步骤:
OnBackPressedDispatcher
API,他需要指定对于 Activity:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = onBackPressedDispatcher.addCallback(this) {
// Handle the back button event
}
}
... }
对于 Fragment:
public class FormEntryFragment extends Fragment { @Override public void onAttach(@NonNull Context context) { super.onAttach(context); OnBackPressedCallback callback = new OnBackPressedCallback( true // default to enabled ) { @Override public void handleOnBackPressed() { showAreYouSureDialog(); } }; requireActivity().getOnBackPressedDispatcher().addCallback( this, // LifecycleOwner callback); } }
禁用原有的系统返回手势回调,比如 onBackPressed()、KEYCODE_BACK
解释:getOnBackPressedDispatcher 早在 13 之前就已经支持,既然换了就没必要保留 SDK API 逻辑。
最后记得加入新返回导航的支持。
4.5 迁移非推荐 SDK 返回 API 到新 SDK 返回 API
适配步骤:
OnBackInvokedCallback
,12及之前的版本仍可使用旧的返回onBackInvoked
方法的 OnBackInvokedCallback。这将阻止当前的 ActivityTo ensure that future enhancements to the system Back navigation are
properly supported, your app MUST unregister the
OnBackInvokedCallback. Otherwise, users may see undesirable behavior
when using a system Back navigation—for example, “getting stuck”
between views and forcing them to force quit your app.
@Override
void onCreate() {
if (BuildCompat.isAtLeastT()) {
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
() -> {
// ...
}
);
}
}
比如 WebView 需要拦截返回手势以回退网页,当已经返回到主画面的时候应当注销该 Callback 让系统来处理 finish。
备注:onBackPressed() 逻辑保留也没有关系,并不会发生冲突,而且为了兼容 13 之前的系统功能本就应该保留。
registerOnBackInvokedCallback() 说明
registerOnBackInvokedCallback() 调用的时候需要提供如下两个参数:
priority
:按照注册的逆序进行,但如果是高优先级的先回调。可选范围:int 型,亦可选如下预设常量:
PRIORITY_DEFAULT
:值为 0
,普通回调PRIORITY_OVERLAY
:值为 1000000
,优先回调但不可以是负值、否则会发生 IllegalArgumentException
异常
java.lang.IllegalArgumentException: Application registered
OnBackInvokedCallback cannot have negative priority. Priority: -1
实际结果:只有最后一个 register 的 Callback 得到调用,但如果列表里存在 PRIORITY_OVERLAY 等更高优先级的 Callback 的话则优先。与如下描述不符:
When back is triggered, callbacks on the in-focus window are invoked
in reverse order in which they are added within the same priority.
Between different priorities, callbacks with higher priority are
invoked first.
5.1 案例
和 KEYCODE_BACK 相关的有很多 API 可以处理、场景也很繁杂,简单举例如下:
class Activity {
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if ( ... ) return false
when (keyCode) {
KeyEvent.KEYCODE_BACK -> { methodA() }
KeyEvent.KEYCODE_MENU -> { ... }
else -> {}
}
return if ( ... ) {
true
} else super.onKeyDown(keyCode, event)
}
}
5.2 适配
适配的目的在于确保如下:
以上述的案例 1 的代码为例,如下是如何改造以保证能在 12 和 13 上运行一样的 Key 相关动作:
class Activity { private var onBackInvokedCallback: OnBackInvokedCallback? = null override fun onCreate(savedInstanceState: Bundle?) { ... if (BuildCompat.isAtLeastT()) { onBackInvokedCallback = OnBackInvokedCallback { onBackEvent() }.also { onBackInvokedDispatcher.registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, it ) } } } override fun onDestroy() { super.onDestroy() if (BuildCompat.isAtLeastT()) { onBackInvokedCallback?.let { onBackInvokedDispatcher.unregisterOnBackInvokedCallback(it) } } } private fun onBackEvent() { // if ( ... ) return false if ( ... ) return // when (keyCode) { // KeyEvent.KEYCODE_BACK -> { methodA() } // KeyEvent.KEYCODE_MENU -> { ... } // else -> {} // } methodA() // return if ( ... ) { // true // } else super.onKeyDown(keyCode, event) } // 为兼容旧版仍需完全保留 override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { ... } }
如上适配的关键点在于:除了在 Manifest 中将 enableOnBackInvokedCallback 属性打开和注册 OnBackInvokedCallback() 以外,重点在于如何实现 onBackInvoked() 来达到旧版的同等返回逻辑:
此外,需要留意如下一些细节:
新的 Callback 如何区分 dispatchKeyEvent()、onKeyDown()、onKeyUp() 的时机?
无法区分
,开启新返回导航之后只有一个 OnBackInvokedCallback 回调时机,其在 Back Gesture Trigger
或 Back KeyButton Up
时触发。
原本时序:dispatchKeyEvent(DOWN) -> onKeyDown() -> dispatchKeyEvent(UP) -> onKeyUp()
新的 Callback 如何针对 KEYCODE_BACK 的 DOWN 和 UP 作区分?
无法区分
,开启新返回导航之后只有最终的 Callback,没有 DOWN 和 UP 之分。
新的 Callback 针对 dispatchKeyEvent() 等处理的 return true、false、super 如何区分?
false:本意是不处理,对应于现在的 Callback 可以是什么也不做或直接 return
true:本意是处理,对应于现在的 Callback 可以是处理外加 return
super:本意是交由父类处理,对应于现在的 Callback 可以是 return 或者直接删除,这取决于原来的 super
调用位置,也可以考虑在某条件满足的时候提前注销 Callback
这种思路
Back 以外,比如 Menu KeyEvent 的监听是否受影响?
不受影响。之前的 Menu Key 等监听在 13 上仍可以监听到、正常运行,可以保留。
如何兼容 13 以前的版本呢?
新老处理共存
,判断运行版本:13 上开启的话执行新逻辑,13 以前继续沿用旧逻辑。
5.3 集成到 Base 中统一处理
Activity、Fragment 以及 Dialog 众多的情况下,可在 Base 类里加入统一的注册和销毁 Callback 的复用代码。
为了不干预不需要处理的子类,默认不进行注册。需要的子类覆写 isNeedInterceptBackEvent()
返回 true 并实现自己的 Callback 逻辑即可。
如下的 BaseActivity 事例代码:
open class BaseActivity: AppCompatActivity() { private var onBackInvokedCallback: OnBackInvokedCallback? = null /** * Inner class for handle back callback totally. */ internal class OnBackInvokedCallbackInner constructor(baseActivity: BaseActivity) : OnBackInvokedCallback { private val activity: WeakReference<BaseActivity> override fun onBackInvoked() { activity.get()?.apply { onBackEvent() } } init { activity = WeakReference(baseActivity) } } /** * Override this method and return true if child wanna handle back event. */ open fun isNeedInterceptBackEvent(): Boolean = false /** * Default back operation is invoking onBackPressed(). * Child activity could override and implement its own operation. */ open fun onBackEvent() { onBackPressed() } override fun onCreate(savedInstanceState: Bundle?) { ... if (isNeedInterceptBackEvent() && BuildCompat.isAtLeastT()) { onBackInvokedCallback = OnBackInvokedCallbackInner(this).also { onBackInvokedDispatcher.registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, it ) } } } override fun onDestroy() { ... if (BuildCompat.isAtLeastT()) { onBackInvokedCallback?.let { onBackInvokedDispatcher.unregisterOnBackInvokedCallback(it) } } } }
需要的子类进行覆写。
class BackKeyHandleActivity : BaseActivity() {
...
override fun isNeedInterceptBackEvent(): Boolean = true
override fun onBackEvent() { ... }
// 兼容 13 之前的逻辑
override fun onBackPressed() { ... }
}
针对采用新 SDK 返回 API 方案分别在 13 上开启和关闭新返回导航的支持,观察 KeyEvent 相关的 Log 输出,并尝试分析一些原理方面的差异。
6.1 开启支持
Back Gesture
开启新的返回手势支持的话,只能收到 OnBackInvokedCallback 回调,确实无法像以前一样灵活、精细地处理 KEYCODE_BACK 了。
如下的系统日志可以瞥见 Callback 处理的一些细节。
05-26 10:26:27.929 787 787 D NoBackGesture: Start gesture: MotionEvent { action=ACTION_DOWN ... }
05-26 10:26:27.929 787 787 D NoBackGesture: Prediction [1653531987929,47,633,-1,0.000000,1]
05-26 10:26:27.930 787 787 D NoBackGesture: reset mTriggerBack=false
05-26 10:26:27.931 787 852 D ShellBackPreview: initAnimation mMotionStarted=false
05-26 10:26:27.932 787 787 D NoBackGesture: Gesture [1653531987932,alw=TRUE,TRUE,TRUE,FALSE,disp=Point(1080, 2340),wl=82,il=0,wr=82,ir=0,excl=SkRegion()]
05-26 10:26:27.933 599 2725 D CoreBackPreview: Focused window found using getFocusedWindowToken
05-26 10:26:27.933 599 2725 D CoreBackPreview: startBackNavigation currentTask=Task{1d3c440 #502 type= ...}, callbackInfo=OnBackInvokedCallbackInfo{ ... }
05-26 10:26:27.934 787 852 D ShellBackPreview: Received backNavigationInfo:BackNavigationInfo{...}
05-26 10:26:27.963 787 787 D OnBackInvokedDispatcher: ViewRootImpl.registerBackCallbackOnWindow. Dispatcher:android.window.WindowOnBackInvokedDispatcher@be64a11 Package:com.android.systemui IWindow:android.view.ViewRootImpl$W@5c4e776 Session:android.view.IWindowSession$Stub$Proxy@3998bd7
05-26 10:26:27.968 787 787 V OnBackInvokedDispatcher: Proxy setActual android.window.WindowOnBackInvokedDispatcher@be64a11. Current null
05-26 10:26:27.968 787 787 V OnBackInvokedDispatcher: Proxy transferring 0 callbacks to android.window.WindowOnBackInvokedDispatcher@be64a11
05-26 10:26:28.271 3978 3978 D BackGesture: onBackInvoked()
通过 adb shell dumpsys input 命令确实也没有看到 InputFlinger 发送 KEYCODE_BACK 的记录。
MotionEvent(deviceId=8, eventTime=2965229468000, source=TOUCHSCREEN | STYLUS, displayId=0, action=DOWN ...)
MotionEvent(deviceId=8, eventTime=2965457324000, source=TOUCHSCREEN | STYLUS, displayId=0, action=MOVE ...)
...
MotionEvent(deviceId=8, eventTime=2965524225000, source=TOUCHSCREEN | STYLUS, displayId=0, action=UP ...)
Back KeyButton
Back KeyButton 场景也是一样,开启新返回导航支持的话,只能收到 OnBackInvokedCallback 回调。
05-26 10:59:05.854 4497 4497 D OnBackInvokedDispatcher: ViewRootImpl.registerBackCallbackOnWindow. Dispatcher:android.window.WindowOnBackInvokedDispatcher@ad0c7c7 Package:com.android.systemui IWindow:android.view.ViewRootImpl$W@2ade1f4 Session:android.view.IWindowSession$Stub$Proxy@f606e17
05-26 10:59:05.904 4497 4497 V OnBackInvokedDispatcher: Proxy setActual android.window.WindowOnBackInvokedDispatcher@ad0c7c7. Current null
05-26 10:59:05.904 4497 4497 V OnBackInvokedDispatcher: Proxy transferring 0 callbacks to android.window.WindowOnBackInvokedDispatcher@ad0c7c7
05-26 10:59:05.977 7700 7700 D BackGesture: onBackInvoked()
05-26 10:59:06.495 7700 7700 V OnBackInvokedDispatcher: Proxy unregister android.app.Activity$$ExternalSyntheticLambda0@a72f76. Actual=android.window.WindowOnBackInvokedDispatcher@1da92aa
05-26 10:59:06.495 7700 7700 V OnBackInvokedDispatcher: Proxy unregister com.example.tiramisu_demo.MainActivity$$ExternalSyntheticLambda1@d37f96a. Actual=android.window.WindowOnBackInvokedDispatcher@1da92aa
05-26 10:59:27.696 4497 4497 D OnBackInvokedDispatcher: ViewRootImpl.registerBackCallbackOnWindow. Dispatcher:android.window.WindowOnBackInvokedDispatcher@cdfd9c Package:com.android.systemui IWindow:android.view.ViewRootImpl$W@da2b3a5 Session:android.view.IWindowSession$Stub$Proxy@f606e17
05-26 10:59:27.707 4497 4497 V OnBackInvokedDispatcher: Proxy setActual android.window.WindowOnBackInvokedDispatcher@cdfd9c. Current null
05-26 10:59:27.707 4497 4497 V OnBackInvokedDispatcher: Proxy transferring 0 callbacks to android.window.WindowOnBackInvokedDispatcher@cdfd9c
但 dump input 却出现了 Back 的 KeyEvent 记录,这是为什么呢?
此处留个悬念,后面会揭开谜底。
MotionEvent(deviceId=8, eventTime=2276120343000, source=TOUCHSCREEN | STYLUS, displayId=0, action=DOWN ...)
KeyEvent(deviceId=-1, eventTime=2276124000000, source=KEYBOARD, displayId=0, action=DOWN, flags=0x00000048, keyCode=BACK(4) ...)
MotionEvent(deviceId=8, eventTime=2276205324000, source=TOUCHSCREEN | STYLUS, displayId=0, action=UP ...)
KeyEvent(deviceId=-1, eventTime=2276266000000, source=KEYBOARD, displayId=0, action=UP, flags=0x00000048, keyCode=BACK(4) ...)
6.2 关闭支持
Back Gesture
当关闭支持后 Back Gesture 场景下能和旧版本一样收到 KEYCODE_BACK 了。
05-26 11:09:28.235 6784 6784 D BackGesture: dispatchKeyEvent() event:KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK ... }
05-26 11:09:28.236 6784 6784 D BackGesture: dispatchKeyEvent() event:KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK ... }
05-26 11:09:28.240 6784 6784 D BackGesture: onBackPressed()
dump input 也可以证实该 KeyEvent 的真实存在,而且可以看到 Back Gesture 的 UP 之后连续注入了 KEYCODE_BACK 的 DOWN 和 UP 的细节
。
MotionEvent(deviceId=8, eventTime=585598303000, source=0x00005002, displayId=0, action=DOWN ...)
MotionEvent(deviceId=8, eventTime=585812734000, source=0x00005002, displayId=0, action=MOVE ...)
...
MotionEvent(deviceId=8, eventTime=585858936000, source=0x00005002, displayId=0, action=UP ...)
KeyEvent(deviceId=-1, eventTime=585859000000, source=0x00000101, displayId=0, action=DOWN, flags=0x00000048, keyCode=4 ...)
KeyEvent(deviceId=-1, eventTime=585860000000, source=0x00000101, displayId=0, action=UP, flags=0x00000048, keyCode=4 ...)
Back KeyButton
自不必说,Back KeyButton 的按下当然也可以收到 KEYCODE_BACK。
05-26 10:48:21.580 5817 5817 D BackGesture: dispatchKeyEvent() event:KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK...}
05-26 10:48:21.635 5817 5817 D BackGesture: dispatchKeyEvent() event:KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK ...}
05-26 10:48:21.635 5817 5817 D BackGesture: onBackPressed()
但与 Gesture 不同,dump input 的结果可以看到:在 Back KeyButton 上按下时注入了 KEYCODE_BACK 的 DOWN,抬起注入了 UP
。
MotionEvent(deviceId=8, eventTime=352268883000, source=0x00005002, displayId=0, action=DOWN ...)
KeyEvent(deviceId=-1, eventTime=352289000000, source=0x00000101, displayId=0, action=DOWN ...)
MotionEvent(deviceId=8, eventTime=352378721000, source=0x00005002, displayId=0, action=UP ...)
KeyEvent(deviceId=-1, eventTime=352386000000, source=0x00000101, displayId=0, action=UP...)
6.3 Back 相关时序的变化总结
对比点 | 关闭支持-Back Gesture | 关闭支持-Back KeyButton | 开启支持-Back Gesture | 开启支持-Back KeyButton |
---|---|---|---|---|
ViewRootImpl#processKeyEvent() | YES | YES | NO | YES |
dispatchKeyEvent(DOWN) | YES trigger 时连续发送 DOWN 和 UP | YES 按下时发送 DOWN | NO | NO |
onKeyDown() | YES | YES | NO | NO |
dispatchKeyEvent(UP) | YES | YES 抬起时发送 UP | NO | NO |
onKeyUp() | YES | YES | NO | NO |
onBackPressed() | YES | YES | NO | NO |
OnBackInvokedCallback | NO | NO | YES | YES |
6.4 开启支持的原理分析
13 开启支持 | 12 | |
---|---|---|
Back Gesture | Callback + KEYCODE_BACK 无法监听 | KEYCODE_BACK 可以监听 |
Back KeyButton | Callback + KEYCODE_BACK 无法监听 但 KEYCODE_BACK 实际存在 | KEYCODE_BACK 可以监听 |
13 上开启支持之后,如果是点击 Back KeyButton,从 dump 来看仍然发出了 KEYCODE_BACK,猜测与你大体是这样:
这里不禁产生一个疑问:
经过思考,觉得不免又如下几点可能:
Back Gesture 和 Back KeyButton 功能定位有区别:前者是返回手势,需要展示返回图标和背面视图的动画,它的处理在
EdgeBackGesture 里;后者是虚拟按键,在 NavigationBar 的 KeyButtonView 中处理
(1)13 之前没有引入可预测型动画的时候两者功能雷同,所以 Back Gesture 采用了和 Back KeyButton 一样的逻辑
(2)13 引入了和 Back KeyButton 完全不同的返回预测动画,需要实现一套自己的回调路径,不需要再依赖原来的 KeyEvent 路径
另外从是否属于按键的角度上来讲
(1) Back Gesture 不是虚拟按键、也不是实体按键,没有必要发送 KeyEvent
(2)Back KeyButton 是虚拟按键,需要遵从 Key 的 Map 规范,是需要发送对应 KeyEvent 出来的。而且即便后面会被
App 拦截,但对于前期的系统 PhoneWindowManager、InputFilter 可能也需要处理
需要说明的是,当关闭新返回导航支持后,为了兼容旧的 API,Back Gesture 仍像以前一样发送 KEYCODE_BACK。当然这肯定是暂时的,后续系统肯定会强制使用该特性,到时候这个 Back Gesture 就再也不用发送 KEYCODE_BACK 了。
7. 注意和残留事项
TargetSDKVersion
无关,运行在 13 上的 App 都需要思考是否收到影响、如何适配API OnBackInvokedDispatcher
中注册的 OnBackInvokedCallback
enableOnBackInvokedCallback
属性关闭的话,不要残留注册Dialog
场景使用新版 SDK 返回 API 没有效果,原因未知View
监听 KEYCODE_BACK 的逻辑是否受影响,暂未实验onCreate()
+ onDestroy()
,onCreate() +1.6.0-alpha04
,不然不生效OnBackPressedCallback
,没有 KeyEvent 回调,原因未知unregister
方法注销该disabled
制作了一张 Android 13 新返回导航适配流程图供大家快速查阅。
做个简单总结:
如果决定支持新返回导航即声明 enableOnBackInvokedCallback
为 true,之后需依据 App 集成了 SDK API 还是 AndroidX API 决定适配的方案。
SDK 方案的话需要引入新的 OnBackInvokedDispatcher
相关API,并留意Activity、Dialog、Window、View 上现有的 Back 逻辑是否会收到影响,以及如何改造。当然需要判断运行版本,并为了兼容 13
之前的设备保留现有的 Back 逻辑
AndroidX 方案的话使用专属的OnBackPressedDispatcher
API,AppCompat
库升级之后会自行完成内部的SDK API 迁移
另外还需要留意上述章节提及的注意事项和残留事项。
当然如果没有余力适配,决定舍弃可预测型返回手势、OnBackInvokedDispatcher 新 API 以及 KEYCODE_BACK
等一系列变更,可以选择什么也不做。
但早在 13 之前,官方已推荐使用 AndroidX 的 OnBackPressedDispatcher 来取代 onBackPressed
,13 花这么大精力完全废弃 onBackPressed 并向 AOSP 新增了 OnBackInvokedDispatcher 等系列 API。
从这个趋势来看,估计到 Android 14 这个新返回导航就会成为强制要求,开发者们当尽早适配才是!
官方文档:
SDK API:
AndroidX API:
在此特地鸣谢@TechMerger,大家可以共同学习进步!
原文地址:https://juejin.cn/post/7105645114760331300
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。