OnBackPressedDispatcher的使用与原理剖析

3,439 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

OnBackPressedDispatcherComponentActivity 提供的处理返回事件的工具,今天就来研究它

概述

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 的执行需要以下两个条件:

  1. ComponentActivity.onBackPressed()回调
  2. 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居然打印了……

来分析下流程:

  1. callback添加的顺序:Activity -> FirstFragment -> SecondFragment,onBack处理的时候,逆序处理
  2. 首先SecondFragment,因为它的callback默认是disabled的,所以没回调。且因为没有添加backstack,也不会被移除
  3. 在添加SecondFragment时,FirstFragment已经通过replace而被remove掉了,且它添加callback是和lifecycle绑定的,所以该callback已经不存在,也不会回调
  4. 最后就到了Activity的回调,成功执行

当勾上SecondFragment的CheckBox,设为enabled,它就会拦截onBack,并打印自己的日志

至此,以上的实验,证明了前文的源码分析结论

小结

总地说来,OnBackPressedDispatcher 可以让返回处理不再局限于Activity中的父类方法覆写,以注册callback方式存在,可控性强,还有扩展性,可以支持更多的组件添加自己的返回处理