Jankとは、個々のフレームがスキップされることによってインターフェースアニメーションに目に見える途切れや“ひっかかり”が生じることを指す用語です。モバイルアプリでは、フレームのレンダリング時間がディスプレイのリフレッシュレートによって割り当てられた予算を超えるとJankが発生します。Android Developers, 2025によると、Jankは“遅さ”という主観的な感覚の主な原因です — アプリは機能的に完璧でも、FPSが不安定なためにユーザーは遅いと感じます。
重要なポイント
Jankはコンピュータグラフィックスの用語で、アニメーションがスムーズに動く代わりにぎくしゃくと動く視覚的欠陥を指します。モバイル開発では、Jankは単位時間あたりのスキップされたフレーム数(skipped frames)として測定されます。システムがVSyncのタイミングまでにフレームを準備できない場合、ディスプレイは前のフレームを繰り返します — 60Hzで16.6 msの停止が発生します。1回のフレームスキップは気づかれないかもしれませんが、3~5回の連続したフレームスキップは50~80 ms続く“途切れ”感を生み出し、ユーザーは明確に認識します。
Jankは、一定の速度で動作する必要があるアニメーション(フィードのスクロール、メニューを開くアニメーション、パララックス効果、画面間の遷移)にとって特に重要です。GoogleのUX調査(2024年)によると、スクロールセッションの3%以上でJank率が発生するアプリは、0.5%未満のアプリよりも22%多く1つ星レビューを受けています。Android VitalsツールはJankを自動的に追跡し、重要度(中程度、重大、致命的)に分類します。
Jankの原因はいくつかのカテゴリに分類されます。1つ目はLayout Jank:Viewのサイズ変更、LayoutTransitionアニメーション、動的コンテンツ読み込みによる頻繁なrequestLayout()呼び出しによって発生します。requestLayoutが呼び出されるたびに、Viewサブツリー全体でMeasure + Layoutがトリガーされ、5~30 msかかる可能性があります。2つ目はDraw Jank:オーバードローと重いdrawableの使用に関連します。3つ目はThread Jank:同期的な操作(ファイル読み込み、メインスレッドでのデータベース操作、Bitmapデコード)によるメインスレッドのブロッキングです。
4つ目のカテゴリはGC Jank:ART/DalvikまたはSwift ARCでのガベージコレクション(Garbage Collection)です。ヒープに多くのオブジェクトが蓄積されると、GCは5~15 msのStop-The-Worldポーズを開始します。Androidでは、GCポーズはループ内での頻繁なアロケーション(onDraw()でのオブジェクト生成、アダプターでのアロケーション、未使用のラムダ式)時に最も頻繁に発生します。5つ目はIPC Jank:メインスレッドでのプロセス間通信(ContentProvider、Binder)です。6つ目はRendering Jank:最適でないシェーダーや大きなテクスチャによるGPUレンダリングの遅延です。
| Jankのタイプ | 原因 | 標準的な持続時間 | 検出ツール |
|---|---|---|---|
| Layout | requestLayout, relayout | 5~30 ms | Perfetto, Systrace |
| Draw | Overdraw, 重いdrawable | 3~20 ms | GPU Profiling |
| Thread | メインスレッドのブロッキング | 10~200 ms | Android Studio Profiler |
| GC | Garbage Collection | 5~15 ms | Memory Profiler |
| Rendering | GPU負荷 | 10~50 ms | GPU Tracer, Xcode GPU |
Androidでは、Jankの診断はPerfettoシステムトレースから始まります。Perfettoはすべてのスレッド、CPU、GPU、スケジューラのアクティビティを記録します。Jankの明確な指標はChoreographer.doFrameとChoreographer.doCallbacksの行です:2つの連続するdoFrame呼び出しの間隔が16.6 msを超える場合、フレームがスキップされています。Perfettoは正確な原因(どのシステムコール、ロック、GCが遅延を引き起こしたか)を示します。Android Studio Profilerでは、CPU Profilerを通じて同様の機能が利用できます。
本番環境での自動Jank検出にはFrameMetricsAggregatorが使用されます — 各フレームの統計を収集し、セッションごとに集約するAPIです。Android 12+ではPerformanceHintManagerが登場しました — システムに目標フレームレートをヒントするAPIです。アプリが120 FPSシナリオで動作していることを示すと、システムはJankを防ぐためにCPU/GPU周波数を上げることができます。すべてのスキップされたフレームを簡単にログ記録するには、Choreographer.FrameCallbackにサブスクライブするだけで十分です。
KotlinコードはChoreographer.FrameCallbackにサブスクライブし、遅延時間とともに各スキップされたフレームをログに記録します。コールバックは各VSyncで呼び出されます。
class JankDetector {
private val frameBudget = 16_666_666L
private var previousFrameTime = 0L
private val callback =
Choreographer.FrameCallback { currentTime ->
if (previousFrameTime != 0L) {
val frameDuration =
currentTime - previousFrameTime
val skippedFrames =
(frameDuration / frameBudget) - 1
if (skippedFrames > 0) {
Log.w("Jank",
"Skipped $skippedFrames frames")
}
}
previousFrameTime = currentTime
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(callback)
}
}
iOSでは、Jankの診断はCore Animationテンプレートを使用したInstrumentsで行われます。InstrumentsはリアルタイムのFPS、オフスクリーンレンダリングの数、ヒットテストを表示します。iOSでのJankの主な指標:Core Animationタイムラインの赤いバー(フレーム予算超過)、高いRenderer値(オフスクリーンレンダリングを示す)、低いFPSです。本番モニタリングでは、MetricKitがMXAnimatoryMetricメトリクスを含むレポートを収集します。これには平均FPS、P50およびP95フレーム時間が含まれます。
iOSでのネイティブJank診断には、タイムスタンプとtargetTimestampのチェックを備えたCADisplayLinkが含まれます。現在のタイムスタンプがtargetTimestampから大幅に遅れている場合、1つ以上のフレームがスキップされています。Appleはカスタムプロファイリングにos_signpostを使用することも推奨しています:フレームレンダリングの開始と終了にsignpost-intervalを配置し、Instrumentsでどの間隔が16.6 msを超えているかを確認します。SwiftUIでは、Jank診断にUIView.invalidateIntrinsicContentSizeが使用されます — このメソッドの頻繁な呼び出しは不安定なLayoutを示します。
SwiftコードはCADisplayLinkを使用してスキップされたフレームを検出します。タイムスタンプとtargetTimestampの差が16.6 msを超える場合、Jankが記録されます。
class JankMonitor {
private var displayLink: CADisplayLink?
private var totalJank = 0
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(detectJank)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func detectJank() {
guard let link = displayLink else { return }
let delay = link.targetTimestamp
- link.timestamp
if delay > 0.0167 {
totalJank += 1
}
}
}
Jankのプロファイリングには、OS内蔵ツールとサードパーティSDKの両方が使用されます。Androidでは、主要なツールはPerfettoです(Systraceの後継)。Perfettoは最大30秒間のトレースを記録し、Webインターフェースui.perfetto.devで分析できます。Choreographerのアクティビティ、レンダリングスレッド(RenderThread)、GPUを含む正確なタイムラインを表示します。GPUの問題を詳細に分析するには、AGI(Android GPU Inspector)が使用され、フレーム時間だけでなく、シェーダー、ラスタライザー、テクスチャユニットなどの特定のGPUブロックの負荷も表示します。
iOSでは、同等のものはCore Animation、Metal System Trace、GPU Driverテンプレートを使用したInstrumentsです。Core AnimationはFPSとフレーム時間を表示し、Metal System Traceは各ドローコールまでのGPU作業を表示します。負荷のかかる実際のデバイスでのプロファイリングには、Firebase Performance(Screen Renderingメトリクスを収集)とSentry(Jank時にスタックトレースをキャプチャ)が使用されます。新しいAndroid 15 Performance Hint APIにより、開発者はシステムにどのフレームが重要かを伝え、Jankが近づいたときにシステムから警告を受け取ることができます。
Kotlinコードは、セッション全体のフレーム統計を収集するためにFrameMetricsAggregatorを使用します。アグリゲーターを停止した後、スキップされたフレーム数が出力されます。
class JankAggregator(private val activity: Activity) {
private val aggregator = FrameMetricsAggregator()
fun startCollection() {
aggregator.add(activity.window)
}
fun stopAndReport() {
aggregator.remove()
val result = aggregator.getMetrics()
val totalFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.size ?: 0
val jankFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.count { it > 16_666_666L} ?: 0
Log.d("JankReport",
"Jank ratio: \${jankFrames * 100 / totalFrames}%")
}
}
Jankを解消するには、そのタイプに応じたテクニックの組み合わせが必要です。Layout Jankの場合:深い階層をConstraintLayout/Compose/SwiftUIに置き換え、mergeタグを使用し、アニメーションでのrequestLayoutを避けます。Draw Jankの場合:Debug GPU Overdrawを使用して4x+のオーバードローを検出し、重いdrawableをベクターグラフィックス(VectorDrawable/PDF)に置き換え、ハードウェアレイヤーは注意して使用します — レンダリングは高速化しますが、GPUメモリを多く消費します。Thread Jankの場合:すべてのI/O操作、データベース作業、Bitmapデコードをバックグラウンドスレッドに移動し、適切なDispatcherを使用したKotlin Coroutines、またはSchedulers.io()を使用したRxJavaを使用します。
GC Jankの場合:onDraw()とgetView()でのアロケーションを最小限に抑え、オブジェクトプール(ObjectPool)を使用し、for-eachをインデックス付きforに置き換え、Kotlinのcopy()付きイミュータブルdata classは注意して使用します — copyは新しいオブジェクトを作成します。IPC Jankの場合:App Startupを介してContentProviderを遅延初期化し、Binder呼び出しをバックグラウンドスレッドに移動します。Rendering Jankの場合:テクスチャサイズを最大画面解像度まで縮小し、ASTCまたはETC2圧縮を使用し、過剰なシェーダーコンパイルを回避します(シェーダーは事前にコンパイル)。包括的な解決策は、CIでの定期的なPerfetto/InstrumentsプロファイリングとJank回帰の追跡です。
Kotlinコードは、reportFullyDrawn後に画面上にデータを非同期で読み込むことを示しています。これにより、重い処理が最初のフレームをブロックしません。コールバックはユーザーがインターフェースを表示した後に呼び出されます。
class JankSafeLoader {
suspend fun loadAfterFirstFrame(
activity: Activity
) {
// 最初のフレームがすでにレンダリングされていることを保証
if (Build.VERSION.SDK_INT >= 29) {
activity.reportFullyDrawn()
}
// 重い読み込み — 最初のフレームの後
withContext(Dispatchers.IO) {
val data = fetchHeavyData()
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
}
よくある質問
Jankとは、スキップされたレンダリングフレームで、アニメーションに目に見える途切れやぎくしゃく感として現れます。フレームの準備時間が時間予算(60 FPSで16.6 ms)を超えると発生します。
Layout Jank(頻繁なrequestLayout)、Draw Jank(オーバードロー)、Thread Jank(メインスレッドのブロッキング)、GC Jank(ガベージコレクション)、IPC Jank(Binder呼び出し)、Rendering Jank(重いシェーダー)。
システムトレースにはPerfetto、フレームフェーズ分析にはGPU Profiling、本番モニタリングにはFrameMetricsAggregatorを使用します。Android Studioでは — Deep Java Trace付きのCPU Profiler。
Instrumentsを使用し、Core AnimationまたはMetal System Traceテンプレートを使用します。本番環境では — MXAnimatoryMetricを使用したMetricKit。プログラム的には — timestampとtargetTimestampの差をチェックするCADisplayLink。
Googleによると、スクロールセッションの3%を超えるJank率(100回のスクロール中3回に途切れがある)は、否定的なレビューが22%増加することにつながります。目標率はスクロールセッションの0.5%未満です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。