Back Press Handlingは、Androidのシステム戻るボタンをインターセプトして処理するメカニズムであり、押されたときにどのアクションが実行されるかを決定します。Android Developers (2024)によると、Android 11以降、従来のonBackPressed()メソッドはOnBackPressedDispatcherに置き換えられました。新しいAPIは、Activityだけでなく、階層の任意のレベルのコンポーネントが押下をインターセプトできるようにします。主な違い — 優先順位を持つチェーンでの複数コールバックのサポート。
主要なポイント
Back Press Handlingは、ユーザーがハードウェアまたはソフトウェアの戻るボタンを押したときに何が起こるかを決定するAndroidのシステムメカニズムです。コンテキストに応じて、押下は次のことを行います:現在の画面を閉じて前の画面に戻る、キーボードを隠す、DrawerまたはBottom Sheetを閉じる、ユーザーがルート画面にいる場合はアプリを終了する。
戻るボタンの動作はAndroidのバージョンごとに進化してきました。Android 10はジェスチャーナビゲーションを導入し、Android 11はOnBackPressedDispatcherを標準APIとして導入し、Android 13は拡張されたpredictive back gestureのサポートをもたらしました。これは、アクションが実際に実行される前にシステムが遷移アニメーションを表示するものです。Googleはすべてのデバイスで予測可能で一貫性のあるBack Press動作に向けて着実に進んでいます。
戻るボタンの適切な処理は、AndroidアプリにおいてUXの重要な要素です。ユーザーは、戻るを押すとナビゲーションスタックの前の画面に戻り、予期せずアプリが閉じないことを期待します。この期待に反することは、Google Playで否定的なレビューや低評価を受ける主な原因の一つです。
AndroidのBack Press APIの歴史は、プラットフォーム全体の進化を反映しています:Activityの単純なメソッドから、ライフサイクルサポートとComposeを備えた柔軟なコールバックシステムへ。開発の3つの段階を見てみましょう。
最初のAndroid API以来、戻るボタンはActivityクラスのonBackPressedメソッドで処理されていました。開発者はこのメソッドをオーバーライドして独自のロジックを記述していました。問題は、FragmentとViewが押下をインターセプトできないことでした — すべての制御はActivityを通じて行われていました。これにより、肥大化したActivityと、誰が押下を処理すべきかを決定する複雑なif-elseチェーンが発生しました。
Activity 1.0.0(AndroidX)で、GoogleはOnBackPressedDispatcherを導入しました。これは、Activity、Fragment、Dialog、カスタムViewなど、任意のコンポーネントからコールバックを受け入れる中央ディスパッチャです。コールバックは順序(priority経由)で登録され、動的に追加または削除できます。OnBackPressedDispatcherは古いonBackPressedより前に呼び出され、Activityが処理する前に押下をインターセプトできます。
Android 13はpredictive back gestureを導入しました — ユーザーがジェスチャーを完了する前に、戻る押下がどこにつながるかを示すシステムアニメーションです。このアニメーションをサポートするには、開発者はOnBackPressedDispatcherを使用し、isEnabledプロパティを通じてコールバックがシステムアニメーションをサポートするかどうかを示す必要があります。コールバックがpredictive animationをサポートしない場合、システムはアプリのコンテキストに一致しない可能性のあるデフォルトのアニメーションを表示します。
| API | 最小SDK | Fragmentサポート | Predictive Back |
|---|---|---|---|
| onBackPressed | API Level 1 | Activity経由 | いいえ |
| OnBackPressedDispatcher | Activity 1.0.0 | 直接 | 部分的 |
| OnBackPressedDispatcher + lifecycle | Activity 1.3.0 | Lifecycle-aware | 完全 |
OnBackPressedDispatcherは、新しいBack Press APIの核心です。コールバックのチェーンを管理し、最初のコールバックがイベントを処理するまで順番に呼び出します。どのコールバックも押下を処理しない場合、ディスパッチャはデフォルトのアクションを実行します — Activityのfinish()またはNavigation ComponentのpopBackStack()を呼び出します。
コールバックは、LifecycleOwnerとOnBackPressedCallbackオブジェクトを指定してaddCallbackで登録します。コールバックにはisEnabledプロパティがあり、falseに設定するとコールバックはスキップされます。優先度には、0(最低)からInteger.MAX_VALUEまでの値を渡せます。Fragment Activity Result APIは、このメカニズムを使用してライフサイクルにバインドされたコールバックを自動登録します。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : OnBackPressedCallback(enabled = true) {
override fun handleOnBackPressed() {
if (isDrawerOpen) { closeDrawer() }
else { isEnabled = false; onBackPressed() }
}
}
onBackPressedDispatcher.addCallback(this, callback)
}
コールバックは、LifecycleOwnerがDESTROYED状態になると自動的に削除されます。これにより、画面回転時のコールバックリークという古い問題が解決されます。コールバックがFragmentに追加された場合、Fragmentが破棄されると削除されることが保証されます。コールバックを一時的に無効にするには、isEnabledプロパティを使用します — 削除と再登録なしで切り替えられます。
呼び出し順序は追加順序の逆です:最後に追加されたコールバックが最初に制御を受け取ります。これは、最もネストされたUI要素(例えば、Fragment内のBottom Sheet)が親Fragmentより先に押下を処理すべきであるため理にかなっています。最も深いコールバックが押下を処理しない場合(isEnabled = false)、制御はチェーンの次のコールバックに渡されます。
Fragment APIは、requireActivity().onBackPressedDispatcherメソッドを介してOnBackPressedDispatcherとの独自の統合を提供します。Fragment 1.2.0以降、各Fragmentは独自のコールバックを登録でき、これは自動的にFragmentのライフサイクルにバインドされ、破棄時に削除されます。
Fragmentでのコールバックの登録は、onCreate、onViewCreated、またはView自体で行われます — 重要なのは、LifecycleOwner(Fragment)がアクティブであることです。FragmentがSTARTED状態に移行するとコールバックが有効になり、STOPPEDになると無効になります。これにより、非表示のFragment(ViewPager内)が戻る押下を処理しないことが保証されます。
class EditorFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
showDiscardDialog()
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}
}
ダイアログとBottomSheetは、閉じるために戻る押下を自動的にインターセプトします。閉じる前に追加のアクションを実行する必要がある場合は、より高い優先度のコールバックを登録してください。重要:DialogでsetCancelable(false)を設定した場合、コールバックは起動しません — これはシステムの動作です。
Jetpack Composeは、BackHandlerコンポーザブル関数を介して戻るボタンを処理する宣言的APIを提供します。BackHandlerはenabled(ブール状態)とonBack — 押下時に呼び出されるコールバックを受け入れます。enabled = falseの場合、押下はチェーンのさらに先に渡されます。
BackHandlerは、親ActivityのOnBackPressedDispatcherに自動的にOnBackPressedCallbackを登録します。コンポーザブルのライフサイクルを尊重します:コンポジションを離れると、コールバックは削除されます。enabledは状態にバインドできます — 例えば、フォームに未保存の変更がある場合のみ確認ダイアログを表示するなど。
@Composable
fun EditScreen(hasUnsavedChanges: Boolean) {
BackHandler(enabled = hasUnsavedChanges) {
// Show confirmation dialog
}
Column {
TextField(value = ..., onValueChange = ...)
}
}
ComposeでのPredictive back gestureはCompose 1.5.0以降でサポートされています。BackHandlerは、デバイスで有効になっている場合、システムの遷移アニメーションを自動的に処理します。カスタムのpredictiveアニメーションには、predictiveBackHandlerモディファイアを使用します。これはジェスチャーの進行状況を0から1で返します。
Back Press Handlingは単純に見えますが、実際には開発者は系統だったエラーを数多く犯します。Googleの推奨事項とコミュニティの経験に基づいて、最も一般的な問題とその解決策を見てみましょう。
handleOnBackPressedで直接finish()を呼び出すと、ナビゲーションスタックにバックグラウンド画面がある場合、予期せぬアプリ終了につながる可能性があります。Activityを閉じる前に、必ずNavigation ComponentまたはCoordinatorを介してNavController.backStackを確認してください。
LifecycleOwnerなしでコールバックを登録すると(パラメータなしの古いaddCallbackを使用)、コールバックは永久に存続し、Activityが既に破棄されている場合にNullPointerExceptionを引き起こす可能性があります。必ずLifecycleOwnerを指定してaddCallback(this, callback)を使用してください。
モーダルウィンドウ(Bottom Sheet、Dialog)の場合、ウィンドウが表示されているときのみisEnabled = trueに設定してください。ウィンドウの状態をチェックするラムダを指定してaddCallbackを使用します。Navigation ComponentはNavHostに対してこれを自動的に管理します。
戻るボタンの高速ダブルプレスは、finish()の二重呼び出しにつながる可能性があります。フラグまたはthrottleLastを使用して、500ms以内の繰り返し呼び出しから保護してください。Navigation Componentはこの状況をネイティブに処理しますが、カスタムシナリオでは手動で保護を実装する必要があります。
private var lastBackPressTime = 0L
override fun handleOnBackPressed() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastBackPressTime > 500) {
lastBackPressTime = currentTime
navigateBack()
}
}
よくある質問
onBackPressedはAndroid 11で非推奨になりました。Activityレベルでしか機能しないためです。OnBackPressedDispatcherは、ライフサイクルサポートを備えた単一のメカニズムを通じて、任意のコンポーネント(Fragment、Dialog、View)が押下をインターセプトできるようにします。
Androidシステムは、アプリケーションが受け取る前に、端からのスワイプジェスチャーをシステムの戻る押下に変換します。OnBackPressedDispatcherのレベルでは、これら2つのイベントを区別できません — 両方ともhandleOnBackPressedとして到着します。
はい、OnBackPressedDispatcherの処理は、3ボタンデバイスとジェスチャーナビゲーションの両方で同じです。インターセプトコードはナビゲーションの種類に依存しません — システム自体がジェスチャーをディスパッチャ呼び出しに変換します。
Android 13+エミュレーターのDeveloper Optionsでpredictive backを有効にしてください。ADBを使用します:`adb shell settings put global enable_back_animation 1`。有効にすると、システムアニメーションが戻る押下時の遷移プレビューを表示します。
2つの条件を確認してください:LifecycleOwnerがSTARTEDまたはRESUMED状態である必要があり、コールバックのisEnabledがtrueである必要があります。両方の条件が満たされている場合、コールバックが正しいOnBackPressedDispatcherに追加されていることを確認してください — FragmentではrequireActivity().onBackPressedDispatcherを使用します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。