フレームレートとは、グラフィックスシステムが1秒間に表示するフレーム数のことです。モバイルアプリケーションでは、フレームレートがアニメーション、スクロール、画面間の遷移の滑らかさを直接決定します。Android Developers, 2025によると、標準ディスプレイのターゲットフレームレートは60 fps、高リフレッシュレートのデバイスでは120 fpsです。目標値から外れると、視覚的なカクつきが生じ、ユーザーエクスペリエンスが低下します。
主なポイント
フレームレート(フレーム数)は、1秒あたりのフレーム数(fps)で測定される指標で、アプリケーションが1秒間に画面の画像を更新する回数を示します。人間の目は24 fps(映画)から動きを滑らかに知覚しますが、インタラクティブなUIでは、タッチやアニメーションを即座に感じさせるために少なくとも60 fpsが必要です。各フレームは完全なサイクルです。ユーザー入力の処理、レイアウトの計算、ビュー階層のレンダリング、画面への出力です。これらのいずれかの段階が割り当てられた時間予算(60 fpsで16.6ミリ秒)を超えると、フレームがスキップされ、ユーザーはカクつきを目にします。
アプリケーションのフレームレートとディスプレイのリフレッシュレートを区別することが重要です。リフレッシュレートは画面のハードウェア特性です。ディスプレイが1秒間に物理的に画像を更新する回数(60、90、120、144 Hz)です。フレームレートはアプリケーションが1秒間にレンダリングできるフレーム数です。アプリケーションが120 Hzディスプレイで60 fpsを出力する場合、1つおきのフレームが複製されます。画像は滑らかですが、可能な限り応答性が高いわけではありません。Google I/O 2023によると、最新のフラッグシップはシンプルなUIシナリオで120 fpsを維持できますが、負荷が高い場合(ゲーム、複雑なリスト)は40~60 fpsに低下します。
モバイルアプリケーションでのフレームレンダリングは、複数の段階からなるパイプラインを経由します。Androidのパイプラインには次の段階が含まれます。入力処理(Input)、アニメーション(Animation)、測定と配置(Layout)、描画(Draw)、GPU同期、画面出力(Swap)です。各段階はCPUまたはGPUで実行され、全段階の合計時間がフレーム予算を超えてはなりません。60 fpsの予算は16.6ミリ秒、120 fpsでは8.3ミリ秒です。Choreographer(Android)とCADisplayLink(iOS)は、ディスプレイの垂直ブランキング期間(VSync)にレンダリングを同期させ、画面更新の瞬間にのみフレームが出力されるようにして、ティアリングを防ぎます。
iOSのパイプラインも同様です。Run Loopがイベントを処理し、Core Animationがレイヤーを計算し、Render Server(別プロセス)がレンダリングしてフレームをGPUに送信します。iOSの違いは、専用のRender Serverプロセスがレンダリングをメインアプリケーションから分離している点です。アプリケーションがメインスレッドをブロックしても、Render Serverは最後に認識されたフレームを描画できますが、アニメーションは停止します。Render Server自体が追いつかない場合、GPUはアイドル状態になり、フレームレートが低下します。Apple WWDC 2022によると、iOSでフレームレートが低くなる最も一般的な原因は、CALayerの過剰なネスト、重いshadowPath、オフスクリーンレンダリングです。
KotlinコードはChoreographer.FrameCallbackを購読し、フレーム間の実際の時間を記録します。間隔が16.6ミリ秒を超えると、フレーム落ちが記録されます。
class FrameRateMonitor {
private var lastFrameTime = 0L
private val frameCallback =
Choreographer.FrameCallback { frameTimeNanos ->
if (lastFrameTime != 0L) {
val deltaMs = (frameTimeNanos - lastFrameTime) / 1_000_000f
if (deltaMs > 16.6f) {
Log.w("FrameRate",
"Skipped frame: $deltaMs ms")
}
}
lastFrameTime = frameTimeNanos
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(frameCallback)
}
}
リフレッシュレートは、ディスプレイが1秒間に物理的に画像を再描画する回数を決定するハードウェア特性です。標準ディスプレイは60 Hz、最新のフラッグシップは90、120、144 Hzです。アプリケーションのフレームレートは、リフレッシュレートより低い、等しい、または高い場合があります(後者の場合、超過フレームは破棄されます)。理想的なシナリオは、フレームレートがリフレッシュレートと一致することです。各ハードウェアサイクルがアプリケーションから新しいフレームを受け取り、動きが最大限に滑らかになります。フレームレートが低い場合、ディスプレイは最後のフレームを繰り返し、マイクロカクつきとして認識されます。
AndroidとiOSは動的リフレッシュレート切り替えをサポートしています。Android 12+はSmart Refresh Rateを使用します。スクロール中はシステムがレートを120 Hzに上げ、静的コンテンツではバッテリー節約のため60 Hzに下げます。iOS ProMotion(iPhone 13 Pro以降)も同様に機能し、コンテンツに応じてレートが10~120 Hzの間で変化します。開発者はデバイスが高リフレッシュレートをサポートしているか確認し、1フレームあたりの時間予算を適応させる必要があります。アプリケーションが8.3ミリ秒(120 Hzの場合)でフレームをレンダリングできない場合は、強制的に60 Hzにする方が、フレーム落ちのない安定したフレームレートを確保できます。
| ディスプレイタイプ | リフレッシュレート | 1フレームあたりの予算 | デバイス |
|---|---|---|---|
| 標準 | 60 Hz | 16.6ミリ秒 | 大多数のAndroid/iOS |
| 高 | 90 Hz | 11.1ミリ秒 | OnePlus、Pixel 6+ |
| フラッグシップ | 120 Hz | 8.3ミリ秒 | iPhone Pro、Galaxy S22+ |
| ゲーミング | 144 Hz | 6.9ミリ秒 | ROG Phone、Nubia RedMagic |
モバイルアプリケーションでフレームレートを測定するには、プラットフォームの組み込みツールとサードパーティのプロファイラーの両方が利用可能です。Androidでは、主要ツールはGPU Profiling(Developer Options → Profile GPU Rendering)で、各フレームを段階(Draw、Prepare、Process、Execute)ごとに分解したタイムラインを表示します。より詳細な分析はAndroid Studio Profilerが提供します。再描画を引き起こす特定のViewを示す完全なレンダリングプロファイルを記録します。iOSでは、Core AnimationテンプレートのInstrumentsを使用します。FPS、レイヤーレンダリング時間、オフスクリーンレンダリングの数を表示します。
本番環境でのフレームレート監視には、Firebase Performance(Android)を使用します。バックグラウンドでフレームレートを収集し、デバイス、OSバージョン、セッションごとに集計します。iOSでは、MetricKitがMXAnimatoryMetricを通じて同様のデータを提供します。ゲームやFlutterアプリケーションでは、FrameTimingCallback(Flutter)やUnity Profilerを使用します。平均フレームレートではなく、パーセンタイル(P50、P90、P99)を測定することが重要です。アプリケーションの平均が55 fpsでもP99が30 fpsの場合、1%の時間ユーザーが深刻なカクつきを経験していることになり、ネガティブなレビューの原因となります。
Dartの例は、FlutterでFrameTimingCallbackを購読し、フレーム落ちの数を記録する方法を示しています。コールバックは各フレーム完了後に発火します。
import 'package:flutter/scheduler.dart';
class FrameRateLogger {
int totalFrames = 0;
int missedFrames = 0;
void start() {
SchedulerBinding.instance
.addTimingsCallback(_onReportTimings);
}
void _onReportTimings(List<FrameTiming> timings) {
for (final timing in timings) {
totalFrames++;
if (timing.totalSpan()
> Duration(milliseconds: 16)) {
missedFrames++;
}
}
debugPrint("FPS: \${totalFrames - missedFrames}");
}
}
フレームレートの最適化は、レンダリングパイプラインのボトルネックを特定することから始まります。レイアウト段階での主な問題は、ビュー階層の過剰なネスト、相対レイアウト(多数のルールを持つRelativeLayout)の使用、頻繁なrequestLayout呼び出しです。解決策は、ConstraintLayoutまたはフラットな階層を使用し、5~6レベル以上のネストを避けることです。Draw段階ではオーバードローがあります。1つのピクセルが1フレームに複数回描画されることです。例えば、半透明のフラグメントの下にある白いActivityの背景、その下にさらにもう1つのレイヤーがある場合、各ピクセルは3回描画されます。Debug GPU Overdrawツールは問題領域を色で示します。オーバードローは2倍以下に抑えることを推奨します。
iOSでの主な問題は、重いcornerRadiusとmasksToBoundsです。これらはオフスクリーンレンダリングを引き起こし、Core Animationが一時バッファを作成し、そこに描画してから結果を画面にコピーします。オフスクリーンレンダリングはInstruments Core Animationで簡単に確認できます。Renderer行が赤の場合、問題があります。解決策は、cornerRadiusの代わりに事前に切り抜いた画像を使用したUIImageViewを使用し、絶対に必要な場合を除いてgroupOpacityとshouldRasterizeを避けることです。両プラットフォームとも、invalidate()とsetNeedsDisplay()の呼び出し回数を最小限に抑えることが重要です。このような呼び出しは毎回、完全なビュー再描画サイクルをトリガーします。
コードは、深いRelativeLayoutのネストをフラットなConstraintLayout構造に置き換えることを示しています。ネストレベルを4から1に減らすと、レイアウト時間が30~50%短縮されます。
// 例: ConstraintLayoutによるフラット構造
class OptimizedView(context: Context) :
ConstraintLayout(context) {
private val binding =
ItemProfileBinding.inflate(
LayoutInflater.from(context)
)
fun bind(user: User) {
binding.avatar.setImageURI(user.avatarUrl)
binding.nameText.text = user.name
// コンテナ全体を再描画せずにデータをバインド
}
}
最新のモバイルアプリケーションは、適応型フレームレートをますます使用しています。これは、現在のシナリオに応じて目標周波数を動的に調整するシステムです。高速スクロールには滑らかさのため120 fpsが必要ですが、静的画面では60 fps、ビデオでは30 fpsで十分です。Androidでは、Choreographer.setFrameInterval(API 33+)とWindow.setFrameRateを介して適応が実装されています。開発者は優先周波数を指定できます。SurfaceViewのsetPreferredRefreshRateやWindowのsetFrameRateなどです。iOSはProMotionを介して自動的に周波数を管理しますが、開発者はCADisplayLinkのpreferredFramesPerSecondを明示的に設定できます。
動的フレームレートは、ゲームやアニメーションを含むアプリケーションにとって特に重要です。Googleによると、静的画面でフレームレートを120 Hzから60 Hzに下げると、GPUエネルギーを30~40%節約できます。滑らかさと消費電力の最適なバランスを達成するには、さまざまなシナリオで実際のフレームレートを測定し、シーンに応じて目標fpsを設定し(ゲーム:60、メニュー:30、ビデオ:24)、Lifecycle-awareコンポーネントを介してモードを切り替えて、アプリケーションが最小化されたときにバックグラウンドで120 fpsのレンダリングにリソースを浪費しないようにすることを推奨します。
Swiftコードは、iOSでCADisplayLinkのpreferredFramesPerSecondを設定します。スクロール中はレートが120 Hzに上昇し、停止時は60 Hzに低下します。
class AdaptiveFrameRateManager {
private var displayLink: CADisplayLink?
func startWithHighRate() {
displayLink = CADisplayLink(
target: self,
selector: #selector(step)
)
if #available(iOS 15.0, *) {
displayLink?.preferredFrameRateRange =
CAFrameRateRange(
minimum: 60,
maximum: 120,
preferred: 120
)
}
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func step() {
// アニメーション更新
}
}
よくある質問
モバイルアプリケーションの目標フレームレートは60 fps(1フレームあたり16.6ミリ秒)です。120 Hzディスプレイ搭載デバイスでは120 fpsが望ましいです。30 fpsを下回ると、ユーザーエクスペリエンスが著しく低下します。
フレームレートはアプリケーションが1秒間にレンダリングするフレーム数です。リフレッシュレートはディスプレイが1秒間に物理的に画像を更新する回数です。フレームレートがリフレッシュレートより低い場合、ディスプレイは最後のフレームを繰り返します。
Developer OptionsのGPU Profiling、Android Studio Profiler、またはFirebase Performanceを使用します。プログラムによる測定には、フレーム間隔計算付きのChoreographer.FrameCallbackを使用します。
オーバードローは、1つのピクセルが1フレームに複数回描画されることです。余分なレイヤーごとにDrawフェーズの時間が増加し、フレームレートが低下します。最適なオーバードローは2倍、重大なのは4倍以上です。
静的コンテンツでは、動的フレームレートが周波数を30~60 Hzに下げ、GPU負荷を30~40%削減します。スクロール中は滑らかさのため90~120 Hzに上がります。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。