ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-05-17中級

Androidのテーマ切替で白画面が出る問題:Activity.recreate()をやめてAppRestarterで直した話

テーマ切替のたびに白い画面が一瞬出る問題を、Activity.recreate()からAppRestarter.safeRestart()に切り替えて根治した実体験。Antigravityに原因分析を依頼して見えてきた設計上の落とし穴も記録します。

android27themeapprestarteractivityapp-dev47ui

テーマ切替ボタンを押したとき、一瞬だけ白い画面が表示される——Beautiful HD Wallpapersのv2.0.0に存在していた問題です。

手元の端末では再現しにくく、ユーザーレビューで「テーマが変わるとき画面が白くなる」というフィードバックが複数来るまで気づけていませんでした。Play Consoleを確認すると、端末スペックの低い機種、特にAndroid 7〜9の古いミドルレンジ機種で発生頻度が高いことが分かりました。

原因の見当はつきました。Activity.recreate() です。ただ、なぜそれが白画面を引き起こすのか、そしてどう直すのかを、Antigravityに聞いてみることにしました。

Activity.recreate()の落とし穴

Androidでテーマを動的に変更する場合、変更をUIに反映するにはActivityを再生成する必要があります。最もシンプルな実装は recreate() を呼ぶことです。

// 最もよく使われるが、白画面を引き起こす可能性がある
fun applyTheme(isDark: Boolean) {
    AppCompatDelegate.setDefaultNightMode(
        if (isDark) AppCompatDelegate.MODE_NIGHT_YES
        else AppCompatDelegate.MODE_NIGHT_NO
    )
    activity.recreate() // ← これが問題
}

recreate() はActivityを破棄して再生成します。このとき、旧Activityの終了と新Activityの起動の間に一瞬だけウィンドウが空になる瞬間があります。スペックの高い端末では描画が間に合って気づかないことが多いのですが、描画が遅い端末では白い画面が見えてしまいます。

Antigravityへの相談と返ってきた提案

コードとエラー状況を渡して、Antigravityに状況を整理してもらいました。

以下のコードでテーマ切替後に白画面が発生する。
- setDefaultNightMode() 後に recreate() を呼んでいる
- Android 7〜9の端末で頻度が高い
- 新端末では再現しにくい

白画面の発生メカニズムと、推奨される代替実装を教えてほしい。

返ってきた分析は明確でした。「recreate() はシステムのActivity遷移アニメーションを伴うため、テーマ変更後のウィンドウが一時的に透過またはデフォルト(白)になります。推奨される回避策は、recreate() を使わず Intent で自分自身を起動し直す方法(safeRestart)です。アニメーションをなくすフラグを組み合わせることで、白画面が発生する余地をなくせます」

AppRestarter.safeRestart の実装

Antigravityが示した方針をもとに、AppRestarter クラスとして実装しました。

object AppRestarter {
    /**
     * recreate()の代わりに使用するテーマ切替用リスタート
     * アニメーションを無効化することで白画面の発生を防ぐ
     */
    fun safeRestart(activity: Activity) {
        val intent = activity.intent.apply {
            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        activity.startActivity(intent)
        activity.finish()
 
        // アニメーションを無効化(白画面抑制の核心部分)
        activity.overridePendingTransition(0, 0)
    }
}

呼び出し側はシンプルです。

fun applyTheme(isDark: Boolean) {
    AppCompatDelegate.setDefaultNightMode(
        if (isDark) AppCompatDelegate.MODE_NIGHT_YES
        else AppCompatDelegate.MODE_NIGHT_NO
    )
    // recreate() の代わりに safeRestart を使う
    AppRestarter.safeRestart(this)
}

overridePendingTransition(0, 0) がポイントです。第1引数は入場アニメーション、第2引数は退場アニメーションで、それぞれ0(アニメーションなし)を指定します。これによって、旧Activityが消えてから新Activityが現れるまでの間隙がなくなります。

Intentのフラグ選択について

FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_NEW_TASK の組み合わせは、バックスタックを意図した状態に保つために重要です。

FLAG_ACTIVITY_CLEAR_TOP だけでは、同じタスク内に複数のActivityが積み重なっている場合に意図しない状態になることがあります。Beautiful HD Wallpapersはカテゴリ→壁紙詳細→お気に入りといった階層構造があるため、テーマ切替後にトップ画面へ遷移させたい場合は FLAG_ACTIVITY_NEW_TASK を追加して明示的にタスクを再構築する方が安全です。

アプリの画面構造によって最適なフラグの組み合わせは変わります。シングルActivityアーキテクチャ(NavHostFragmentを使うアプリ)ではやや異なる対処が必要になります。その場合はFragmentのライフサイクルを再起動させる方法(NavControllerによる再描画)が現実的です。

修正後の変化

v2.1.0として段階公開(5% → 25% → 50% → 100%の順)したところ、テーマ切替に関するフィードバックが止まりました。Play Consoleのユーザーレビュー分析でも、「白画面」に関する言及が消えています。

2013年に個人でアプリ事業を始めてから12年以上経ちます。この種の「手元では再現しないが実機報告が来ている問題」は、一人で開発していると特にデバッグが難しい。端末の多様性が原因になっているケースが多く、自分の開発環境だけでは網羅できません。

Antigravityに渡したのはコードと状況説明だけです。どの端末で発生しやすいかという文脈も添えることで、ピンポイントな提案が返ってきました。問題の「何が起きているか」は把握していても「なぜそうなるか」が見えにくいとき、コードの外から俯瞰してもらう価値があります。

参考:ModalGate との組み合わせ

safeRestart を導入した後、別の問題が見えてきました。テーマ切替直後に広告モーダルや評価誘導ダイアログが重なって表示されることがあったのです。

これはActivity再生成のタイミングで複数のDialogが同時に show() を呼ぶために発生します。解決策として、モーダルの表示を一元管理する ModalGate オブジェクトを導入しました。

object ModalGate {
    private var isShowing = false
 
    fun requestShow(show: () -> Unit): Boolean {
        if (isShowing) return false
        isShowing = true
        show()
        return true
    }
 
    fun dismiss() {
        isShowing = false
    }
}

PaywallDialog、ReviewInductionDialog、RewardedInterstitialDialogのいずれも ModalGate.requestShow {} 経由で表示することで、同時表示の競合が解消されました。

v2.1.0での段階公開とクラッシュ監視

修正版はv2.1.0として段階公開(5% → 25% → 50% → 100%)でリリースしました。各フェーズでPlay ConsoleのAndroid vitalsを確認し、Crash-free usersが99.7%以上を維持できていることを確認してから次の段階に進みました。

段階公開の利点は、万が一修正に問題があったときに影響を限定できる点です。AppRestarter の実装はシンプルですが、Intentフラグの組み合わせはアプリの画面構造によって最適解が変わります。5%フェーズで実際のユーザー環境での動作を観察し、問題がないことを確認してから全ユーザーへ展開する判断ができたことで、リリース後の不安が大きく減りました。

Antigravityを使ったAndroidアプリ開発の全体像については、こちらの記事もあわせてご覧ください。

Activity.recreate() から AppRestarter.safeRestart() への切り替えは1時間もかからない修正ですが、ユーザー体験への影響は明確です。同じ問題に直面している方の参考になれば幸いです。

個人開発12年で見えてきた「地味なバグほど大事にする」という感覚

2013年から個人開発でアプリを作り続けています。今では4本のiOS/Androidアプリが累計5,000万DLを超えるまでになりましたが、最初の数年は「動いていればいい」という感覚でコードを書いていました。

テーマ切替の白画面は、機能的には問題ありません。アプリは正常に動作します。ただ、毎回テーマを変えるたびに白い画面を見たユーザーが「なんか品質が低いな」と感じることで、長期的な離脱につながります。

廣川政樹として世界各地でアートの展示をしていると、作品の微細な部分への注意が鑑賞者の体験を変えることを実感します。ソフトウェアも同じです。宮大工だった両祖父が「手を動かすことが一つの信心」と言い残した言葉を、私はコードの中でも意識するようにしています。

地味に見える修正を丁寧に重ねることが、個人開発者としてのアプリの品質を作ります。

全体を振り返ってと次のアクション

Activity.recreate() から AppRestarter.safeRestart() へ移行する手順:

  1. AppRestarter オブジェクトをプロジェクトに追加
  2. 全てのテーマ切替箇所で recreate()AppRestarter.safeRestart(this) に置き換え
  3. ModalGate を導入して Dialog 同時表示の競合を防ぐ
  4. 低スペック端末(Android 7〜9 のエミュレーター)でテーマ切替を繰り返して確認

段階的に進めるなら、まず AppRestarter だけ導入し、ModalGate は必要が生じてから追加する形で問題ありません。recreate() を置き換えるだけでも白画面問題の大半は解消されます。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-07-03
ダウンロードサイズの逆行に提出直前で気づかないために — AAB/IPA のサイズ予算をエージェントの週次ゲートで守る
メディエーションSDKやアセットの追加で静かに膨らむダウンロードサイズに、bundletool と App Thinning レポートを使ったサイズ台帳・予算ゲート・増分の内訳特定で歯止めをかける設計をまとめます。
アプリ開発2026-06-21
戻るボタンで広告が出る/出ないが安定しない — 入れ子のifを独立ガードのリストに作り替えた記録
戻るボタン押下時のインタースティシャル表示判定が、入れ子のif文で優先度が暗黙化して壊れていました。各条件を理由つきの独立ガードに分解し、決定表からテストを生成した設計をまとめます。
アプリ開発2026-05-17
AntigravityにRecyclerViewクラッシュを直させると別の場所が壊れる問題 — 防御的コピーで根本解決した話
RecyclerViewのIndexOutOfBoundsExceptionをAntigravityに修正させても再発する場合、共有リスト参照が原因の可能性があります。防御的コピーをAntigravityに正確に伝えるプロンプトとAGENTS.mdの書き方を解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →