RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-05-16中級

テーマ切替の白画面バグを recreate() なしで解決した話 — 壁紙アプリ12年の実体験

Android アプリのテーマ切替で白画面が発生する問題を、recreate() を使わず AppRestarter.safeRestart パターンで根本解決した実体験。累計5,000万DLの壁紙アプリ運営で気づいた設計の落とし穴を詳しく共有します。

Android43テーマ切替白画面AppRestarterrecreate壁紙アプリ28個人開発186

アプリにダークモード切替を実装したとき、「設定を保存 → Activity を再起動 → テーマが反映される」という流れは Android 開発者なら誰もが通る道です。多くのサンプルコードでは recreate() が使われていますが、私が長年運営している壁紙アプリでは、この実装が原因で一部ユーザーに白画面が再現していました。

個人アプリ開発を2014年から続け、累計5,000万DLを超えるアプリ群を運営してきた中で、「バグは隠れた設計の問題の表れ」だと繰り返し実感しています。今回のテーマ切替の白画面も、掘り下げてみると recreate() の非同期タイミング問題という根本原因がありました。

recreate() で白画面が出るのはなぜか

recreate() は呼び出されると現在の Activity を破棄して再生成します。問題は、破棄と生成の間に一瞬だけ「何も描画されない状態」が存在することです。

通常のデバイスでは気づかないほど短時間ですが、次の条件が重なると白画面が見えやすくなります。

  • 画像の読み込みが重い Activity(壁紙アプリは該当しやすい)
  • スペックの低い端末や Android 6〜8 系のデバイス
  • SharedPreferences の write が commit() ではなく apply() の場合(まだ書き込まれていない可能性がある)
  • AdMob など広告 SDK のコールバック処理が Activity のライフサイクルに絡んでいる場合

私のケースでは、Beautiful HD Wallpapers でテーマ切替直後に一瞬白い画面が出るというユーザーレビューが複数上がっていました。再現条件が限定的なため自分では確認しにくく、しばらく原因を特定できなかった問題です。星3〜4つのレビューに「たまに白くなる」という一文がある場合、多くのケースはこのパターンです。

AppRestarter.safeRestart とはどんなパターンか

解決の糸口になったのは、アプリを「意図的に終了 → 再起動」するアプローチです。recreate() が Activity の再生成に依存しているのに対して、AppRestarter.safeRestart パターンはアプリのプロセスごと再起動します。

実装の考え方はこうです。

// AppRestarter.kt
object AppRestarter {
    fun safeRestart(context: Context, delayMs: Long = 300L) {
        // 1. 起動 Intent を準備(アプリのルート Activity)
        val intent = context.packageManager
            .getLaunchIntentForPackage(context.packageName)
            ?.apply {
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
            }
            ?: return
 
        // 2. 設定の確実な書き込みを待ってから再起動
        Handler(Looper.getMainLooper()).postDelayed({
            context.startActivity(intent)
            // 3. 現在のプロセスを終了
            android.os.Process.killProcess(android.os.Process.myPid())
        }, delayMs)
    }
}

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

// SettingsActivity.kt — テーマ切替後の処理
fun applyTheme(isDark: Boolean) {
    // SharedPreferences に確実に保存(commit で同期書き込み)
    prefs.edit().putBoolean("dark_mode", isDark).commit()
 
    // safeRestart でアプリ全体を再起動
    AppRestarter.safeRestart(this)
}

recreate() との実際の違い

recreate()AppRestarter.safeRestart() の違いは、影響範囲にあります。

recreate()現在の Activity だけを再生成する操作です。Fragment や ViewModel のライフサイクルが複雑に絡んでいる場合、State の引き継ぎに抜けが生じることがあります。また、Glide のキャッシュや広告 SDK(AdMob など)の初期化状態が中途半端なままで Activity が再生成されることがあり、これが白画面や表示崩れの原因になります。

一方 AppRestarter.safeRestart() は、アプリプロセスをゼロから起動しなおす操作です。Application クラスの onCreate() から全部やり直すため、SDK の初期化状態が必ず整合します。

私の壁紙アプリでは AdMob の初期化が Activity のライフサイクルの早い段階で走っており、recreate() 後にこの初期化が二重になるケースがありました。これが白画面の一因でした。

SharedPreferences の apply() と commit() の選択

白画面とは直接関係ありませんが、safeRestart パターンを使う際は SharedPreferences の書き込みに commit() を使うことを推奨します。

apply() は非同期で書き込むため、killProcess() の直前に呼び出しても書き込みが完了していない可能性があります。設定を保存してからアプリを終了するシーケンスでは、必ず commit() を使って同期的に書き込んでください。

// ❌ apply() は非同期なので safeRestart 前は危険
prefs.edit().putBoolean("dark_mode", isDark).apply()
AppRestarter.safeRestart(this)
 
// ✅ commit() で同期書き込みしてから再起動
prefs.edit().putBoolean("dark_mode", isDark).commit()
AppRestarter.safeRestart(this)

ユーザー体験への影響とトレードオフ

safeRestart アプローチに切り替えると、テーマ適用の際に一度ホーム画面を経由するような体験になります。「アプリが再起動している感」がより明示的です。

ユーザーにとってこれがマイナスに感じるかどうかは、アプリの性質によります。壁紙アプリの場合、テーマ設定は頻繁に行う操作ではなく、「設定を変えたらアプリが再起動する」という動作のほうが「変な白画面が一瞬出る」よりも信頼感があります。

delayMs の値については 300ms で大半のデバイスで問題ありません。極端に遅いデバイスを考慮するなら 500ms まで伸ばしても構いません。短くしすぎると SharedPreferences の書き込みが完了していない場合があるため、200ms 未満は避けることをおすすめします。

Rork Max で生成した Android プロジェクトへの適用

Rork Max が生成する Android コード(React Native ベース)の場合、テーマ管理は Context や Theme Provider を通じて行われます。Rork の生成コードにこのパターンを適用する場合、以下の点を確認するとスムーズです。

  • AppCompatActivity を継承しているか(recreate() が呼べる前提)
  • テーマ設定の保存先が SharedPreferences か、あるいは Jetpack Datastore か
  • Application.onCreate() でテーマ初期化および SDK 初期化が行われているか
  • AdMob や Firebase など広告・分析 SDK の初期化順序

Rork が生成する設定画面から直接テーマ切替を実装すると、デフォルトでは recreate() 相当の処理になることが多いです。本番運用を視野に入れるなら、生成後に上記のような safeRestart パターンへのリファクタリングを検討する価値があります。

ネイティブ Android コードを直接扱う Rork Max の場合、Kotlin の object として実装した AppRestarter をそのまま使えます。React Native 側から Kotlin ネイティブモジュールを呼び出す形でも実装できますが、シンプルに全面ネイティブで書く方が管理しやすいと感じています。

修正が効いているか確認する方法

実装後に本当に白画面が出なくなったかを確認するには、以下の手順が有効です。

まず開発者オプションで「アクティビティを保持しない」をオンにします。この設定を有効にすると、バックグラウンドに回った Activity が即座に破棄されるため、Recreation 時の挙動が誇張されて再現しやすくなります。

次に、スペックの低い実機か Android エミュレーターの CPU を 1 コアに制限した状態でテーマ切替を試します。recreate() を使っていた場合はこの環境で白画面が確認しやすく、safeRestart に置き換えた後は白画面が出ないことを確認できます。

また、Play Console の Android Vitals で「クラッシュと ANR」のグラフを確認することも有効です。テーマ切替に関連したクラッシュが存在していた場合、修正後のバージョンで件数が減少するはずです。私のアプリでは v2.1.0 のリリース後、テーマ切替に起因すると思われるクラッシュレポートが数週間で検出ゼロになりました。

さらに、Google Play の段階公開(5% → 25% → 50% → 100%)を使いながら Crash-free users の値を監視するアプローチも有効です。壁紙アプリのような日次アクティブユーザーが多いアプリでは、5% 公開の時点でもサンプル数が十分に確保でき、重大な不具合があれば早期に検出できます。

12年間の個人開発で見えてきた「隠れたバグ」への向き合い方

両家の祖父が宮大工だったこともあり、「手を動かすことが一つの信心」という感覚が身近にありました。表面的に動いているものでも、根本の設計に問題があれば、必ずどこかで歪みが出てきます。

recreate() のケースも同様です。多くのデバイスで動いているから問題ない、と思っていても、ユーザーレビューに散在する「たまに白い画面が出る」というコメントを集めてみると、パターンが見えてきます。個人開発で累計5,000万DLのアプリを長く維持してきた中で、こういう小さな問題への丁寧な対処が、長期的なレーティングとダウンロード数の安定につながると実感しています。

Rork を使っていても、自分でコードを書いていても、アプリの品質を上げるのは最終的にはこういう小さな問題への丁寧な対処の積み重ねです。

テーマ切替の白画面に悩んでいる方の参考になれば幸いです。まず recreate() の呼び出しタイミングと、その前後の SharedPreferences の書き込み方法を見直してみてください。それだけで解決するケースは少なくありません。

Android 開発全般のデバッグ手法についてはRork Max エラーハンドリング・デバッグ・本番監視の実践パターンも参考になります。また、個人開発アプリでの AdMob や収益化の設計については5,000万DLの個人開発者がRork Maxでアプリを作り直した記録で詳しく触れています。

シェア

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

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

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

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

関連記事

開発ツール2026-05-28
Rork で作った壁紙アプリの「お気に入り」を NSUbiquitousKeyValueStore に寄せた6本並行運用の実装ノート
Rork が出した iOS アプリで「お気に入り壁紙」をデバイス間同期する場合、CloudKit より NSUbiquitousKeyValueStore が個人開発の実情に合います。壁紙アプリ6本を並行運用する立場から、しきい値設計・競合解消・初回起動の戻し順序まで実装ノートとしてまとめます。
開発ツール2026-05-28
Rork で書いた 6 つの壁紙アプリで MMKV に移し替えた 3 週間の所感
壁紙アプリ 6 本で AsyncStorage から react-native-mmkv へ少しずつ移し替えてみた 3 週間分の手順と所感を、つまずいた点も含めて書き残します。2014年からの個人開発の現場感を踏まえて、判断のラインを共有します。
開発ツール2026-05-28
Rork 製 iOS 壁紙アプリ 6 本でインタースティシャル広告疲労を Crashlytics と Remote Config で運用調整した frequency cap 設計ノート
Rork 製の壁紙アプリ 6 本を並行運用する中で、インタースティシャル広告の疲労を Crashlytics の non-fatal シグナルと Remote Config の動的しきい値で運用調整した実装メモ。eCPM・D1 リテンションの実測値と段階リリース手順を Swift コード付きで残します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →