SharedPreferencesを信じすぎて全初期化事件〜大事な設定が一夜にして消えた日〜

「設定の保存?SharedPreferencesで十分でしょ」

──そう言って、何の疑いもなくapply()commit()を使っていたあの頃の自分に、「ちょっと待て」と声をかけたい。

ある日、あるアプリの利用ユーザー全員から「設定がリセットされた」という問い合わせが殺到。

調査してもログは残らず、変更履歴にもバグは見当たらない。

それでも、確かにSharedPreferencesは何かの拍子に“初期化”されていた

今回は、SharedPreferencesにまつわる「全初期化事件」と、その背後に潜んでいた落とし穴を語ります。

事件の始まり:ユーザーの声で気づく“異変”

ある朝、カスタマーサポートからSlackに悲鳴が上がりました。

「ユーザー設定が全部リセットされているって苦情が…!」
「昨日のアップデート以降、通知もオフになってるみたい…」

慌ててFirebase CrashlyticsやLogcatをチェックしましたが、エラーは皆無。

アプリは正常に動いており、クラッシュもしていませんでした。

ただ、ユーザーの設定値──とくに通知のON/OFF、テーマ、アカウント連携フラグなどが、すべてデフォルトに戻っていたのです。

原因を追え:ログが残らない「消え方」

まず、SharedPreferencesに保存していた設定項目をすべて洗い出し、getSharedPreferences()で取得するパスをログで出力。

val prefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
Log.d("PrefsPath", prefs.toString())

結果、パスやファイルは正しく存在していたのに、値がなかった。

prefs.getBoolean("isNotificationEnabled", true)true(デフォルト)
prefs.getString("theme", "light")“light”(デフォルト)

これは、SharedPreferencesのファイル自体がリセットされたことを意味していました。

疑わしい犯人その1:clear()の誤用

当初は自分のコードの中にprefs.edit().clear()が混入していたのではないかと疑いました。

例えば、ログアウト処理などでやりがちなこのコード:

prefs.edit().clear().apply()

一見して「ログイン情報だけ消す」ように見えますが、このコードは「すべてのキーを消す」動作になります。

さらに最悪なのは、複数のSharedPreferencesをまとめて扱っている場合。

ログアウト時に別の設定までclear()されてしまい、ユーザーの通知設定やUIテーマまでも初期化されていたのです。

疑わしい犯人その2:Context.MODE_PRIVATEの誤解

もう一つの原因は、同じファイル名を異なるスコープ(Context)で取得していたことです。

たとえば、アプリ内のあるActivityではこのように取得:

val prefs = getSharedPreferences("user_prefs", Context.MODE_PRIVATE)

一方で、カスタムViewやServiceでは、applicationContextを使って以下のようにアクセス:

val prefs = context.applicationContext.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)

このスコープの違いが、端末やAPIレベルによって異なるファイルとして扱われたり、アクセスタイミングで競合が発生することがあるのです。

また、旧バージョンで保存された形式が新バージョンで読み取れない(例:暗号化ライブラリの変更)といったケースも原因になりえます。

本当のトリガーは「端末のストレージ圧迫」

最終的な調査で明らかになった原因は、一部端末でストレージが圧迫された際にSharedPreferencesのファイルがOSによって削除されたというものでした。

特に低スペック・低容量端末では、OSがアプリのキャッシュや一時ファイル、SharedPreferencesを「不要なファイル」とみなして削除することがあるという情報がありました。

/data/data/[package]/shared_prefs/user_prefs.xml

このファイルが、アプリは正常でも、ファイル単位で消されていた

それが、あの“静かなるリセット”の真相でした。

対策:SharedPreferencesを使うときに心がけるべきこと

今回の事件を経て、私は以下のようにSharedPreferencesの扱いを見直しました。

保存する値を「慎重に」選ぶ

  • 認証情報やアプリの根幹に関わる設定は、SharedPreferencesではなくEncryptedSharedPreferencesRoom/Proto DataStoreに移行。
  • SharedPreferencesはあくまで「軽い一時設定」用と割り切る。

clear()の範囲を限定する

  • ログアウト時に全設定を消さないよう、キーを指定して削除。
prefs.edit().remove("login_token").apply() // これだけ消す

apply()ではなくcommit()を重要箇所では使う

  • apply()は非同期なため、すぐにプロセスが落ちると書き込みが失敗する可能性がある。
  • 保存が確実に必要な場面(初回起動のフラグなど)ではcommit()で同期保存。

保存成功後にログを出す

val result = prefs.edit().putBoolean("isLoggedIn", true).commit()
if (!result) {
    Log.e("Prefs", "保存失敗!")
}

最後に:SharedPreferencesは万能じゃない

SharedPreferencesは便利です。

でも、OSのバージョン差、ファイル競合、非同期処理、ストレージ状態など、多くの不確定要素に依存しています。

“動いているように見えるけど、実は危うい”

そんな機能ほど、「なぜ動くか」ではなく「なぜ危険か」を理解することが重要だと感じました。

あのときの「一晩で設定がすべて消えた」という体験は、SharedPreferencesに対する信頼を完全に揺るがすには十分すぎるものでした。

タイトルとURLをコピーしました