アプリが遅いことは、ユーザーがプログラムを削除する主な理由です。起動時やリストスクロール時の数ミリ秒の遅延により、リテンション率は数十パーセント低下します。パフォーマンスとは、速度だけでなく、安定性(ANR、クラッシュ、メモリリークがないこと)も意味します。この記事では、メモリ管理(GC、ARC)からプロファイリングツールまで、パフォーマンスのあらゆる側面を解説します。詳細は公式Android Performanceガイドをご覧ください。
重要ポイント
アプリのパフォーマンスは、ジャンク(jank) — ユーザーの操作とUIの応答の間の顕著な遅延に直接関係しています。主な原因:メインスレッドのブロッキング(UIスレッドでの重い処理)、頻繁なレイアウトの再描画(オーバードロー)、メモリリーク(頻繁なGC)、非最適なアルゴリズム(大規模データでのO(n²))。フレームレート(FPS) — 1秒あたりのフレーム数。快適な体験には、安定した60 FPS(Android)または120 FPS(iPhone Pro、iPad Pro)が必要です。VSyncは、レンダリングを画面のリフレッシュレートと同期します。
ジャンクは、1フレームのレンダリングが16.6ミリ秒(60 FPSの場合)または8.3ミリ秒(120 FPSの場合)を超えたときに発生します。GPUプロファイリング(AndroidのProfile GPU Rendering、iOSのCore Animation)は、レンダリングのどの段階が最も時間を消費しているかを示します。主な段階:Layout(要素の配置)、Draw(描画)、Display(フレームバッファへの転送)。最も一般的な問題は、XMLでのレイアウトインフレーション、特に複雑なネストされたConstraintLayoutを使用する場合です。
Time-to-Interactive(TTI) — アプリが完全に操作可能になるまでの時間。TTIには、コールドスタート、データ読み込み、ライブラリの初期化が含まれます。GoogleはTTIを5秒未満、Appleはメイン画面で2秒未満を推奨しています。レイジーローディング — コンテンツとライブラリの遅延読み込み手法で、TTIの改善に重要です。IT Sectrでは、すべてのプロジェクトでデフォルトで遅延初期化を採用しています。
ANRとクラッシュは、モバイルアプリのパフォーマンスにとって最大の敵です。ANR(Application Not Responding) — メインスレッドが5秒以上ブロックされた場合にAndroidに表示されるダイアログボックス。原因:UIスレッドでの同期ネットワークリクエスト、コルーチンなしのデータベース操作、ダウンサンプリングなしの大きなビットマップデコード、メインスレッドでのデッドロック。ANRコールスタックは/data/anr/traces.txtに保存され、正確なブロッキング位置を特定できます。
クラッシュ — アプリの予期しない終了。Androidの場合 — Exception(Java/Kotlin)またはSignal(ネイティブコード)。iOSの場合 — NSExceptionまたはシグナル(EXC_BAD_ACCESS — 解放済みメモリへのアクセス)。クラッシュレポートツール:Firebase Crashlytics、Sentry、BugSnag。これらはスタックトレース、デバイスデータ、再現手順を収集します。Stack Overflow — 無限再帰によるコールスタックオーバーフロー。OutOfMemoryError — ヒープが満杯になった場合。
StrictMode — スレッドセーフティ違反を検出するAndroidツール。ルールを設定できます:ThreadPolicy(メインスレッドでのディスク/ネットワークを禁止)、VmPolicy(Activity、SQLite、CloseGuardのリークを検出)。StrictModeはデバッグビルドでのみ有効にすることを推奨します — リリースでは動作させるべきではありません。iOSでの同等品はMain Thread Checker(Xcode)で、メインスレッド以外でのUIKit呼び出しを自動的に検出します。
メモリリークとは、アプリがもう使用していないにもかかわらず、オブジェクトがメモリに残り続ける状態です。これはアプリのパフォーマンスを直接低下させます。Androidでは、GC(ガベージコレクション)は、オブジェクトに強参照があると収集できません。典型的な原因:Activityへの静的参照、クリアされていないコールバック/オブザーバー、外部クラスへの暗黙的な参照を持つ内部クラス、クリアされていないメッセージを持つHandler。LeakCanaryは、リークを自動検出するライブラリです。
ARC(Automatic Reference Counting) — iOSのメモリ管理モデル。各オブジェクトには参照カウント(retain count)があります。カウントがゼロになるとメモリが解放されます。Retain Cycleは、2つのオブジェクトが互いに強参照を保持している場合(A → B かつ B → A)に発生します。ARCはカウンタを決してゼロにしません。解決策:弱参照(weak)または非所有参照(unowned)。Weakはオブジェクト解放時に自動的にnilになります。Unownedはnilになりませんが、オブジェクトが生きていることを保証します。
GC(ガベージコレクション)はAndroid(Java/Kotlin)で動作します。GCは、到達不能なオブジェクトを検索して解放するために、定期的に実行を一時停止します(Stop-the-Worldポーズ)。GCトリガー:ヒープが一定の割合まで満たされたとき。ARCはiOS(Swift/Objective-C)で動作し、ポーズはありません — カウンタは各代入でアトミックに更新されます。ARCはより予測可能ですが、高い代入頻度では過剰なretain/release操作が蓄積される可能性があります。
弱参照(Weak Reference)と強参照(Strong Reference) — 参照の種類によって、GC/ARCがオブジェクトを解放できるかどうかが決まります。Strong Reference — この参照が存在する限り、オブジェクトは収集されません。Weak Reference — GC/ARCはオブジェクトを収集できます。弱参照はnilになります(Swift/Java WeakReference)。Unowned Reference(Swift) — 解放時にnilになりません。オブジェクトの解放後にアクセスするとクラッシュします。Androidでは、弱参照にjava.lang.ref.WeakReferenceが使用されます。
LeakCanaryを使用したAndroidでのリーク検出の例:
// Утечка: анонимный класс держит ссылку на Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// Используем `this@MainActivity`, сохраняя ссылку на Activity
Log.d("TAG", "Handler received message")
}
}
handler.sendEmptyMessageDelayed(0, 60000)
}
}
// Исправление: статический Handler + WeakReference
class SafeHandler(activity: MainActivity) : Handler() {
private val weakActivity =
WeakReference(activity)
override fun handleMessage(msg: Message) {
weakActivity.get() ?: return
Log.d("TAG", "Handler received message")
}
}
プロファイリングは、アプリのパフォーマンス(CPU、メモリ、ネットワーク、電力消費)を測定するプロセスです。プロファイリングなしの闇雲な最適化は無意味です — コードのどの部分が実際に遅いのかを知ることはできません。
| ツール | プラットフォーム | 測定対象 | 使用タイミング |
|---|---|---|---|
| Instruments (Time Profiler) | iOS | CPU、関数呼び出し、実行時間 | アルゴリズム最適化、ボトルネック検索 |
| Instruments (Allocations) | iOS | メモリ、オブジェクト数、retain counts | リークと過剰なメモリ消費の検出 |
| Instruments (Leaks) | iOS | Retain cycles、メモリリーク | リリース前の定期チェック |
| Android Profiler (CPU) | Android | CPU使用率、スレッドアクティビティ、トレース | メインスレッドのブロッキング検出 |
| Android Profiler (Memory) | Android | ヒープダンプ、アロケーション追跡 | リーク検出、オブジェクト分析 |
| Android Profiler (Network) | Android | トラフィック、速度、リクエストタイミング | ネットワーク呼び出しの最適化 |
| LeakCanary | Android | 自動メモリリーク検出 | すべての開発段階 |
| StrictMode | Android | メインスレッドのディスク/ネットワーク、リーク | デバッグビルド |
| Traceview / Systrace | Android | メソッドトレーシング、システムイベント | 詳細なレイテンシ分析 |
Instruments(Xcode) — iOS用の最も強力なツール。Time Profilerは、どの関数が最もCPUを消費しているかを示します。Allocationsはオブジェクトの作成と解放を追跡します。Leaksはretain cyclesを自動的に検出します。プロファイリング手順:(1)Instrumentsを起動;(2)テンプレートを選択(CPU用のTime Profiler);(3)問題のあるシナリオを実行;(4)コールスタックを分析 — 最も幅の広い列が最も「ホットな」関数です。
Android ProfilerはAndroid Studioに組み込まれています(View → Tool Windows → Profiler)。CPU Profilerは各スレッドの負荷を表示します。Memory Profiler — ヒープダンプとアロケーション追跡。Network Profiler — タイミング付きのすべてのHTTPリクエスト。Energy Profiler — 電力消費:WakeLock、Location、Network。詳細なトレーシングには、Systrace(Android 10+)またはPerfetto — マイクロ秒精度のシステムトレーシングを使用します。
アプリの起動は、主要なパフォーマンス指標の1つです。3つのタイプに分類されます:コールドスタート — アプリがゼロから起動:プロセス作成、Application.onCreate(Android)/ AppDelegate.applicationDidFinishLaunching(iOS)、クラス読み込み、ライブラリ初期化。ウォームスタート — プロセスは存在するが、Activity/ViewControllerが破棄されている(例:画面回転時やメモリからの復帰時)。ホットスタート — Activity/ViewControllerがメモリに存在し、アプリが単に表示される(別のアプリからの切り替え)。
コールドスタートが最も重要な指標です。Androidでは以下を含む:(1)launch Activity — XML読み込み、View初期化;(2)最初のフレーム — 最初のレンダリングまでの時間。Google推奨:launch Activity < 200ミリ秒、最初のフレーム < 500ミリ秒、TTI < 5秒。コールドスタート最適化:Application.onCreateの削減(遅延初期化のためのコルーチン)、SplashScreen API(Android 12+)の使用、ライブラリ初期化の延期(WorkManager、DI)、不要なContentProvidersの削除。
iOSでは、コールドスタートに以下が含まれます:Mach-Oバイナリ読み込み、dyld(動的リンカー)、Objective-Cランタイム初期化、アプリケーションデリゲート、最初のコントローラー。Chrome Custom Tabs(Android)およびUniversal Links(iOS) — 完全なコールドスタートなしでアプリ内の外部コンテンツを迅速に開く技術。実機のミッドレンジデバイスでコールドスタートをテストすることを推奨します。
アプリのサイズは、インストールとアップデートのパフォーマンス要因です。コンバージョンに影響します:10 MBごとにコンバージョンが1%低下します。Google Play推奨APKサイズ150 MB未満;App Store — 200 MB未満(セルラーネットワーク — 100 MB)。主な最適化方法:画像圧縮(PNGの代わりにWebPで25-35%削減)、ベクター化(AndroidのVectorDrawable、iOSのSF Symbols)、未使用コードの削除(R8/ProGuard)、未使用リソースの削除(lint → unused resources)。
App Bundle(Android) — Google Playが各デバイスに最適化されたAPKを生成する公開形式。App Bundleはダウンロードサイズを20-40%削減します。Dynamic Delivery — オンデマンドでダウンロードされるモジュール(on-demand feature modules)。iOSでの同等品はOn-Demand Resources(ODR):初回起動後にダウンロードされるリソース(ゲームレベル、動画)。
レイジーローディング — モジュールとライブラリを起動時に読み込まず、必要に応じて読み込む手法。Split APK(Android)とApp Slicing(iOS) — アプリをアーキテクチャスロットに分割:arm64-v8a、x86_64。アプリサイズ最適化 — 継続的なプロセス:APK構成を分析し(Android StudioのAnalyze APK)、重複アイコンを削除し、複数のPNG密度の代わりにSVGを使用。IT Sectrでは、すべてのMRのCI/CDにビルドサイズチェックを含めています。
よくある質問
ANR(Application Not Responding) — メインスレッドが5秒以上ブロックされた場合にAndroidに表示されるダイアログです。ANRを回避するには、すべての重い処理(ネットワーク、データベース、ファイル処理)をバックグラウンドスレッドに移動してください。iOSでの同等品はfrozen UIで、アプリがタッチに応答しなくなります。
メモリリーク — オブジェクトへの参照が残っているため解放できない状態。Retain Cycle — iOS/Objective-Cで2つのオブジェクトが相互に参照している状態(A → B → A)で、ARCがどちらも解放できません。解決策:weak/unowned参照と適時のコールバッククリーンアップ。
iOS:Instruments(Time Profiler、Allocations、Leaks)。Android:Android Profiler(CPU、Memory、Network)、LeakCanary(メモリリーク)、StrictMode(スレッド違反)。開発と統合の段階でプロファイリングを組み合わせることをお勧めします。
コールドスタート — アプリがゼロから起動:プロセス作成、クラス読み込み、Application.onCreate実行。ウォームスタート — プロセスは存在するがActivity/ViewControllerが再作成される。ホットスタート — Activity/ViewControllerが既にメモリにあり、単に表示される。コールドスタートが最も遅い(1-5秒)ため、ユーザーエクスペリエンスにとって重要です。
主な方法:未使用のリソースとコードを削除(R8/ProGuardを使用)、画像のベクター化(VectorDrawable、SF Symbols)、PNG/WebPの圧縮(Android)、APKの代わりにApp Bundleを使用、不要なライブラリの削除、モジュールのレイジーローディング。サイズ最適化によりAPKを40-60%削減できます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。