持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情。
OnBackPressedDispatcher 是 ComponentActivity 提供的处理返回事件的工具,今天就来研究它
概述
OnBackPressedDispatcher 可以在哪些场景下使用呢?
开篇已经说到,OnBackPressedDispatcher 存在于 androidx.activity.ComponentActivity, 又,有以下继承关系:
AppCompatActivity -> FragmentActivity -> ComponentActivity
所以说,你的 Activity,无论是 AppCompatActivity 还是 FragmentActivity, 都可使用。
对于 Fragment 来说,有 public final FragmentActivity requireActivity() 方法在手,自然也是能使用的。
原理详解
ComponentActivity 默认提供了 OnBackPressedDispatcher 的实现以及公有get方法:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ... {
// ...
// 默认实现
private final OnBackPressedDispatcher mOnBackPressedDispatcher =
new OnBackPressedDispatcher(new Runnable() {
@Override
public void run() {
// Calling onBackPressed() on an Activity with its state saved can cause an
// error on devices on API levels before 26. We catch that specific error and
// throw all others.
try {
ComponentActivity.super.onBackPressed();
} catch (IllegalStateException e) {
if (!TextUtils.equals(e.getMessage(),
"Can not perform this action after onSaveInstanceState")) {
throw e;
}
}
}
});
// ...
public final OnBackPressedDispatcher getOnBackPressedDispatcher() {
return mOnBackPressedDispatcher;
}
// ...
}
OnBackPressedCallback
要点
OnBackPressedCallback 是用于 OnBackPressedDispatcher的“返回处理”的回调类:
public abstract class OnBackPressedCallback {
// ...
private boolean mEnabled;
public OnBackPressedCallback(boolean enabled) {
mEnabled = enabled;
}
@MainThread
public final void setEnabled(boolean enabled) {
mEnabled = enabled;
}
@MainThread
public final boolean isEnabled() {
return mEnabled;
}
@MainThread
public abstract void handleOnBackPressed();
// ...
}
其中,mEnabled 用于控制回调开启与否,构造时须传入,也可以通过set方法改变。
抽象方法 handleOnBackPressed 用于onBack(返回)事件分发时调用,实现子类用它自行加入处理逻辑。
添加
添加callback有两个方法:
@MainThread
public void addCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
addCancellableCallback(onBackPressedCallback);
}
@MainThread
public void addCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback) {
Lifecycle lifecycle = owner.getLifecycle();
if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
return;
}
onBackPressedCallback.addCancellable(
new LifecycleOnBackPressedCancellable(lifecycle, onBackPressedCallback));
}
第二个带 LifecycleOwner 参数,相当于添加了Activity生命周期的感知,这可以有效防止内存泄漏,尤其对于Fragment这种短于Activity生命的组件,更加有用。它通过内部类 LifecycleOnBackPressedCancellable 实现:
private class LifecycleOnBackPressedCancellable implements LifecycleEventObserver,
Cancellable {
private final Lifecycle mLifecycle;
private final OnBackPressedCallback mOnBackPressedCallback;
@Nullable
private Cancellable mCurrentCancellable;
LifecycleOnBackPressedCancellable(@NonNull Lifecycle lifecycle,
@NonNull OnBackPressedCallback onBackPressedCallback) {
mLifecycle = lifecycle;
mOnBackPressedCallback = onBackPressedCallback;
lifecycle.addObserver(this);
}
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_START) {
// 类似普通的addCallback
mCurrentCancellable = addCancellableCallback(mOnBackPressedCallback);
} else if (event == Lifecycle.Event.ON_STOP) {
// Should always be non-null
// stop后,cancel掉。
// 这里仅仅是停止了它的使用
if (mCurrentCancellable != null) {
mCurrentCancellable.cancel();
}
} else if (event == Lifecycle.Event.ON_DESTROY) {
// destroy后,直接移除
cancel();
}
}
@Override
public void cancel() {
// 完全cancel,监听移除,并停掉callback
mLifecycle.removeObserver(this);
mOnBackPressedCallback.removeCancellable(this);
if (mCurrentCancellable != null) {
mCurrentCancellable.cancel();
mCurrentCancellable = null;
}
}
}
最终,两个添加函数,都调用addCancellableCallback添加
OnBackPressedDispatcher的返回事件分发
那 OnBackPressedDispatcher 是如何分发返回事件的呢?
先来看看构造:
public final class OnBackPressedDispatcher {
// ...
@Nullable
private final Runnable mFallbackOnBackPressed;
public OnBackPressedDispatcher() {
this(null);
}
public OnBackPressedDispatcher(@Nullable Runnable fallbackOnBackPressed) {
mFallbackOnBackPressed = fallbackOnBackPressed;
}
// ...
}
两个构造函数,带参数的,需要传入一个 Runnable,默认的 ComponentActivity 就是用的这个。
而这个 Runnable 的执行需要以下两个条件:
ComponentActivity.onBackPressed()回调OnBackPressedDispatcher.hasEnabledCallbacks()返回false
hasEnabledCallbacks 干了什么呢?
// ...
final ArrayDeque<OnBackPressedCallback> mOnBackPressedCallbacks = new ArrayDeque<>();
public boolean hasEnabledCallbacks() {
// 倒序的iterator
Iterator<OnBackPressedCallback> iterator =
mOnBackPressedCallbacks.descendingIterator();
while (iterator.hasNext()) {
// 如果有一项是enabled状态,就返回true
if (iterator.next().isEnabled()) {
return true;
}
}
return false;
}
mOnBackPressedCallbacks 存储了所有的回调处理callback,如果 hasEnabledCallbacks 返回false,就表明所有的callback都是disable的状态,即,不可用(或者,压根儿没有callback)。这时候,就得交给 mFallbackOnBackPressed 处理了,对于默认情况来说,就是关掉界面:
private final OnBackPressedDispatcher mOnBackPressedDispatcher =
new OnBackPressedDispatcher(new Runnable() {
@Override
public void run() {
// Calling onBackPressed() on an Activity with its state saved can cause an
// error on devices on API levels before 26. We catch that specific error and
// throw all others.
try {
// 关闭当前界面
ComponentActivity.super.onBackPressed();
} catch (IllegalStateException e) {
if (!TextUtils.equals(e.getMessage(),
"Can not perform this action after onSaveInstanceState")) {
throw e;
}
}
}
});
代码上来看,确实如此,但是实际上,并没有发现有任何地方调用了 hasEnabledCallbacks —— OnBackPressedDispatcher 是直接处理callback集合的:
@MainThread
public void onBackPressed() {
// 倒序的iterator
Iterator<OnBackPressedCallback> iterator =
mOnBackPressedCallbacks.descendingIterator();
while (iterator.hasNext()) {
OnBackPressedCallback callback = iterator.next();
// 如果有一项是enabled状态,就处理,并返回
if (callback.isEnabled()) {
callback.handleOnBackPressed();
return;
}
}
// 没有callback,或者没有一个callback是enable状态,才会执行默认的返回操作
if (mFallbackOnBackPressed != null) {
mFallbackOnBackPressed.run();
}
}
由上面源码分析,有以下结论:
OnBackPressedCallback 回调处理是逆序且独占的:最后添加的,优先处理,任意enable的callback,将阻断进一步的callback扫描
而ComponentActivity 是直接调用 onBackPressed 触发返回事件分发的:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ... {
// ...
@Override
@MainThread
public void onBackPressed() {
mOnBackPressedDispatcher.onBackPressed();
}
// ...
}
案例
下面,实操来看看 OnBackPressedDispatcher 的使用,并印证前面分析的结论。
Activity处理onBackPressed
首先,在Activity中添加callback,很简单:
class BackMainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(TAG, "Activity handle on back")
}
})
}
}
此callback默认设为enabled,即Activity处理onBack:当点击返回的时候,不会关闭界面,而会打印如下日志:
2022-06-29 17:51:52.409 7335-7335/com.jacee.backpressdispatcher D/back-dispatcher: Activity handle on back
Fragment添加callback
增加一个FirstFragment,并添加lifecycle版本的callback:
class FirstFragment : Fragment() {
private lateinit var onBackPressedCallback: OnBackPressedCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(TAG, "first handle on back")
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
}
}
class BackMainActivity : AppCompatActivity() {
// ...
fun toFragment(v: View) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, FirstFragment.newInstance("", ""))
.addToBackStack("first")
.commit()
}
// ...
}
同样,它的callback默认是enabled,当它添加到Activity后,Activity的callback处理将失效,点击返回时:
2022-06-29 18:01:54.513 7335-7335/com.jacee.backpressdispatcher D/back-dispatcher: first handle on back
的确如此,因为Fragment的callback阻断了Activity的callback。加一个 CheckBox 控制下:
class FirstFragment : Fragment() {
// ...
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false).also {
it.findViewById<CheckBox>(R.id.callback).setOnCheckedChangeListener { _, checked ->
onBackPressedCallback.isEnabled = checked
}
}
}
// ...
}
当设置为disable时,现象如下:
- 第一次返回:因为添加FirstFragment时有backstack,所以会移除Fragment
- 再一次返回:Activity的callback回调,打印日志
Lifecyle的作用
增加一个SecondFragment,并在FirstFragment中,增加替换逻辑:
class BackMainActivity : AppCompatActivity() {
// ...
fun toSecondFragment() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, SecondFragment.newInstance("", ""))
.commit()
}
// ...
}
class FirstFragment : Fragment() {
// ...
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false).also {
it.findViewById<CheckBox>(R.id.callback).setOnCheckedChangeListener { _, checked ->
onBackPressedCallback.isEnabled = checked
}
// 替换为第二个
it.setOnClickListener {
(requireActivity() as BackMainActivity).toSecondFragment()
}
}
}
// ...
}
class SecondFragment : Fragment() {
private lateinit var onBackPressedCallback: OnBackPressedCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// callback默认为disable
onBackPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
Log.d(TAG, "second handle on back")
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false).also {
it.findViewById<CheckBox>(R.id.callback).setOnCheckedChangeListener { _, checked ->
onBackPressedCallback.isEnabled = checked
}
}
}
}
SecondFragment不同于FirstFragment,它默认为disabled。SecondFragment显示后,点击返回:
2022-06-29 18:15:53.326 8577-8577/com.jacee.backpressdispatcher D/back-dispatcher: Activity handle on back
嗯,Activity的callback居然打印了……
来分析下流程:
- callback添加的顺序:Activity -> FirstFragment -> SecondFragment,onBack处理的时候,逆序处理
- 首先SecondFragment,因为它的callback默认是disabled的,所以没回调。且因为没有添加backstack,也不会被移除
- 在添加SecondFragment时,FirstFragment已经通过replace而被remove掉了,且它添加callback是和lifecycle绑定的,所以该callback已经不存在,也不会回调
- 最后就到了Activity的回调,成功执行
当勾上SecondFragment的CheckBox,设为enabled,它就会拦截onBack,并打印自己的日志
至此,以上的实验,证明了前文的源码分析结论
小结
总地说来,OnBackPressedDispatcher 可以让返回处理不再局限于Activity中的父类方法覆写,以注册callback方式存在,可控性强,还有扩展性,可以支持更多的组件添加自己的返回处理