StrictMode はAndroid SDKに組み込まれた開発者ツールで、アプリケーションのメインスレッドでの偶発的なI/O操作やネットワークコールをリアルタイムで検出して報告します。エラーを修正するのではなく、検出器として機能し、設定されたポリシーに違反した場合に例外をスローするかLogCatに書き込みます。Google, 2024によると、適切なStrictMode設定により、アプリのリリース前に最大80%のパフォーマンス問題を検出できます。
重要なポイント
StrictMode はAPI Level 9(Android 2.3 Gingerbread)からAndroid SDKに含まれているAPIです。その役割は、実行時にメイン(UI)スレッドでの重い操作の偶発的な実行を検出し、UIのレンダリングをブロックする可能性を防ぐことです。メインスレッドはユーザー入力の処理、レイアウト計算、レンダリングを担当しており、16msを超えるブロックはフレームドロップの原因となります。
StrictMode は「フェイルファスト」の原則に従います。つまり、問題をできるだけ早期に、理想的には最初に発生した時点で検出します。ユーザーからの遅延の苦情を待つ代わりに、開発者は開発段階で直接シグナル(ログ、ダイアログ、またはクラッシュ)を受け取ります。このツールは追加のライブラリやGradle設定を必要とせず、Application.onCreate に数行のコードを追加するだけで、すべてのデバイスで自動的に機能します。
StrictModeは経験に関係なく、すべてのAndroid開発者向けに設計されています。初心者には良い習慣(UIスレッドでネットワークリクエストをしない)を身につけるのに役立ち、経験者にはCI/CDパイプラインでの品質管理を自動化します。大規模プロジェクト(Google、Uber、Spotify)では、デバッグビルドでpenaltyDeathと共にStrictModeを有効にし、BuildConfig.DEBUGチェックを介してリリースビルドでは無効にします。
StrictMode はスレッドをブロックする可能性のあるシステムコールをインターセプトし、アクティブなポリシーのセットと比較します。コールがポリシーに一致し、メインスレッドで実行された場合、StrictModeは指定されたペナルティを適用します。インターセプトメカニズムはインプロセスフックを介して実装されており、リフレクションを使用せず、最小限のオーバーヘッドで動作します。
StrictMode ポリシーが有効になると、システムコール(FileInputStream、FileOutputStream、Socket、URLConnection)のエントリポイントにハンドラを注入します。アプリケーションがメインスレッドで、例えば URLConnection.openStream を呼び出すと、StrictModeは現在のスレッドをチェックし、メインスレッドの場合はツールが作動します。Android 6.0+ではメカニズムが強化され、メインスレッドでのネットワークコールはStrictModeがなくてもNetworkOnMainThreadExceptionを生成しますが、StrictModeはディスクI/Oも制御できます。
各ポリシーは独自のペナルティタイプまたは組み合わせを持つことができます:penaltyLog — スタックトレース付きでLogCatに書き込み、penaltyDialog — ユーザーにダイアログを表示(デバッグのみ)、penaltyDeath — 例外をスローしてアプリをクラッシュ、penaltyDropBox — 後で分析するためにDropBoxManagerにデータを保存。CI/CDパイプラインでは penaltyDeath が推奨されます。これにより、違反のあるマージが見過ごされることがなくなります。
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
StrictModeはポリシーを2つのレベルに分類します:ThreadPolicy(スレッドレベル — メインスレッドでできないこと)と VmPolicy(仮想マシン — メモリとリソースのリーク)。両方のレベルは独立して設定され、並行して動作します。
スレッドレベルでは、StrictModeは4種類の違反を制御します:ディスク読み取り(detectDiskReads)、ディスク書き込み(detectDiskWrites)、ネットワーク操作(detectNetwork)、カスタムスローコール(detectCustomSlowCalls)。disk_read はメインスレッドでのSharedPreferences、SQLite、ファイルの読み取り時に作動します。network はHTTPリクエスト、WebSocket、Socket接続時に作動します。Android 11+では、バッファリングされていないI/Oを検出するためにdetectUnbufferedIOが追加されました。
VmPolicyはART仮想マシンレベルでリークを制御します:detectActivityLeaks(破棄されなかったアクティビティ)、detectLeakedClosableObjects(閉じられていないCursor、Stream、Socket)、detectLeakedRegistrationObjects(未登録のBroadcastReceiver、ServiceConnection)。VmPolicyが、Activityが作成されたがonDestroy呼び出し後に破棄されなかったことを検出すると、完全なスタックトレースを出力します。これにより、メモリリークのデバッグにかかる時間を大幅に節約できます。
| ポリシー | レベル | 検出内容 |
|---|---|---|
| detectDiskReads | Thread | UIスレッドでのSharedPrefs、SQLite、ファイルの読み取り |
| detectDiskWrites | Thread | UIスレッドでのSharedPrefs、SQLite、ファイルへの書き込み |
| detectNetwork | Thread | UIスレッドでのネットワーク操作 |
| detectActivityLeaks | VM | onDestroyを生き延びたアクティビティ |
| detectLeakedClosableObjects | VM | 閉じられていないCursor、Stream、Socket |
detectCustomSlowCalls を使用すると、独自のメソッドを「疑わしい」としてマークし、指定したしきい値を超えた場合に警告を受け取ることができます。たとえば、loadUserProfile()メソッドが通常5msかかるが、時々200msかかる場合は、StrictMode.noteSlowCall(“loadUserProfile”)でラップします。持続時間がしきい値(デフォルト2000ms)を超えると、StrictModeがペナルティを生成します。しきい値は setSlowCallDurationThreshold で設定します。
基本的な StrictMode 設定は10行のコードで完了し、カスタムApplicationクラスのonCreateメソッドで行います。主なルール:StrictModeはデバッグビルドでのみ有効にします。リリースビルドではアプリが遅くなり、誤検出が発生する可能性があります。
Applicationを拡張するクラスを作成し、android:name属性を介してAndroidManifest.xmlに登録し、StrictMode設定を追加します。ThreadPolicy.Builderにはすべての検出器とすべてのペナルティタイプが含まれます(ダイアログを除く — デバッガが接続されている場合のみ機能)。VmPolicy.BuilderはActivityリークとClosableオブジェクトの検出器を追加します。大規模プロジェクト(100画面以上)では、Activityリーク検出器にpenaltyDeathを設定した VmPolicy を推奨します。厳格ですが効果的です。
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
CI/CDでの自動制御には penaltyDeath を使用します。テストがポリシーに違反すると、アプリは例外でクラッシュします。Android Test Orchestratorと組み合わせて、各テストをクリーンなプロセスで実行します。UIテスト(Espresso、Compose Test)の場合は、StrictMode違反をインターセプトしてアサーション失敗に変換するカスタムTestRuleを作成します。例:@BeforeでStrictModeを有効にし、@Afterで違反がなかったことを確認します。
デフォルトでは、customSlowCallのしきい値は2000ms、disk_readとdisk_writeにはしきい値がありません(任意の操作で作動)。setSlowCallDurationThreshold と setSlowIoDurationThreshold を使用して、ミリ秒単位で独自の値を設定できます。アプリがメインスレッドでSharedPreferencesを正当に読み取る場合(小さな設定)、しきい値を10–20msに増やします。これにより、高速な読み取りはフィルタリングされ、低速な読み取りはそのまま残ります。
StrictMode は強力ですがデリケートなツールです。誤った設定は何百万もの誤検出を引き起こし、開発者がそれらに注意を払わなくなります。以下は、大規模なAndroidチームの経験から集められた実証済みのプラクティスです。
これは厳格なルールです:StrictModeはリリースビルドでは決してアクティブにしてはいけません。BuildConfig.DEBUGフラグまたはカスタムbuildConfigFieldを使用します。リリースビルドでは、多くのサードパーティライブラリがメインスレッドで正当に操作(SDK初期化、キャッシュ書き込み)を実行し、StrictModeが誤検出を生成します。さらに、リリースビルドで penaltyDialog が有効だと、エンドユーザーにダイアログが表示され、許容できません。
小規模プロジェクト(1–10画面)では penaltyLog を設定します。手動分析にはログで十分です。中規模プロジェクト(10–50画面)では、ネットワークとcustomSlowCallsに penaltyDeath を追加します。大規模プロジェクト(50画面以上)では、CI/CDでpenaltyDeathを含む完全なポリシーセットを有効にし、ローカル開発ではpenaltyLogを使用します。この段階的アプローチにより、開発者を誤ったクラッシュで圧倒することなく、パイプラインでの品質を厳格に制御できます。
一部のライブラリ(Firebase、Crashlytics、Adjust)は、StrictModeが誤って検出する可能性のあるバックグラウンド操作を正当に実行します。解決策:penaltyListenerを介してライブラリを ホワイトリスト に追加するか、ライブラリを明示的にバックグラウンドスレッドに切り替えるバージョンにアップデートするか、StrictMode.vmPolicyを使用します。Android 11+では、スタックトレースによる違反のプログラムによるフィルタリングのためのStrictMode.OnVmViolationListenerが導入されました。
// penaltyListenerによる誤検出のフィルタリング
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyListener { violation ->
val stack = violation.stackTraceToString()
if ("com.google.firebase" !in stack) {
logViolation(violation)
}
}
.build()
)
StrictModeはAndroidエコシステムにおける唯一の品質管理ツールではありません。その位置づけを理解するために、主要な基準(チェック時間、分析深度、自動化)でAndroid Lint、Android Profiler、Perfettoと比較してみましょう。
| 基準 | StrictMode | Android Lint | Profiler / Perfetto |
|---|---|---|---|
| チェック時間 | 実行時(アプリ動作中) | コンパイル時(起動前) | 実行時(事後分析) |
| チェック内容 | ディスク、ネットワーク、リーク | XML、コード、リソース | CPU、メモリ、ネットワーク、電力 |
| 自動化 | penaltyDeathによるCI/CD | Gradleタスク + lint-baseline | 手動分析が必要 |
| 深度 | UIスレッドとリークのみ | 静的コード分析 | 完全なパフォーマンス像 |
| 誤検出 | 中程度(ライブラリ依存) | 低い(設定済みルール) | なし(実際の測定値) |
最善の戦略は3つのアプローチをすべて組み合わせることです:Android Lint はコンパイル時に明らかなエラー(忘れられたIdleHandlerなど)をキャッチし、StrictMode は実行時に問題を検出し、Android Profiler / Perfetto は最初の2つのツールが答えを出せない場合の深い分析に使用します。実際のプロジェクト(Google Maps、Instagram)では、StrictModeは開発の2週目、基本的なアーキテクチャを設定した直後に導入されます。
StrictModeがパフォーマンス問題の検出と修正に役立つ2つの実際のシナリオを見てみましょう:メインスレッドでのSharedPreferencesの読み取りと、未登録のコールバックによるActivityリークです。
アプリ起動時に、detectDiskReadsポリシーのStrictModeがメインスレッドでのSharedPreferencesの読み取りを検出します。解決策:CoroutineScope を介して設定を非同期で読み込むか、起動時にメモリにキャッシュします。SharedPreferencesはディスクからXMLファイルを同期的に読み取ります。小さなファイル(1–2 KB)でも、操作には1–5msかかり、安価なデバイスでは最大20msかかり、フレームドロップの原因となる可能性があります。
// ❌ 問題のあるコード — UIスレッドでのSharedPrefsの読み取り
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// StrictMode detectDiskReads → 違反!
prefs = getSharedPreferences("config", MODE_PRIVATE)
}
}
// ✅ 修正済みコード — Coroutineによる読み取り
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfigAsync()
}
}
VmPolicy.detectActivityLeaksを使用したStrictModeは、スタックから抜けた(finishが呼び出された)Activityを検出しますが、静的参照または未登録のコールバックによりActivityオブジェクトがメモリに残り続けます。典型的なシナリオ:onPause でunregisterを呼び出さずに onResume でEventBusやLocationListenerを登録する。VmPolicyは参照が作成された行を示すスタックトレースを出力します。
// ❌ リーク — コールバックがキャンセルされていない
private var locationCallback: LocationCallback? = null
override fun onResume() {
super.onResume()
locationCallback = LocationCallback(this::onLocationUpdated)
locationManager.register(locationCallback) // StrictMode → リーク!
}
override fun onPause() {
super.onPause()
// 忘れました: locationManager.unregister(locationCallback)
}
よくある質問
StrictMode は確かにわずかなオーバーヘッドを追加します — すべてのシステムコールがポリシーに対してチェックされます。パフォーマンスへの影響はデバッグビルドで1–3%で、リリースビルド(StrictModeが無効)では影響はありません。古いデバイス(Android 6–8)でdetectAllを有効にすると、オーバーヘッドが5%に達する可能性があるため、必要なポリシーのみを設定することをお勧めします。
はい、StrictMode はJetpack Composeと完全に互換性があります。ディスクとネットワークのポリシーはUIフレームワークから独立して、フレームワークレベルで機能します。さらに、ComposeではUIブロックの重大性が高くなります。Composeは高リフレッシュレートデバイスで120FPSでフレームを再描画するため、ファイル読み取りの5msの超過がより顕著になります。
Android 8.1(API 27)以降、SharedPreferencesは メモリキャッシュ を使用する場合があります。ファイルが既に読み取られている場合、再読み取りではStrictModeは作動しません。getSharedPreferencesを初めて呼び出していること(コールドリード)と、detectDiskReadsポリシーがアクティブであることを確認してください。また、親のないフラグメントでStrictModeがオーバーライドされていないか確認してください。
JUnitテストでは、@Beforeで StrictMode.allowThreadDiskReads() と StrictMode.allowThreadDiskWrites() を使用し、@AfterでStrictMode.enableDefaults()を介して設定を復元します。インストルメンテーションテストでは、元のポリシーを一時的に保存するカスタムTestRunnerを使用します。Espressoテストでは、StrictModeに敏感なコードを IdlingResource でラップすると便利です。
StrictModeはAndroid SDKを介してAndroidプラットフォームでのみ機能します。Kotlin Multiplatform(KMP)では、commonMainコードはStrictModeを使用できませんが、androidMainでは通常どおり追加できます。iOS部分では、メインスレッド用の DispatchQueue.main.async アサーションという類似の機能を使用します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。