
Like attention, no longer get lost, your support is of great significance to me!
🔥 Hi, I’m ugly. this paperIntroduction to “Android route” — from zero to infinityIt has been included. Here are the notes of Android advanced growth route & blog. Welcome to grow up with Peng Chou Chou. (contact information in GitHub)
preface
- fromandroidx.activity 1.0.0At first, Google introduced the onbackpresseddispatcher API to handle fallback events, aiming to optimize the processing of fallback events: you can define fallback logic anywhere, rather than relying on activity #onbackpressed();
- In this article, I will introduce the usage method, implementation principle and application scenario of onbackpresseddispatcher. If you can help, please be sure to praise and pay attention, which is really very important to me.
- The relevant code of this article can be downloaded fromDemoHall·HelloAndroidXDownload View.
catalogue

Pre knowledge
The content of this article will involve the following pre / related knowledge. I have prepared it for you. Please enjoy it~
1. General
-
What problems does onbackpresseddispatcher solve:In activity, it can be handled through the callback method onbackpressed(), while fragment / view has no direct callback method. Now we can use onbackpresseddispatcher instead of activity #onbackpressed() to implement fallback logic more gracefully.
-
The overall processing flow of onbackpresseddispatcher:The distributor adopts the responsibility chain design mode as a whole, and the callback objects added to the distributor will become a node in the responsibility chain. When the user triggers the return key, the responsibility chain will be traversed in sequence. If the callback object is enabled, the fallback event will be consumed and the traversal will be stopped. If the last event is not consumed, it is returned to activity #onbackpressed() for processing.
-
Comparison of onbackpresseddispatcher with other schemes:Before onbackpresseddispatcher, we can only handle fallback events through “trickery”:
- 1. Define the callback method in the fragment and pass the callback event from the activity #onbackpressed() (disadvantage: increase the coupling relationship between activity and fragment);
- 2. Set key listening setonkeylistener in the fragment root layout (disadvantage: inflexible & multiple fragment listening conflicts).
2. What APIs does onbackpresseddispatcher have?
There are the following APIs, which are easy to understand.Addcallback (lifecycle owner, callback) will enter lifecycle in the lifecycle owner State. Only when it is in the started state will it join the distribution responsibility chain, and enter lifecycle in the lifecycle owner State. When in stop status, it will be removed from the distribution responsibility chain.
1. Add callback object
public void addCallback(OnBackPressedCallback onBackPressedCallback)
2. Adds a callback object to associate with the specified lifecycle holder
public void addCallback(LifecycleOwner owner, OnBackPressedCallback onBackPressedCallback)
3. Determine whether there are enabled callbacks
public boolean hasEnabledCallbacks()
4. Fallback event distribution portal
public void onBackPressed()
5. Constructor (parameter is final callback)
public OnBackPressedDispatcher(@Nullable Runnable fallbackOnBackPressed) {
mFallbackOnBackPressed = fallbackOnBackPressed;
}
3. Onbackpresseddispatcher source code analysis
Onbackpresseddispatcher doesn’t have much source code. I’ll start with the problem and help you sort out the internal implementation principle of onbackpresseddispatcher:
3.1 how does the activity distribute events to onbackpresseddispatcher?
A: the dispenser object is combined inside the componentactivity. The return key callback onbackpressed() will be directly distributed to onbackpresseddispatcher #onbackpressed(). In addition, the fallback logic of the activity itself is encapsulated as runnable and handed over to the distributor for processing.
androidx.activity.ComponentActivity.java
private final OnBackPressedDispatcher mOnBackPressedDispatcher =
new OnBackPressedDispatcher(new Runnable() {
@Override
public void run() {
//Fallback logic of activity itself
ComponentActivity.super.onBackPressed();
}
});
@Override
@MainThread
public void onBackPressed() {
mOnBackPressedDispatcher.onBackPressed();
}
@NonNull
@Override
public final OnBackPressedDispatcher getOnBackPressedDispatcher() {
return mOnBackPressedDispatcher;
}
3.2 what is the processing flow of onbackpresseddispatcher?
A: the distributor adopts the responsibility chain design mode as a whole, and the callback objects added to the distributor will become a node in the responsibility chain. When the user triggers the return key, the responsibility chain will be traversed in sequence. If the callback object is enabled, the fallback event will be consumed and the traversal will be stopped. If the last event is not consumed, it is returned to activity #onbackpressed() for processing.
OnBackPressedDispatcher.java
//Final callback: activity #onbackpressed()
@Nullable
private final Runnable mFallbackOnBackPressed;
//Chain of responsibility
final ArrayDeque<OnBackPressedCallback> mOnBackPressedCallbacks = new ArrayDeque<>();
//Constructor
public OnBackPressedDispatcher() {
this(null);
}
//Constructor
public OnBackPressedDispatcher(@Nullable Runnable fallbackOnBackPressed) {
mFallbackOnBackPressed = fallbackOnBackPressed;
}
//Determine whether there are enabled callbacks
@MainThread
public boolean hasEnabledCallbacks() {
Iterator<OnBackPressedCallback> iterator = mOnBackPressedCallbacks.descendingIterator();
while (iterator.hasNext()) {
if (iterator.next().isEnabled()) {
return true;
}
}
return false;
}
Entry method: each callback method in the responsibility chain can be called only when the previous callback is not enabled.
If it is not enabled, it will eventually be called back to mfallbackonbackpressed
@MainThread
public void onBackPressed() {
Iterator<OnBackPressedCallback> iterator = mOnBackPressedCallbacks.descendingIterator();
while (iterator.hasNext()) {
OnBackPressedCallback callback = iterator.next();
if (callback.isEnabled()) {
callback.handleOnBackPressed();
//Consumption
return;
}
}
if (mFallbackOnBackPressed != null) {
mFallbackOnBackPressed.run();
}
}
3.3 is the callback method executed in the main thread or sub thread?
A: in the main thread, the entry method activity#onbackpressed() of the distributor is executed in the main thread, so the callback method is also executed in the main thread. In addition, the addcallback () method to add a callback also requires execution in the main thread, and the non concurrent security container arraydeque is used inside the distributor to store the callback object.
3.4 question 4: can onbackpressedcallback be added to different distributors at the same time?
A: Yes.
3.5 how to fallback after adding the fragment transaction returning to the stack?
A: the fragment manager also returns the transaction to onbackpresseddispatcher for processing. First, during fragment attach, a callback object will be created and added to the distributor, and the transaction returning to the top of the stack will pop up during callback processing. However, the initial state is not enabled. Only when the transaction is added to the return stack will the callback object be modified to the enabled state. The source code is as follows:
FragmentManagerImpl.java
//3.5.1 distributor and callback object (the initial state is not enabled)
private OnBackPressedDispatcher mOnBackPressedDispatcher;
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
execPendingActions();
if (mOnBackPressedCallback.isEnabled()) {
popBackStackImmediate();
} else {
mOnBackPressedDispatcher.onBackPressed();
}
}
};
//3.5.2 add callback object addcallback
public void attachController(@NonNull FragmentHostCallback host, @NonNull FragmentContainer container, @Nullable final Fragment parent) {
if (mHost != null) throw new IllegalStateException("Already attached");
...
// Set up the OnBackPressedCallback
if (host instanceof OnBackPressedDispatcherOwner) {
OnBackPressedDispatcherOwner dispatcherOwner = ((OnBackPressedDispatcherOwner) host);
mOnBackPressedDispatcher = dispatcherOwner.getOnBackPressedDispatcher();
LifecycleOwner owner = parent != null ? parent : dispatcherOwner;
mOnBackPressedDispatcher.addCallback(owner, mOnBackPressedCallback);
}
...
}
//3.5.3 when executing a transaction, try to modify the callback object status
void scheduleCommit() {
...
updateOnBackPressedCallbackEnabled();
}
private void updateOnBackPressedCallbackEnabled() {
if (mPendingActions != null && !mPendingActions.isEmpty()) {
mOnBackPressedCallback.setEnabled(true);
return;
}
mOnBackPressedCallback.setEnabled(getBackStackEntryCount() > 0 && isPrimaryNavigation(mParent));
}
//3.5.4 recycling
public void dispatchDestroy() {
mDestroyed = true;
...
if (mOnBackPressedDispatcher != null) {
// mOnBackPressedDispatcher can hold a reference to the host
// so we need to null it out to prevent memory leaks
mOnBackPressedCallback.remove();
mOnBackPressedDispatcher = null;
}
}
If you lack a clear concept of fragment transaction, be sure to read an article I wrote earlier:Do you really understand fragment? Core principle analysis of androidx fragment
After discussing the usage & implementation principle of onbackpresseddispatcher, let’s practice it directly through some application scenarios:
4. Press the back key again to exit
Pressing the return key again to exit is a very common function, which is essentially an exit recovery. There are also many incomplete implementation methods on the Internet. In fact, this function seems simple, but it hides some optimization details. Let’s have a look~
4.1 demand analysis
First, I analyzed dozens of well-known apps and summarized four types of return key interaction:
classification | describe | give an example |
---|---|---|
1. System default behavior | Return key events are handled by the system, and the application does not intervene | Wechat, Alipay, etc |
2. Press again to exit | Whether to click the back button again within two seconds. If yes, exit | Iqiyi, Gaode, etc |
3. Return to Home tab | Press once to return to the Home tab, and then press once to exit | Facebook, instagram, etc |
4. Refresh information flow | Press once to refresh the information flow, and then press once to exit | Little red book, today’s headlines, etc |

4.2 how to exit the app?
Interaction logic mainly depends on the product form and specific application scenarios. For our technical students, we also need to consider the differences of different ways to exit the app. By observing the actual effects of the above apps, I sorted out the following four ways to exit the app:
-
1. System default behavior:Submit the fallback event to the system for processing, and the default behavior of the system is finish() current activity. If the current activity is at the bottom of the stack, transfer the activity task stack to the background;
-
2. Call movetasktoback():Manually transfer the task stack of the current activity to the background, and the effect is similar to the default behavior of the system (this method receives a nonroot parameter: true: only the current activity is required to be at the bottom of the stack, false: the current activity is not required to be at the bottom of the stack). Because the activity is not actually destroyed, the next time the user returns to the application, it will be hot started;
-
3. Call finish():End the current activity. If the current activity is at the bottom of the stack, the activity task stack will be destroyed. If the current activity is the last component of the process, the process will also end. It should be noted that the memory will not be recovered immediately after the process is completed. In the future (for a period of time), the user will restart the application as a warm start, which is faster than a cold start;
-
4. Call system Exit (0) kill appKill the process JVM, and it will take more time for the user to restart as a cold start in the future.
So, how should we choose? In general, “calling movetasktoback()” performs best. There are two arguments:
-
1. The purpose of clicking the back button twice is to save the user and confirm that the user really needs to exit. Then, the behavior after exiting is the same as the default behavior without interception. This point can be satisfied by movetasktoback(), while finish() and system The behavior of exit (0) is more serious than the default behavior.
-
2. Movetasktoback() quits the application and does not really destroy the application. When the user returns to the application again, it is a hot start and the recovery speed is the fastest.
It should be noted that system. Is generally not recommended Exit (0) and process Killprocess (process. Mypid) to exit the application. Because the performance of these APIs is not ideal:
-
1. When the called activity is not at the top of the stack, kill the process and the system will restart the app immediately (it may be that the system thinks that the foreground app is terminated unexpectedly and will restart automatically);
-
2. When the app exits, the sticky service will automatically restart (service #onstartcommand() returns to start_ Sticky service), sticky service will run consistently unless stopped manually.
classification | Apply return effect | give an example |
---|---|---|
1. System default behavior | Hot start | Wechat, Alipay, etc |
2. Call movetasktoback() | Hot start | QQ music, little red book, etc |
3. Call finish() | Warm start | To be confirmed (alternative iqiyi, Gaode, etc.) |
4. Call system Exit (0) kill app | cold boot | To be confirmed (alternative iqiyi, Gaode, etc.) |
Process. Killprocess (process. Mypid) and system What is the difference between exit (0)? todo
4.3 specific code implementation
BackPressActivity.kt
fun Context.startBackPressActivity() {
startActivity(Intent(this, BackPressActivity::class.java))
}
class BackPressActivity : AppCompatActivity(R.layout.activity_backpress) {
//Viewbinding + kotlin delegation
private val binding by viewBinding(ActivityBackpressBinding::bind)
/**
*The last time the return key was clicked
*/
private var lastBackPressTime = -1L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Add callback object
onBackPressedDispatcher.addCallback(this, onBackPress)
//Return button
binding.ivBack.setOnClickListener {
onBackPressed()
}
}
private val onBackPress = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (popBackStack()) {
return
}
val currentTIme = System.currentTimeMillis()
if (lastBackPressTime == -1L || currentTIme - lastBackPressTime >= 2000) {
//Display prompt information
showBackPressTip()
//Record time
lastBackPressTime = currentTIme
} else {
//Exit application
finish()
// android.os.Process.killProcess(android.os.Process.myPid())
// System.exit(0) // exitProcess(0)
// moveTaskToBack(false)
}
}
}
private fun showBackPressTip() {
Toast. Maketext (this, "press again to exit", toast. Length_short) show();
}
}
The logic of this code is not complicated. We mainly added a callback object through onbackpresseddispatcher#addcallback(), thus interfering with the logic of the return key event: “click the return key for the first time to pop up a prompt, and click the return key again within two seconds to exit the application”.
In addition, the following code needs to be explained:private val binding by viewBinding(ActivityBackpressBinding::bind)
。 In fact, this is a view binding scheme using viewbinding + kotlin delegate attribute. Compared with the traditional schemes such as findviewbyid, butterknife and kotlin synthetics, this scheme performs better from multiple angles. For specific analysis, you can see an article I wrote before:Android | viewbinding and kotlin entrust a combination of two swords
4.4 Optimization: compatible with fragment return stack
The previous section can basically meet the requirements, but consider a case: there are multiple fragment transactions added to the return stack in the page. When you click the return key, you need to clear the return stack in turn, and finally follow the logic of “press the return key again to exit”.
At this point, you will find that the method in the previous section will not directly exit the logic after the stack is cleared. The reason is also well understood. Because the joining time of the fallback object of the activity is earlier than that of the fallback object in the fragmentmanagerimpl, the fallback logic of the activity takes priority. The solution is to pop up the fragment transaction return stack manually in the activtiy fallback logic. The complete demonstration code is as follows:
BackPressActivity.kt
class BackPressActivity : AppCompatActivity(R.layout.activity_backpress) {
private val binding by viewBinding(ActivityBackpressBinding::bind)
/**
*The last time the return key was clicked
*/
private var lastBackPressTime = -1L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addFragmentToStack()
onBackPressedDispatcher.addCallback(this, onBackPress)
binding.ivBack.setOnClickListener {
onBackPressed()
}
}
private fun addFragmentToStack() {
//Tip: in order to focus on the problem, the scene reconstructed by activity is not considered here
for (index in 1..5) {
supportFragmentManager.beginTransaction().let { it ->
it.add(
R.id.container,
BackPressFragment().also { it.text = "fragment_$index" },
"fragment_$index"
)
it.addToBackStack(null)
it.commit()
}
}
}
/**
*@ return true: no fragment pop-up false: fragment pop-up
*/
private fun popBackStack(): Boolean {
//When the fragment state is saved, the return stack will not pop up
return supportFragmentManager.isStateSaved
|| supportFragmentManager.popBackStackImmediate()
}
private val onBackPress = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (popBackStack()) {
return
}
val currentTIme = System.currentTimeMillis()
if (lastBackPressTime == -1L || currentTIme - lastBackPressTime >= 2000) {
//Display prompt information
showBackPressTip()
//Record time
lastBackPressTime = currentTIme
} else {
//Exit application
finish()
// android.os.Process.killProcess(android.os.Process.myPid())
// System.exit(0) // exitProcess(0)
// moveTaskToBack(false)
}
}
}
private fun showBackPressTip() {
Toast. Maketext (this, "press again to exit", toast. Length_short) show();
}
}
4.5 use in fragment
TestFragment.kt
class TestFragment : Fragment() {
private val dispatcher by lazy {requireActivity().onBackPressedDispatcher}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Toast.makeText(context, "TestFragment - handleOnBackPressed", Toast.LENGTH_SHORT).show()
}
})
}
}
4.6 other finish() methods
In addition, finish () has some similar APIs, which can be supplemented as follows:
- Finishaffinity(): close all activities under the current activity in the current activity task stack (for example, a starts B and B starts C. If b calls finishaffinity(), a and B will be closed while C remains). This API is introduced after API 16, preferably through activitycompat Finishaffinity() call.
- Finishaftertransition(): after the transition animation is executed, the transition animation needs to be defined through activityoptions. This API is introduced after API 21, preferably through activitycompat Finishaftertransition() call.
5. Summary
That’s all for the discussion on onbackpresseddispatcher. I’ll leave you two questions to think about:
- 1. If a dialog pops up on the activity, click the return key to close the dialog first, or will it be distributed to onbackpresseddispatcher? What if popupwindow pops up?
- 2. A floating layer pops up in the WebView of activity. How to click the back button to close the floating layer first and click again to return to the page?
reference material
- Jetpack Application Architecture Guide——Official documents
- Provide custom return navigation——Official documents
- Past, present and future of fragment——Google developer
It’s not easy to create. Your “three company” is the biggest driving force of ugliness. I’ll see you next time!
