.NET MAUIで作ったAndroidアプリを「設定変更後に終了→自動で再オープン」したいのに、FinishAffinity()+Exit(0)では見た目だけ閉じて履歴に残り、ユーザーが履歴から開かないと復帰しない…。この記事では、再起動用IntentでメインActivityを起動し直す実装と、AlarmManager利用時の権限・デバッグ時の落とし穴をまとめます。
.NET MAUI(Android)で「アプリ再起動」がうまくいかない症状
.NET MAUIでAndroidアプリを作っていると、次のような要件にぶつかることがあります。
- ログアウトやユーザー切り替え後に、アプリ状態を完全に初期化したい
- 言語(Culture)やテーマ、接続先(API Base URL)などの設定を変えたので、起動時の初期化処理からやり直したい
- 更新処理のあとに「一度アプリを閉じて、すぐ開き直す」導線を作りたい
そこでよく試されるのが、AlarmManager + PendingIntentで起動を予約しつつ、現在のActivity群を閉じてプロセスを落とすパターンです。流れとしては次のイメージです。
- 少し先(100ms〜数秒後)に、ランチャーActivityを起動するIntentをアラームで予約
FinishAffinity()で画面(Activity)を閉じるExit(0)などでプロセス終了
しかし実際には、見た目は閉じたのに履歴(Recents)にタスクが残り続け、しかも自動で再オープンされず、ユーザーが履歴からタップしないと復帰しない、という現象が起こりがちです。
なぜAndroidは「完全終了→確実に再起動」を保証しづらいのか
ここが重要ポイントです。Androidは「アプリを終了させる」ことをアプリ側が自由にコントロールできる設計ではありません。もう少し噛み砕くと、次の3つが混ざって見えます。
| 用語 | ユーザーの見え方 | 実体 | 開発者が誤解しやすい点 |
|---|---|---|---|
| プロセス | アプリが生きている/死んでいる | Linuxプロセス | プロセスを殺しても、履歴(タスク)が消えるとは限らない |
| Activityスタック | 戻るボタンで戻れる画面 | 画面の積み重ね | 1枚閉じても別Activityが残っていると「閉じたつもり」になる |
| タスク(Recents) | 履歴一覧に残るカード | Activityのまとまり(タスク) | タスクは「スクリーンショット+履歴」として残るため、プロセス終了だけでは消えないことがある |
つまり、Exit(0)でプロセスを落としても、Androidが「はい、今すぐ同じアプリをフォアグラウンドで起動し直します」とは限りません。むしろ最近のAndroidほど、バックグラウンドからのActivity起動は制限されやすく、アラームでの自動再起動が思った通りに前面へ出てこないことがあります。
その結果、「閉じたように見える」「でも履歴は残る」「再起動はされない」という体験になりやすいわけです。
解決の考え方:再起動用IntentでメインActivityを起動し、今のActivity群を確実に閉じる
Androidでアプリ再起動を“それっぽく”実現するうえで現実的なのは、次の方針です。
- 起動用のIntent(Launch Intent)を取得する
- 「再起動タスク」として起動するIntentを作る(新しいタスクとして、既存のタスクを整理)
- 先にStartActivityで新タスクを立ち上げ、その後に現在のActivity群を閉じる
この方式の狙いはシンプルで、バックグラウンドから起動させようとしないことです。まだ自分がフォアグラウンドにいるうちに、次の起動を「ユーザー操作に近い形」で成立させる、というイメージです。
方式比較(どれを選ぶべきか)
| 方式 | 特徴 | メリット | デメリット/注意 | おすすめ度 |
|---|---|---|---|---|
| Launch Intent + RestartActivityTask(即時) | IntentでメインActivityを新タスク起動し直す | 前面で起動しやすい/権限が増えない | “完全な再起動”は保証できない(Androidの設計上) | 高 |
| AlarmManager + PendingIntent(遅延) | 少し後で起動を予約してから終了 | 「先に落としてから起動」の順にしやすい | Android 12以降はExact alarm権限が絡むことがある/バックグラウンド起動制限にハマりやすい | 中 |
| 画面スタックのリセット(擬似再起動) | Shellのルートへ戻す、DIを再初期化する等 | OS仕様に逆らわない/安定 | ネイティブ層の状態は残る/「本当に全部初期化」には限界 | 要件次第 |
.NET MAUIでの実装例:Android限定で再起動処理を用意する
ここからは、質問で挙がりやすい実装(#if ANDROIDでAndroid限定にする)を、WordPressへ貼れる形で整理します。ポイントは次の通りです。
PackageManager.GetLaunchIntentForPackage()で起動Intentを取るIntent.MakeRestartActivityTask(componentName)で「再起動タスク」を作るStartActivity()で起動する- 最後に
FinishAffinity()やRuntime.GetRuntime().Exit(0)で現在のActivity群/プロセスを閉じる
必要になりがちな名前空間(short nameで書きたい場合)
下記は「完全修飾名で書かずに、短い名前で書きたい」場合に追加しがちなusingです(実際の構成に合わせて調整してください)。
#if ANDROID
using Android.App;
using Android.Content;
using Android.Content.PM;
using Java.Lang;
using Microsoft.Maui.ApplicationModel;
#endif
コード例:まずは“安定寄り”に、Activity群を閉じてメインActivityを起動し直す
まずおすすめなのは、Activity(画面)をきちんと閉じることを優先し、プロセス強制終了(Exit)は必要になってから追加する形です。
public static class AppRestart
{
public static void Restart()
{
#if ANDROID
var context = Android.App.Application.Context;
var pm = context.PackageManager;
// 1) 通常起動と同じ「ランチャーIntent」を取得
var launchIntent = pm.GetLaunchIntentForPackage(context.PackageName);
if (launchIntent?.Component == null)
return;
// 2) 「再起動タスク」を作って起動(既存タスクを整理してメインActivityをルートに)
var restartIntent = Android.Content.Intent.MakeRestartActivityTask(launchIntent.Component);
restartIntent.AddFlags(Android.Content.ActivityFlags.NewTask);
// 3) 先に起動を投げる(フォアグラウンド中に成立させるのがコツ)
context.StartActivity(restartIntent);
// 4) 今のActivity群を閉じる(戻るスタックを残しにくい)
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
activity?.FinishAffinity();
#endif
}
}
コード例:それでも初期化が不十分なら“強制寄り”にプロセスを終了する
「DIのシングルトンが残る」「ネイティブSDKの初期化がやり直せない」など、どうしてもプロセスを落としたい事情がある場合は、最後にExitを追加します。ただしデバッグ中は挙動が変わりやすい点に注意してください。
public static class AppRestartHard
{
public static void Restart()
{
#if ANDROID
var context = Android.App.Application.Context;
var pm = context.PackageManager;
var launchIntent = pm.GetLaunchIntentForPackage(context.PackageName);
if (launchIntent?.Component == null)
return;
var restartIntent = Android.Content.Intent.MakeRestartActivityTask(launchIntent.Component);
restartIntent.AddFlags(Android.Content.ActivityFlags.NewTask);
context.StartActivity(restartIntent);
// “ちゃんと閉じる”寄り
Microsoft.Maui.ApplicationModel.Platform.CurrentActivity?.FinishAffinity();
// “完全に落とす”寄り(最終手段)
Java.Lang.Runtime.GetRuntime().Exit(0);
#endif
}
}
UIスレッドで呼ぶべき?
再起動処理は「画面操作(Activityの終了)」を含むため、呼び出し元がバックグラウンドスレッドの可能性があるなら、MAUIのMainThread経由で呼ぶと安全です。
await Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(() =>
{
AppRestart.Restart();
});
終了処理の選び方:FinishAffinity / FinishAndRemoveTask / Exit(0) の違い
“再起動”実装で迷うのが、最後の「閉じ方」です。Androidはアプリが自分で完全終了を保証できないため、やり過ぎると不安定になり、やらなさ過ぎるとスタックが残る、というバランス問題になります。
| API | 何をするか | 向いているケース | 注意点 |
|---|---|---|---|
Finish() | 現在のActivityだけ閉じる | 単一画面で終わるアプリ/戻るスタックが少ない | 他Activityが残ると「閉じたつもり」になりやすい |
FinishAffinity() | 同一タスク内の関連Activityをまとめて閉じる | 「画面をちゃんと閉じたい」寄り | Activity参照が必要。MAUIではCurrentActivityがnullになるタイミングがある |
FinishAndRemoveTask() | タスクを閉じ、Recentsからも外す方向 | 可能なら履歴から消したい | 端末やOSにより期待通りにならないことがある/呼べる環境に注意 |
Runtime.GetRuntime().Exit(0) | プロセスを強制終了する | どうしてもプロセスの再生成を促したい | デバッグ中に挙動が変わる/未処理のI/Oなどがあると副作用が出る |
実務的には、まずFinishAffinity()でActivity群を閉じる方向を優先し、必要がある場合だけ最後にExit(0)を追加する、という段階設計が無難です。特にMAUIアプリでは、アプリ内で多数のサービスやシングルトンを抱えがちなので、強制終了は「最後の手段」と考えると事故が減ります。
AlarmManagerで遅延再起動する場合の注意点(Manifestと権限)
どうしても「少し待ってから起動したい」「先に終了を確実に済ませてから起動したい」という理由で、AlarmManagerで再起動を予約するケースもあります。ただし、この方式はAndroidのバージョンやターゲットSDKにより、追加の考慮が増えます。
参考:AlarmManager方式の最小サンプル(イメージ)
質問で提示されやすい構成を、MAUI向けに簡略化すると次のような形になります(実際のプロジェクトでは例外処理やフラグ管理も入れてください)。
#if ANDROID
var context = Android.App.Application.Context;
var pm = context.PackageManager;
var launchIntent = pm.GetLaunchIntentForPackage(context.PackageName);
if (launchIntent?.Component == null) return;
var pendingIntent = Android.App.PendingIntent.GetActivity(
context,
0,
launchIntent,
Android.App.PendingIntentFlags.CancelCurrent | Android.App.PendingIntentFlags.Immutable);
var alarmManager = (Android.App.AlarmManager?)context.GetSystemService(Android.Content.Context.AlarmService);
alarmManager?.Set(
Android.App.AlarmType.Rtc,
Java.Lang.JavaSystem.CurrentTimeMillis() + 300,
pendingIntent);
// 終了(この順番だと「起動が前面に出ない」ケースがある)
Microsoft.Maui.ApplicationModel.Platform.CurrentActivity?.FinishAffinity();
Java.Lang.Runtime.GetRuntime().Exit(0);
#endif
この方式は「いったん落としてから起動する」順にできる反面、OSや端末の制限の影響を受けやすく、質問のように見た目だけ閉じて履歴に残り、起動が前面に出ない状況が起こり得ます。
Android 12以降のExact alarm(正確なアラーム)に注意
アラームの設定方法によっては「正確な時刻で起こす(Exact)」扱いになり、Android 12(API 31)以降で権限や特別な許可が絡む場合があります。質問で挙がりやすいのは次の2つです。
android.permission.SCHEDULE_EXACT_ALARM(maxSdkVersion付きで宣言する案)android.permission.USE_EXACT_ALARM
ただし重要なのは、AlarmManagerを使わずにStartActivityで即時起動する方式なら、これらの権限は原則不要という点です。最初から即時起動方式を採ると、権限や端末差分でのトラブルを大きく減らせます。
Manifest記述例(AlarmManager方式を採るときだけ)
AndroidManifest.xml(またはMAUIのマニフェスト統合)で、必要に応じて次のような宣言が検討されます。
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
ここはプロジェクトのターゲットSDK、使っているアラームAPI(setExactか、setか、setExactAndAllowWhileIdleか)、そして「正確さが本当に必要か」で判断が変わります。再起動のためだけにExact扱いが必要とは限らないため、まずは即時StartActivity方式を検討するのがコスパが良いです。
動作確認でハマりやすいポイント(デバッグ時に再起動できない問題)
再起動処理は、実装が合っていてもテスト方法でハマります。特に次の2点は再現頻度が高いです。
- デバッグ実行中はExit(0)系が意図通りに動かないことがある
- Visual Studioのデバッガがプロセス終了を検知した時点でセッションが切れ、起動し直しの挙動が読みにくくなる
そのため、再起動の動作確認は次の手順が安定します。
- いったんデバッグを停止(アタッチなし)
- 端末/エミュレータのランチャーからアプリを手動で起動
- アプリ内で再起動処理を実行して、フォアグラウンド復帰まで確認
さらに端末固有の省電力(バッテリー最適化)や、メーカー独自のバックグラウンド制限が強い機種では、AlarmManager方式が表に出ないことがあります。即時StartActivity方式は、その影響を受けにくいのが利点です。
よくある失敗と対処(“再起動ループ”や“状態が引き継がれる”を防ぐ)
再起動直後にまた再起動してしまう(無限ループ)
再起動処理を「設定変更イベント」や「アプリ起動時の初期化」に直結させると、条件によっては起動のたびに再起動が走り、無限ループになります。対策としては次のような考え方が有効です。
- 再起動フラグを
Preferences等に保存し、1回起動したら必ずクリアする - “再起動が必要な変更”と“即時反映できる変更”を分ける
- 起動直後ではなく、ユーザー操作で「再起動します」を明示してから実行する
再起動したのに状態が残る(キャッシュやログイン情報)
プロセスを落としても、永続化しているデータ(Preferences、SQLite、ファイル、KeyStoreなど)は当然残ります。「再起動=完全初期化」ではないので、ログアウトやユーザー切り替えが目的なら、再起動処理とは別に次の整理が必要です。
- アクセストークンやユーザー識別子の削除
- DIコンテナで保持しているセッション系サービスの作り直し(またはスコープ化)
- 画像キャッシュやAPIレスポンスキャッシュのクリア(必要な場合のみ)
ここを丁寧に設計すると、「再起動が必要な場面」自体を減らせます。Androidの仕様に逆らわない、という意味でも重要です。
“本当の再起動”が不要なら:擬似再起動(画面とサービスの再初期化)も検討する
要件によっては、OSレベルの再起動を狙うより、アプリ内で安全に状態を作り直す方がユーザー体験も安定します。たとえばMAUIなら次のようなアプローチがあります。
- Shellをルートから作り直して、ナビゲーションスタックを初期化する
- ログアウト後はログイン画面をルートにし、戻るで戻れないようにする
- 設定変更をアプリ全体に配信し、再描画(Theme/Cultureの再適用)する
「どうしても再起動が必要」と判断できるのは、ネイティブ層の初期化(SDKの初期化やプロセス単位の状態)をやり直したい場合など、限られます。まずは擬似再起動で満たせないかを検討すると、保守コストが下がります。
実装チェックリスト(再起動を安定させるための最終確認)
| チェック項目 | 意図 | 確認ポイント |
|---|---|---|
| StartActivityを先に呼んでいる | バックグラウンド起動制限を避ける | Exit(0)やFinishAffinity()より前に再起動Intentを投げているか |
| 再起動IntentがメインActivityを指している | 通常起動と同じ入口に揃える | GetLaunchIntentForPackageのComponentNameを使っているか |
| タスクを整理する構成になっている | 戻るスタックを残さない | MakeRestartActivityTask / NEW_TASK / CLEAR_TASK相当の構成になっているか |
| 終了処理をやり過ぎていない | 不安定化を防ぐ | まずはFinishAffinity中心で、必要時のみExit(0)にしているか |
| デバッグ以外でも検証した | デバッガの影響を排除 | 端末から手動起動→再起動を確認したか |
| AlarmManager方式なら権限とOS差分を考慮 | Android 12+の制約に対応 | Exact alarm権限が本当に必要か/不要なら即時方式へ寄せたか |
まとめ:Androidで再起動するなら「Intentで起動し直してから閉じる」が現実的
.NET MAUI(Android)で「コードからアプリを再起動したい」とき、見た目だけ閉じて履歴に残り、ユーザー操作が必要になるのは珍しくありません。Androidの設計上、アプリが“完全終了→確実再起動”を保証するのは難しいため、実務では次の形が安定します。
- Launch Intentを取得し、MakeRestartActivityTaskで再起動タスクを作る
- StartActivityでメインActivityを起動し直す
- FinishAffinityなどで現在のActivity群を閉じる(必要なら最後にExit(0))
また、AlarmManagerによる遅延再起動は、Android 12以降のExact alarm権限や端末差分が増えるため、要件が許すなら即時StartActivity方式を優先すると、トラブルシュートが大幅に楽になります。

コメント