ラグとは、モバイルアプリにおいて、メインスレッドの過負荷、メモリリーク、または最適でないI/O操作によって発生する、ユーザーの操作とインターフェースの応答の間の顕著な遅延です。論理エラーに関連するグリッチとは異なり、ラグはパフォーマンスの問題です。アプリは正しく動作しますが、遅くなります。AppDynamics Mobile App Performance Report 2024によると、62%のユーザーがアプリが3秒以上遅延するとアンインストールします。ラグの診断には、Android Studio ProfilerとXcode Instrumentsを使用したCPU、メモリ、ネットワークのプロファイリングが必要です。
重要ポイント
ラグとは、モバイルアプリにおいて、ユーザーの操作(タッチ、スワイプ、テキスト入力)とインターフェースの応答の間に生じる、主観的に認識可能な遅延です。技術的には、ラグは入力イベントからフルフレームレンダリングまでの時間として測定されます。快適な閾値は最大100 ms、認識可能なのは200 msから、重大なのは500 ms超です。
ユーザー用語では、「ラグ」と「遅延」はしばしば同義語として使用されますが、技術的にはラグは固定された遅延(例:毎タップ300 ms)であり、「遅延」は断続的な減速です。アプリがスムーズに動作した後、1秒間フリーズします。グリッチはラグとは異なり、速度ではなく表示の正確性に関連します。
Google PlayとApp Storeはアプリのランキング時にパフォーマンス指標を考慮します。ANR率、ジャンク頻度、起動時間は検索の可視性とインストール変換に影響します。持続的なラグがあるアプリは、最初の起動後に最大40%のユーザーを失います。
ラグは、メインUIスレッドが60 FPS(フレームあたり16.6 ms)または120 FPS(8.3 ms)でフレームを処理できない場合に発生します。遅延の主な原因を見てみましょう。
UIスレッドでの同期操作 — SharedPreferencesからの読み取り、suspendなしのRoomを使ったデータベース操作、Bitmapへの画像デコード — はフレームレンダリングをブロックします。Androidではジャンクを引き起こし、iOSではCore Animationのレンダリング遅延を引き起こします。
AndroidのガベージコレクターまたはiOSのARCがメモリ解放を行うと、すべてのスレッドが一時停止します。頻繁なGCポーズは、多数の一時オブジェクトが作成されるときに発生します(例:アダプター呼び出しごとに新しいViewHolderインスタンスを作成する)。これはギクシャクしたスクロールとして現れます。
ネストされたConstraintLayout、複数のLinearLayout、重なり合うView — ネストの各レベルはmeasureとlayout passの時間を増加させます。Xcodeは、深いレイヤー階層(10レベル以上)がFPSを20-30%低下させることを示しています。
ラグの原因を特定するには、IDEに組み込まれたプロファイラーとシステム監視ツールが使用されます。各ツールは独自のタスクを解決します。
CPU Profilerは、どのメソッドがCPU時間を消費し、どのスレッドで実行されるかを示します。重い計算メソッドがメインスレッドで実行されている場合、それが根本原因です。サンプルJava Methodを有効にしてトレースを記録すると、任意の時点でのコールスタックを確認し、ホットスポットを見つけることができます。
iOSの同等ツールであるTime Profilerは、毎ミリ秒スタックサンプルを収集し、各メソッドが消費するCPU時間の割合を示します。Main Thread Onlyフラグと組み合わせることで、メインスレッド操作のみをフィルタリングし、ラグの原因を直接特定します。
遅いネットワークリクエストは、UIスレッドがブロックされていなくてもラグの印象を与えます。Android StudioのNetwork ProfilerとXcodeのNetwork Link Conditionerを使用すると、低速接続をシミュレートし、実際の条件下でのアプリの動作を特定できます。進行状況のないチャンク応答と大きなJSONペイロードは、見かけ上のラグの典型的な原因です。
OkHttpを使用したネットワークリクエストのプロファイリング例(時間計測付き):
class TimingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val start = System.nanoTime()
val response = chain.proceed(chain.request())
val duration = (System.nanoTime() - start) / 1_000_000
Log.d("Timing", "Request took $duration ms")
return response
}
}
ラグの解決には体系的な作業が必要です:単一のメソッドの最適化からアーキテクチャの変更まで。最も効果的なテクニックを見てみましょう。
ネットワークリクエストにはDispatchers.IO、計算にはDispatchers.Defaultを使用するKotlin Coroutinesにより、メインスレッドがUIのために解放されたままになります。iOSでは、バックグラウンドタスク用のqueue .global(qos: .userInitiated)とUI更新用の.mainを使用するGrand Central Dispatchが標準的なアプローチです。キュー間の同期操作は避けてください。
AndroidのRecyclerViewとiOSのUICollectionViewは適切な設定が必要です:onBindViewHolderでの最小限のオブジェクト作成、変更計算のためのDiffUtil、データの事前読み込みのためのprefetching。iOSでは手動管理なしでアニメーション更新を行うためにdiffable data sourceを使用します。
スクロールのたびに同じ画像を読み込むことは、確実なラグを引き起こします。Coil(Android)とKingfisher(iOS)は画像をメモリとディスクにキャッシュし、繰り返しのリクエストで即座に表示します。データには、FlowまたはCombineに基づくキャッシュ層を備えたRoomを使用します。
AndroidでCoilを使用した画像キャッシュ設定の例:
val imageLoader = ImageLoader(context) {
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
crossfade(true)
size(512, 512)
}
// Loading with auto-caching enabled
imageView.load("https://example.com/image.jpg") {
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
ラグを予防することは、本番環境で修正するよりも費用対効果が高くなります。予防措置はツールとアーキテクチャのレベルで開発プロセスに組み込まれます。
StrictModeは、開発中にメインスレッドでの偶発的なI/O操作やネットワーク呼び出しを検出するAndroidの組み込みツールです。重大な違反に対してpenaltyDeathポリシーを設定してApplication.onCreateで有効にします。これが、コミット前に開発者が問題を確認する唯一の方法です。
iOSの同等品であるXcodeのMain Thread Checker(Runtime Sanitizationの一部)は、すべてのUIKitおよびAppKit呼び出しがメインスレッドで実行されることを自動的にチェックします。Debugビルドスキームで有効にし、CIでゼロ警告を目指します。
CIパイプラインにMacrobenchmark(Android)とXCTMetrics(iOS)の実行を追加して、起動時間、スクロールFPS、メモリ使用量を測定します。しきい値を設定します:新しいコミットが起動時間を5%以上増加させる場合、ビルドは失敗します。
よくある質問
ラグは遅延の主観的な感覚であり、レンダリングではなく入力処理時間によって遅延が発生する場合、高いFPSでも発生する可能性があります。低FPS(30 fps未満)はラグの原因の1つですが、唯一の原因ではありません。
フレーム間の時間を測定するには、AndroidでFrame Timing API(Choreographer)、iOSでCADisplayLinkを使用します。Google Play Vitalsは実際の条件下でのジャンク率を表示します。正確な測定には、スクロールシナリオを使用したMacrobenchmarkを使用します。
古いデバイスはCPUコア数が少なく、RAM容量が小さく、メモリが遅くなります。フラッグシップで5 msかかる操作が、低価格デバイスでは50 msかかる場合があります。ローエンドデバイスでパフォーマンスをテストし、AOTコンパイル用にBaseline Profilesを設定します。
はい、これは最も効果的な方法の1つです。高解像度の画像はデコードに多くのメモリとCPU時間を消費します。Viewサイズへのダウンスケール、WebP(Android)やHEIC(iOS)形式、CoilやKingfisherによるキャッシュを使用します。
SwiftUIは差分によって更新を自動的に最適化し、データ変更時のラグのリスクを低減します。ただし、複雑な階層と頻繁なbodyの再構築はFPS低下を引き起こす可能性があります。UIKitはパフォーマンスをより細かく制御できますが、手動での最適化が必要です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。