✦工程

我們為什麼選擇原生而非 Electron(而且還會再選一次)

每個人都叫我們用 Electron。寫一次,到處執行。動作要快。我們卻反其道而行:Apple 上用 SwiftUI、Android 上用 Jetpack Compose、Windows 上用 C++/WinRT,而在需要按時鐘把像素送出的地方,則直接使用 Metal 與 Vulkan。以下是我們為同一個產品打造四個原生用戶端所學到的事。

簡單的路與正確的路

當你告訴別人,你在 2024 年要從零開始做一個遠端桌面 App,第一個問題永遠是:「為什麼不直接用 Electron?」

這是個合理的問題。Electron 驅動了地球上一些最成功的 App:VS Code、Slack、Discord、Figma 的桌面包裝。它讓你寫一次就能到處部署,你的網頁開發者一夜之間就成了桌面開發者。上市時間的優勢是真實的。

我們看了數字、權衡了取捨,選擇了最困難的路:每個平台都完全原生。iOS 與 macOS 上用 SwiftUI,Android 上用 Jetpack Compose,Windows 上用 C++/WinRT,Apple 上以 Metal 繪製,其他平台用 Vulkan,核心則是自行打造的 Go 伺服器。

不是因為我們喜歡受苦,雖然有些日子確實感覺如此。而是因為對這種類型的 App 而言,原生不是奢侈品,而是必要條件。

Electron 稅是真實存在的

先從 Electron 到底是什麼說起:一個假裝成桌面 App 的 Chromium 瀏覽器視窗。這不是批評,而是架構。而對許多應用程式來說,這完全沒問題。

但對遠端桌面呢?你要從另一台電腦串流像素並即時繪製,同時擷取每個輸入事件並送回去。延遲不是錦上添花,而是整個產品。

以下是我們為兩種方式製作原型時量測到的結果:

那 380 MB 不是 Electron 臃腫,而是 Chromium 在做它的工作:V8、GPU 程序、工具程序,還有一整個瀏覽器引擎。這是令人佩服的工程,只是這項工程是為了另一個問題而設計的。

影格比功能更重要

遠端桌面是少數每一個影格都至關重要的 App 類別。掉一個影格,使用者就感覺得到。增加 16 毫秒的輸入延遲,設計師就畫不出流暢的曲線。增加 32 毫秒,玩家就沒辦法玩了。

透過原生繪製,我們直接與 GPU 對話。在 Apple 平台上,VideoToolbox 讓我們能直接存取硬體影像解碼。我們從網路取得一個 H.265 影格,以硬體解碼,然後顯示,這個影格從不經過主記憶體。在現代 Mac 上,從解碼到顯示的管線執行時間不到 4 毫秒。

在 Electron 中,同一個影格要經過 Chromium 的合成器、經過 IPC 橋接、由 Skia 繪製,然後再由作業系統合成一次。每一個躍點都增加延遲,每一個躍點都增加抖動。而抖動,也就是不一致的影格時序,正是讓遠端桌面即使平均 FPS 看起來正常,卻感覺不對勁的原因。

我們選擇原生不是為了當純粹主義者,而是因為我們問題的物理條件要求如此:區域網路上約 8 毫秒的處理時間(其餘是你的顯示器自己的 60 Hz 更新節奏),讓中間沒有容納瀏覽器的空間。

原生與網頁的能力對照表

Electron 與原生的爭論常常陷入憑感覺的泥淖。所以我們寫下 Remio 在每次連線的每一秒都依賴的七項作業系統能力,並直白地問:每一項在各種執行環境下表現如何?

能力原生網頁 / Electron
60 fps 硬體解碼● 直接使用 VideoToolbox / D3D11VA○ 透過 WebCodecs,有抖動
低延遲 USB 音訊● CoreAudio / WASAPI— 沙盒限制,40 毫秒以上
鍵盤輸入監控● CGEvent / SendInput— 失去焦點時被封鎖
Metal / D3D11 GPU 存取● 零複製的紋理○ WebGPU,有複製躍點
本機檔案系統● 完整,由作業系統劃定範圍○ 僅限選擇器,會跳出提示
多視窗、多螢幕● 作業系統原生視窗管理○ BrowserWindow 墊片
背景常駐● LaunchAgent / 服務— 隱藏時被暫停

每一列都是我們今天已推出的功能。網頁欄中的每個「—」,都是我們得自己發明、撰寫文件並為此道歉的變通辦法。走原生,意味著我們把這些能力當作基本元件,而不是用墊片補出來。

平台公民素養

有件事是「寫一次,到處執行」那群人很少談的:使用者分辨得出來。

他們看得出 App 不尊重系統的深色模式轉場,看得出捲動手感有一點不對,看得出 App 不會出現在 Spotlight 裡,或是沒有正確處理 macOS 的視窗管理,也看得出快顯選單看起來像網頁而不是原生選單。

這些不是錦上添花,而是人們勉強容忍的 App 與人們熱愛的 App 之間的差別。在 iOS 上這尤其關鍵:App Store 審核會拒絕感覺不原生的 App,而且理所當然。

有了 SwiftUI,這一切都是免費的:動態字體、VoiceOver 輔助使用、系統觸覺回饋、iPad 上的多工處理、Mac 上的鍵盤快速鍵。我們不是在重新實作平台功能,而是在使用它們。

成本也是真實的

讓我們誠實面對缺點:走原生很昂貴。不是金錢,而是時間與複雜度。

我們維護四套介面程式碼。我們的網路與編解碼層是共用的(訊號用 Go,串流協定用 C++),但你在螢幕上看到的每個像素,都是特定平台的程式碼。一個在 Electron 裡一天就能完成的功能,我們要花三到五天,因為我們得為每個平台調校,總共做四次。

我們曾有過好幾週,一個動畫在 iOS 上運作得很漂亮,在 Android 上壞掉,在 Windows 上卻卡頓。除錯的組合矩陣非常殘酷。

但我們學到的是:最困難的路與正確的路,往往是同一條路。我們的使用者不知道也不在乎我們的架構。他們只知道 Remio 感覺很快、感覺很對,而且不會把他們的電池當早餐吃掉。

我們會再選一次嗎

毫不猶豫。

原生的做法,給了我們 Electron 在這個使用情境下根本無法匹敵的效能。它給了我們在每個平台上都像是原生一部分的 App。它給了我們低於 45 MB,而不是 380 MB 的記憶體用量。它給了我們區域網路上約 8 毫秒的處理時間(其餘是你的顯示器自己的 60 Hz 更新節奏),而以瀏覽器包裝的競爭對手則在 60 毫秒以上。

最重要的是,它給了我們技術護城河。在四個平台上打造原生已經夠困難,大多數競爭對手都不會去做。那份困難,就是我們的優勢。

遠端桌面這個類別,一直由以 Electron 包裝的網頁 App,以及多年來沒有實質更新的老舊原生 App 所主導。這裡有空間容納既現代又原生的東西,一個從零開始、以今日硬體為考量打造的東西。

這就是 Remio。用最困難的方式打造,因為有些事值得做對。

Remio App 圖示

感受原生帶來的差異

Remio 免費、每個平台都是原生,並且端對端加密。記憶體用量低於 45 MB,區域網路上約 8 毫秒的處理時間(其餘是你的顯示器自己的 60 Hz 更新節奏),用最困難的方式打造,只為了讓它感覺對。

免費使用100% 原生端對端加密