サインイン

Flutter アプリを捨て、Rust コアの上にネイティブで Cliqer Remote を作り直した理由

Cliqer Remote を Flutter で作り、実機で計測し、ひとつの Rust コアの上にネイティブで作り直しました。バッテリー消費は半分、発熱なし。その数値と教訓を紹介します。

Cliqer Team

2026年10月5日・3分で読めます

A presenter holds a phone remote: one half seen through a thermal camera glowing hot, the other half cool in mint stage light

9月26日、私たちは発表者向けスマートフォンアプリ Cliqer Remote の開発を Flutter で始めました。5日後には TestFlight と Google Play の内部テストトラックに公開され、Web 版の発表者アプリのすべての機能、つまりプレゼンリモコン、ライブ画面、インク、チャット、ノート、通話を備えていました。ちゃんと動きました。ところが実機のスマートフォンで計測したところ、共有画面を表示しているだけで1台の表面温度が42 °Cに達するのを目にし、そのアプリを捨てました。

このアプリをチームの外の人が目にすることはありませんでした。代わりにお届けするのは、すべてのプラットフォームでネイティブのアプリです。iPhone、iPad、Apple Watch では SwiftUI、Android と Wear OS では Kotlin と Jetpack Compose、そしてその下には Rust で一度だけ書かれたひとつのエンジンがあります。本記事では、その理由を、途中で計測した数値とともにお伝えします。

要点
  • プレゼンリモコンは、スマートフォンアプリとしては特殊な役割を担います。温かい手の中で1時間握られ、画面のライブ映像を表示し、タップを一度も取りこぼしてはいけません。フレーム時間、発熱、サイズが、ほとんどのアプリ以上に重要になります。
  • Flutter 版は書くのが速く見た目も良好でしたが、ガラス効果と映像処理の経路が毎フレーム GPU に負担をかけていました。Galaxy S24+ では、ある画面の1フレームあたりの GPU 処理を19.8 msから5.7 msに削減しましたが、それでも製品づくりではなくエンジンとの格闘が続いていました。
  • 次に、3つのビルドを同じスマートフォン、同じ部屋で、同じ動く画面をそれぞれ10分間表示させました。最初の Flutter 版は毎秒47.6フレームを描画し、スマートフォンを5.9 °C温め、168 mAhを消費しました。ネイティブアプリはUI フレームを1枚も描画せず、温度上昇は0.2 °C、消費は86 mAh。バッテリー消費は半分です。
  • ネイティブアプリはより小さくなりました。Apple Watch アプリを内包した iPhone アプリで10.8 MB(Flutter 版は27.6 MB)、Android のダウンロードサイズは5.8 MB(Flutter 版は15.6 MB)で、いずれも同じ方法で計測しています。
  • 同じ取り組みの中で、デスクトップアプリがクリックをスライド送りに変える速度を10倍に高めました。Mac の PowerPoint で48 ms、Keynote で42 msです。以前は約0.5秒かかっていました。

プレゼンリモコンという特殊なアプリ

ほとんどのアプリは短時間ずつ使われます。プレゼンリモコンは違います。講演の間ずっと片手に握られ、しばしばステージ照明の下で、画面はつけっぱなし、無線通信も常に稼働しています。Cliqer では、発表者の画面のライブ映像を表示し、スライドにインクを描き、チャットやノートを扱い、ホストに招待されれば発表者をカメラに映すこともできます。

この役割から、3つのルールが決まります。

  1. すべてのタップが、今すぐ届くこと。遅れて届くクリックは、聴衆の前で沈黙したまま立ち尽くす登壇者を意味します。
  2. スマートフォンが熱くならないこと。熱くなったスマートフォンはスロットリングを起こし、スロットリングしたスマートフォンはフレームを落とし、映像のデコードが遅れ、バッテリーの減りも速くなります。それに、1時間握り続けるには不快です。
  3. アプリが小さく、すぐに起動すること。多くの人は登壇の数分前に、会場で、しばしばカンファレンスの Wi-Fi 経由でインストールします。

Flutter は、ほとんどのアプリならこの3つをすべて満たせます。私たちのアプリは、得られるものよりコストのほうが大きくなる種類のアプリだったのです。

Flutter が優れていた点

評価すべき点はきちんと評価します。Flutter のおかげで、空のフォルダから両ストアに並ぶ全機能を備えたアプリまで5日で到達しました。コードベースはひとつ、ホットリロード、優れたテスト環境、どこでも同じに見える UI。テストは266本書きました。フォームとリストが中心のアプリ、ダッシュボード、ストアを作るなら、今でも迷わず Flutter をおすすめします。

Flutter からは多くを学びました。この記事のほぼすべての知見は、その Flutter 版を実機でトレースして得たもので、修正の一部は今では Web 版を含むすべての Cliqer クライアントに組み込まれています。

痛手その1:ガラス効果は1フレームごとに画面全体を消費する

Cliqer の外観はすりガラスです。背後にあるものをぼかす半透明のパネルです。iPhone と iPad では、これは軽い処理でした。Android のテスト機、120 Hz ディスプレイを搭載した Galaxy S24+ では違いました。120 Hz では、1フレームを描画するための時間は全部で8.3 msしかありません。

デバイス上で GPU をトレースしたところ、原因はレンダリングエンジンが背景を読み取る方法にありました。ガラスパネルごとに、毎フレーム画面全体をコピーし、さらにマルチサンプリングまでかけていたのです。参加画面にはガラスパネルが9枚あったので、1フレームあたり9回のフルスクリーンコピーです。このスマートフォンの GPU はタイルベースではないデスクトップクラスの設計で、直前に描画した内容を低コストで読み戻す手段がありません。さらに新しいテクスチャサイズのたびに新たにメモリが確保され、カーネルがまずそれをゼロで埋めます。あるトレースでは、**ラスタースレッドの時間の71%**がグラフィックメモリの確保に費やされていました。

私たちは画面ごとに修正していきました。ブラーをパネルの範囲内に制限できるよう、エンジンのプレリリース版に移行しました(41 msから17 msに)。参加画面の9枚のパネルをひとつの共有読み取りにまとめました(19.8 msから5.7 msに)。新しいサイズのたびに新たな確保が発生するため、ガラス面のサイズのアニメーションをやめ、固定サイズのレイヤーでアニメーションを再現しました。QR スキャナーの最悪フレームは285 msから18 msになりました。

これらの修正はどれもエンジンへの回避策であって、発表者のための機能ではありません。しかも、ルーム画面で**GPU 時間の80〜90%**をガラスが占めていたため、新しいパネルを1枚追加するだけで再びフレーム落ちが起きかねない状態でした。

本当の痛手:映像と、それが生む熱

共有画面は、アプリが表示するもののなかで最も重い処理です。Flutter 版では、デコードした映像フレームをテクスチャとして UI に渡していたため、映像フレームのたびに UI 全体が再描画されていました。ルーム、ガラス、コントロール、そのすべてを毎秒45回、ひとつの矩形の中のピクセルを動かすためだけにです。

最初に計測したのは10月1日、Galaxy でシステムトレースを使ったときです。映像の再生中、GPU は約440 MHzで動いていました。スライドが切り替わると、フレームには25〜33 msかかり、切り替えの半分以上の時間で GPU は上限の800 MHzに達していました。スマートフォンはシステムの「moderate」熱ステータスで、表面温度は42 °C、それに対処するために自ら GPU を制限していました。熱は計測結果まで狂わせました。温まったスマートフォンは冷えたものより約20%遅い値を示したため、比較はすべて同じ温度でビルドを交互に入れ替えて行う必要がありました。

次に、当然の実験を試しました。映像を UI の外、OS 自身が合成するネイティブレイヤーに載せるのです。同じスマートフォン、同じアプリ、同じルームで、UI は映像のための再描画をやめ、GPU クロックは440 MHzから121 MHzに下がり、スライド切り替え時のフレームは25〜33 msから3〜7 msになりました。私たちは10月1日にこの修正を Flutter アプリに組み込んでリリースしました。同時に、本当の答えがどこにあるかも見えました。ネイティブアプリでは、iPhone でも Android でも映像は常にシステムレイヤー上にあるため、この種の問題は回避策ではなく、構造そのものによって消えるのです。

同じスマートフォン、同じ部屋、10分間

発熱についての主張は口で言うだけなら簡単なので、私たちは計測しました。10月5日、同じ Galaxy S24+ に3つのビルドを入れ、1つずつ順番に、それぞれ10分間、電源を外した状態で、同じ部屋で、同じ動く共有画面を表示させました。各計測の前にはスマートフォンを冷ましました。

  1. Flutter 初期版:最初に作ったままのアプリ。映像は UI が描画。
  2. Flutter 調整版:上記のガラスと映像の修正を加えた同じアプリ。
  3. ネイティブ:新しい Cliqer Remote。

最初の Flutter 版は、映像を表示するためだけにインターフェース全体を毎秒47.6回再描画し、GPU を平均366 MHzに保ち、毎秒1.47秒の CPU 時間を使っていました。ネイティブアプリは10分間でインターフェースのフレームを1枚も描画しませんでした。動いていたのは映像レイヤーだけです。GPU は平均129 MHz、CPU は毎秒0.69秒、バッテリー消費は168 mAhに対して86 mAhでした。同じ仕事で、バッテリー消費は半分です。

そして発熱です。10分間で、最初の Flutter 版はバッテリー温度を36.2 °Cから42.1 °Cに上げ、スマートフォンは自らスロットリングを始めました。Flutter 調整版の上昇は1.9 °C。ネイティブアプリは0.2 °Cでした。講演は10分より長く続きます。この差こそ、手で感じる違いです。

Flutter 調整版の名誉のために言えば、かなり迫っていました。Flutter 内での私たち自身の修正でコストの大部分はなくなり、ネイティブとの差はより小さく、CPU で12%、バッテリーで25%でした。重要なのは、それぞれの出発点です。初期版は、優れた Flutter アプリが素のままでどう動くかを示すものでした。ネイティブは、回避策なしで低温という結果を得られます。

再現できるよう、計測方法を記します。デバイス上の Perfetto トレース(フレームタイムライン、GPU 周波数、バッテリーカウンター)、カーネルから取得したプロセスの CPU 時間、スマートフォン自身のバッテリー温度を使い、すべての計測で共有画面に同じ60 fpsのアニメーションを表示しました。ビルドごとにスマートフォン1台、計測1回なので、数値はラボのベンチマークではなく、慎重なフィールドテストとして捉えてください。また、ネイティブの計測は約2 °C低い温度から始まったため、上昇幅で比較しています。

代わりに作ったもの

新しい Cliqer Remote は、すべてのプラットフォームでネイティブであり、ひとつの共有エンジンの上に構築されています。

インターフェースは各プラットフォームでネイティブです。iPhone と iPad ではシステム標準の Liquid Glass を使った SwiftUI。Android では Jetpack Compose で、プラットフォームが許す範囲で本物の背景ブラーを使っています。Apple Watch と Wear OS のアプリもネイティブで、「次へ」と「戻る」を手首に載せられるほど小型です。

エンジンは Rust で一度だけ書かれています。ルームへの参加、ネットワーク変更後の再接続と再開、画面映像の受信、クリック・レーザー・インクの送信、チャット、ノート、タイマー、通話、スマートフォンとコンピューターのペアリングまで、すべてが両アプリ共通のひとつの Rust ライブラリにあります。各アプリが担うのは、インターフェースの描画、システムのハードウェアデコーダーによる映像のデコード、カメラとマイクとのやり取りだけです。プロトコルの修正は一度で済み、両プラットフォームに届きます。2つのアプリの挙動がずれることはありません。

そのエンジンに Rust を選ぶのは自然なことでした。フレームを止めるガベージコレクターがなく、ランタイムなしでメモリ安全性が得られ、iOS と Android の両方に向けてひとつの小さなライブラリにコンパイルできます。そして、重要な部分で高速です。Mac 上で、私たちのエンジンは20 KBの H.264 フレームを31 µsでパケット化し、セキュアなハンドシェイクを片側198 µsで完了します。テストでは、100 KBの映像フレームを46 µsで Android のインターフェースに渡しています(この境界を最適化する前は440 µsかかっていました)。リレー経由の往復時間は、テストで中央値13〜16 msでした。

さらに小さく

両方のビルドを同じ方法で計測しました。Apple にアップロードした iPhone アプリは27.6 MBから10.8 MBになりました。しかもこれは Apple Watch アプリ(1.5 MB)を内包した状態です。Android スマートフォン向けのダウンロードサイズは、Google 公式のバンドルツールで同じ64ビット端末向けに計測し、15.6 MBから5.8 MBになりました。会場の Wi-Fi で残り5分というときにアプリをインストールする人にとって、旧サイズの約3分の1という差は大きな意味を持ちます。

ミリ秒の追求はスマートフォンで終わらなかった

スマートフォンアプリが速くなっても、それはクリックの半分にすぎません。残りの半分はプレゼンテーションを動かすコンピューターです。アプリを作り直している間、私たちはそちら側にも取り組みました。

クリック:0.5秒からその10分の1へ

Cliqer デスクトップアプリにクリックが届くと、プレゼンテーションを進めたうえで、どのスライドが表示されているかをルーム内の全員に伝える必要があります。この後半の処理では、以前はクリックのたびにプレゼンテーションアプリにスライド番号を問い合わせており、その問い合わせが遅かったのです。

Cliqer 2.3 は PowerPoint や Keynote への接続を1本開いたまま維持し、直前に実行したステップから位置を直接読み取ります。Mac で20回計測した中央値で、クリックからスライドまでの時間は PowerPoint で509 msから48 ms、Keynote で408 msから42 msに短縮されました。発表者がセクションを飛ばすときのような10回の素早い連打は、以前は落ち着くまで5.3秒かかっていました。今ではひとつの移動にまとめられ、254 msで着地します。

不安定な Wi-Fi でも遅れない映像

今年の初め、共有画面が Wi-Fi で数秒間フリーズすることがある原因を突き止めました。Cliqer の画面映像は信頼性のあるデータチャネルで送られており、そのチャネルの基盤となる標準規格では、失われたパケットを再送するまで少なくとも丸1秒待ち、再送のたびに待ち時間が倍になります。Chrome は400 msを使っています。失われたフレームの後ろにあるフレームはすべて列に並んで待ち、さらにホストが同じキューに完全リフレッシュを押し込み、遅い回線ではそれが何度も繰り返されていました。

再送タイマーを Chrome の値に合わせ、リフレッシュが滞留の後ろに並ばないようにしました。Wi-Fi の損失モデルを使ったネットワークシミュレーションでは、1080p の共有で2分あたり15〜22回あった1秒以上のフリーズが0〜2回に、ライブからの遅延の中央値は最大2.3秒から12 msになりました。また、完全リフレッシュはすべての視聴者に送られるため、ストリームごとに0.5秒に1回までと上限を設けました。この上限を設ける前は、1人の視聴者が毎秒19回、約40 Mbit/sの完全リフレッシュを受け取ることもありました。

スマートフォン側では、Galaxy のハードウェア映像デコーダーが、ほぼ1秒に1回、通常のフレームを約60 ms保持していました。デコーダーにフルスピードで動作するよう指示したところ、50秒間で遅延フレームはゼロになりました。この修正はネイティブの Android アプリに搭載されています。

ほかのチームに伝えたいこと

スマートフォンでリアルタイム処理を作るなら、以下は私たちが何日もかけて学び、数分で適用できることです。

  1. エミュレーターではなく、手元で最も条件の悪い実機で計測する。私たちのエミュレーターは別のグラフィックバックエンドで60 Hz動作しており、ガラスの問題をひとつも再現できませんでした。
  2. 同じ温度でビルドを交互に計測する。温まったスマートフォンは約20%遅い値を示しました。冷えた状態の計測と熱い状態の計測を比べると、結果を見誤ります。
  3. ブラーの読み取り回数を数える。背景ブラーはひとつごとに、1フレームあたりフルスクリーンコピー1回分のコストがかかることがあります。まとめる、範囲を制限する、そしてぼかした面のサイズは決してアニメーションさせないことです。
  4. 映像のために UI を再描画しない。映像はシステムが合成するレイヤーに載せましょう。私たちが計測したなかで、GPU、バッテリー、発熱のいずれにおいても最大の改善でした。
  5. ハードウェアデコーダーにリアルタイム速度を要求する。Android ではデコーダーの operating rate を設定します。1行で、毎秒発生していた60 msの停止がなくなりました。
  6. トランスポートのタイマーを確認する。信頼性のあるデータチャネルは、古い標準規格から再送タイマーを受け継いでいます。最小1秒は、ライブ映像にとっては永遠に等しい時間です。
  7. 完全リフレッシュを高コストなフレームとして扱う。1回ごとにすべての視聴者に届くため、ストリームごとに上限を設けます。
  8. 入力の連打をまとめる。10回の押下は、10回の往復ではなく、10ステップ分の1回の移動にすべきです。
  9. ツールがビルドに書き込む内容を読む。プレリリース版のツールチェーンが、断りなく Android マニフェストのレンダリングフラグを有効にしていました。

これがもたらすもの

10x

Mac の PowerPoint と Keynote でクリックからスライドまでが高速化

5.8 MB

Android のダウンロードサイズ。旧サイズの約3分の1

10.8 MB

Apple Watch アプリを内包した iPhone アプリ

Half

最初の Flutter 版と比べたバッテリー消費。同じスマートフォン、同じ10分間

Cliqer Remote は iPhone、iPad、Android、Apple Watch、Wear OS でネイティブに動作します。クリックはより速く、スマートフォンは熱くならず、バッテリー消費は最初のビルドの半分。映像はスマートフォン本来の設計どおりの方法で描画され、アプリのサイズは旧版の約3分の1です。Web 版の発表者アプリは、これまでどおりインストール不要であらゆるブラウザで動作するので、ルームの誰ひとり取り残されることはありません。

アプリを入手

iPhone、iPad、Android、スマートウォッチ向けの Cliqer Remote。

プレゼンリモコンの遅延:ミリ秒が重要な理由

タップからスライドが動くまでの間に、実際に何が起きているのか。

画面共有を4倍速くした方法

今年のもうひとつの大きなパフォーマンス改善の話。

リアルタイム基盤を Cloudflare から移行した理由

安定性を追求する同じ取り組みを、サーバー側で。

よくある質問

flutterrustnative appsswiftuijetpack composeperformancelatencyengineering

執筆:Cliqer Team

私たちは、世界中のステージ、教室、会議室で使われているインターネットリモコン、Cliqerを開発しています。

次の本番はCliqerで

どんなスライドでも、どんなスマートフォンでも、どこからでも。デスクトップアプリがルームをホストし、発表者はリンクを開くだけです。

こちらもおすすめ

次の本番に役立つ製品ニュースをもっと

A dedicated server rack glowing mint in a dark data hall
製品ニュース

CliqerのリアルタイムをCloudflareから移行した理由(それでも私たちがファンであり続ける理由)

Cloudflareのエッジで過ごした4か月:何が壊れ、対処するために何を作り、なぜCliqerが今は自社の専用サーバーで動いているのか。
A software engineer reviewing a video latency graph on a monitor in a dimly lit office
製品ニュース

画面共有を4倍高速化した方法

Cliqerの画面共有のレイテンシを2秒超から約0.5秒まで短縮した、その舞台裏のエンジニアリングストーリーです。
A bright hybrid meeting room with a camera on a tripod and a laptop running Cliqer Studio
ライブプロダクション

Cliqer vs Ecamm Live:ショーに合うライブスタジオはどちら?

Cliqer と Ecamm Live を公平に比較。Mac と Windows に対応し、Studio、Live、Timer、スマートフォンのリモコンを備えた Cliqer と、Mac 専用スタジオの違いを解説します。
ニュースレター

次の本番を迎える前に、最新ガイドを受け取る

発表者、AVクルー、イベントチームのための実践的なガイドと、押さえておきたいCliqerのリリース情報をお届けします。スパムは送りません。配信停止はワンクリックです。