8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutterとネイティブのパフォーマンス比較:iOS/Androidで実測ベンチマーク

Posted at

はじめに

モバイルアプリ開発において、クロスプラットフォーム開発の選択は重要な判断です。Flutterは単一のコードベースでiOSとAndroidの両プラットフォームに対応できる利点がありますが、パフォーマンス面での懸念が常に付きまといます。
本記事では、iOSとAndroidの両プラットフォームにおいて、Flutterとネイティブ実装のパフォーマンスを具体的な数値とともに比較検証しました。

検証内容

  • リスト表示
    • 画像とテキストで構成されたアイテム(縦:100個)
  • Lottieアニメーション表示
    • ネット上にあるjsonファイルを再生(5列×20行)
  • 各実装でのCPU使用率、メモリ使用量、FPSを計測

計測

  • デバイス:iPhone15(iOS)、Pixel8 (Android)

  • Flutter vs iOS

    • CPU 使用率、メモリ使用量
      • xcode の Debug navigator を使用
      • 5 回計測して平均値を算出
    • FPS
      • xcode の Instruments の TimeProfiler で計測
      • スクロール操作をしながら計測する
  • Flutter vs Android

    • CPU 使用率、メモリ使用量
      • AndroidStudio の Profier の SystemTrace と HeapDump で計測
      • 5 回計測して平均値を算出
    • FPS
      • Flutter DevTools と Profier の CaptureSystemActivities で計測
      • スクロール操作をしながら計測する

実装方法

iOS:ListView実装
struct ListView: View {
    var body: some View {
        let url = URL(string: "image_url")
        List {
            ForEach(0..<100) { _ in
                HStack {
                    AsyncImage(url: url){
                        image in image.image?.resizable()
                    }.frame(width: 160, height: 100)
                    VStack{
                        Text("テストタイトル").frame(maxWidth: .infinity, alignment: .leading)
                        Text("テキストテキストテキスト").frame(maxWidth: .infinity, alignment: .leading)
                    }
                }
            }.listRowSeparator(.hidden)
        }.listStyle(.plain)
    }
}
iOS:Animation実装
struct AnimationView: View {
    var body: some View {
        let url = URL(string: "animation_url")
        List {
            ForEach(0..<20) { _ in
                HStack {
                    ForEach(0..<5) {
                        _ in
                        if let url = url {
                            LottieView(path: url)
                                .background(.cyan)
                        }
                    }
                }.frame(width: 400, height: 70)
            }.listRowSeparator(.hidden)
        }.listStyle(.plain)
    }
}

Android ListView実装
@Composable
fun VerticalListView() {
    LazyColumn {
        items(100) {
            ListItem()
        }
    }
}

@Composable
fun ListItem() {
    Row(modifier = Modifier.padding(4.dp)) {
        Image(
            painter = rememberImagePainter("image_url"),
            contentDescription = null,
            modifier = Modifier
                .width(160.dp)
                .height(100.dp),
            contentScale = ContentScale.Crop
        )
        Column(modifier = Modifier.padding(start = 8.dp)) {
            Text(text = "テストタイトル")
            Text(text = "テキストテキストテキスト")
        }
    }
}
Android Animation実装
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AnimationGrid() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Animation Grid") }
            )
        }
    ) { paddingValues ->
        LazyVerticalGrid(
            columns = GridCells.Fixed(5),
            contentPadding = paddingValues,
            modifier = Modifier.fillMaxSize()
        ) {
            items(100) { _ ->
                LottieAnimationItem()
            }
        }
    }
}

@Composable
fun LottieAnimationItem() {
    val compositionResult = rememberLottieComposition(
        LottieCompositionSpec.Url("animation_url")
    )
    val composition = compositionResult.value

    val isVisible = LocalWindowInfo.current.isWindowFocused

    val progressState = animateLottieCompositionAsState(
        composition = composition,
        iterations = LottieConstants.IterateForever,
        isPlaying = isVisible // 画面に表示されている時だけアニメーション
    )
    val progress = progressState.value

    LottieAnimation(
        composition = composition,
        progress = { progress },
        modifier = Modifier
            .aspectRatio(1f)
            .graphicsLayer {
                renderEffect = android.graphics.RenderEffect
                    .createBlurEffect(0f, 0f, android.graphics.Shader.TileMode.CLAMP)
                    .asComposeRenderEffect()
            }
    )
}
Flutter ListView実装
return MaterialApp(
        title: 'Flutter ListView',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: Scaffold(
          body: ListView.builder(
            itemCount: 100,
            itemBuilder: (context, index) {
              return Row(
                children: <Widget>[
                  Container(
                    width: 160,
                    height: 100,
                    margin: const EdgeInsets.only(top: 4, bottom: 4),
                    child: Image.network("image_url")
                  ),
                  const Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text("テストタイトル"),
                      Text("テキストテキストテキスト"),
                    ],
                  )
                ],
              );
            },
          ),
        ));
Flutter Animation実装
return MaterialApp(
      title: 'Animation Grid',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Animation Grid'),
        ),
        body: GridView.count(
          crossAxisCount: 5,
          children: List.generate(100, (index) {
            return Lottie.network("animation_url");
          }),
        ),
      ),
    );

計測結果(ListView)

1. CPU 使用率

iOS

実装 起動時 スクロール時 通常時
Native 78% 28.2-41% 0-0.2%
Flutter 95.6% 31.4-36.8% 0-0.8%

Android

実装 起動時 スクロール時 通常時
Native 14.0% 2.9-10.0% 0%
Flutter 18.0% 5.1-28.8% 0%

2. メモリ使用量

iOS

実装 起動時 スクロール時 通常時
Native 17.22MB 20.1-21.14MB 17.48-20.8MB
Flutter 97.5MB 104.4-107.84MB 90.62-100.02MB

Android

実装 起動時 スクロール時 通常時
Native 148.4MB 159.4-184.4MB 186.8-189.4MB
Flutter 314.2MB 379.4-403.4MB 390.0-390.2MB

3. FPS

iOS

実装 FPS
Native 49-60fps
Flutter 59-60fps

Android

実装 FPS
Native 60fps
Flutter 60fps

計測結果(Animation)

1. CPU 使用率

iOS

実装 起動時 スクロール時 通常時
Native 105.8% 37.8-65.6% 0%
Flutter 157.8% 146-156.4% 124-142%

Android

実装 起動時 スクロール時 通常時
Native 17.6% 9.1-24.2% 8.2-19.6%
Flutter 26.9% 12.1-40.8% 8.0-37.5%

2. メモリ使用量

iOS

実装 起動時 スクロール時 通常時
Native 105.8% 37.8-65.6% 0%
Flutter 157.8% 146-156.4% 124-142%

Android

実装 起動時 スクロール時 通常時
Native 225.2MB 335.0-401.6MB 239.6-282.8MB
Flutter 220.2MB 576.8-609.8MB 417.2-426.6MB

3. FPS

iOS

実装 FPS
Native 59-60fps
Flutter 39-49fps

Android

実装 FPS
Native 58fps
Flutter 23-25fps

結論

本検証により、パフォーマンス面ではネイティブ実装が全体的に優位であることが分かりました。

パフォーマンス比較結果

Android

  • CPU 使用率、メモリ使用量、FPS においてネイティブが優位
  • メモリ使用量は Flutter がネイティブの約 2 倍
  • アニメーション処理で Flutter の FPS 低下が顕著 (Flutter: 23-25fps vs Native: 58fps)
  • CPU 使用率は軽量アプリ(ListView)では差が小さいが、アニメーション処理で差が拡大

iOS

  • CPU 使用率、メモリ使用量、FPS においてネイティブが優位
  • メモリ使用量は Flutter がネイティブの約 5 倍
  • アニメーション処理で Flutter の FPS 低下が顕著(Native: 59-60fps vs Flutter: 39-49fps)
  • CPU 使用率はアニメーション処理で Flutter が大幅に高い(Flutter: 124-157% vs Native: 37-65%)

アプリケーション別推奨事項

ネイティブを推奨

  • アニメーションを多用するアプリ(FPS 差が顕著)
  • 低スペック端末でも快適に動作させたい場合
  • パフォーマンスおよびリソース効率を重視する場合
  • 長期的な保守・最適化を重視する場合

Flutter も選択可能

  • 軽量な UI 中心のアプリ(ListView レベルでは FPS 差が小さい)
  • 開発効率とのトレードオフを重視する場合(1 つのコードベースで両プラットフォーム対応)

参考情報: 起動時間、UI初期化時間について

※以下の起動時間、UI初期化時間の計測は、Flutter とネイティブでレンダリングシステムが根本的に異なるため、直接比較は参考程度に留めることを推奨します。

計測方法

iOS

  • 合計起動時間
  • UI 初期化(レンダリング時間)
    • iOS
      • Initial Frame Rendering (IFR)
        • xcode の profile の App Launched で計測できる
        • 30 回計測して平均値を算出
    • Flutter
      • WidgetsBinding.instance.addPostFrameCallback
        • ウィジェットツリーが描画された後に呼ばれる
        • 5 回計測して平均値を算出

Android

  • 合計起動時間(reportFullyDrawn()まで)
  • UI 初期化(レンダリング時間)
    • Android
      • SideEffectからdoOnPreDrawまでの時間を計測
        • 5 回計測して平均値を算出
    • Flutter
      • WidgetsBinding.instance.addPostFrameCallback
        • ウィジェットツリーが描画された後に呼ばれる
        • 5 回計測して平均値を算出

計測の制限事項

  • iOS: Skia エンジン vs ネイティブ UIKit/SwiftUI の描画システムの違い
  • Android: Flutter Engine vs Android View System の初期化タイミングの違い
  • レンダリングの違い: Flutter の Skia エンジンによる独自描画とネイティブの標準 UI フレームワークでは描画完了の定義が異なる

起動時間計測結果

ListView 実装

iOS

実装 UI 初期化 合計起動時間
Native 34.35ms 453ms
Flutter 195ms 458ms

Android

実装 UI 初期化 合計起動時間
Native 514.6ms 166ms
Flutter 144.4ms 198ms

Animation 実装

iOS

実装 UI 初期化 合計起動時間
Native 43.29ms 472ms
Flutter 186.4ms 454ms

Android

実装 UI 初期化 合計起動時間
Native 563.6ms 174ms
Flutter 157.8ms 209ms

「Android vs Flutter」の計測にて、onCreate()からonResume()を計測しましたが、Flutterには独自のUIライフサイクル(StatefulWidget)があり、onResume()時点でUIの初期化が完全に完了していない可能性があります。

参考までにFlutterとNativeにおいてLottieのローディング完了までを再計測してみました。
https://zenn.dev/mukkun69n/articles/552173cb084e18#statefulwidget%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB

実装 Lottieローディング時間
Native 165ms
Flutter 82.9ms
8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?