0%

【文章內容使用 Gemini 1.5 Pro 自動產生】

早期採用者使用 Flutter Web 應用程式開發的經驗

兩年前,Flutter 宣布為準備使用 Flutter Web 支援進行開發的公司提供 早期採用者計劃。今天,其中一家早期採用者 Supernova, 推出了其設計系統平台,該平台同時使用 Flutter 建立,並為 Flutter 輸出程式碼。

我們認為 Supernova 是 Flutter 在 Web 上實現的體驗的一個很好的例子。我和 Supernova 的 CEO Jiří Třečák 和 CTO Artem Ufimtcev 坐在一起,了解更多關於他們使用 Flutter Web 支援所構建的內容。

您如何向從未聽說過 Supernova 的人描述它?

通常情況下,設計和開發團隊是分開的,從未真正共同合作;這是因為工具是專為其中一個團隊而設計的。因此,我們決定建立一個平台,讓設計師和開發人員可以走到一起,作為一個團隊共同合作。

告訴我更多關於 Supernova 概念背後的靈感。

這個概念源於在設計過程中需要一定程度的自動化,以減輕迭代工作流程的繁瑣。我們今天推出的工具可以自動將設計師建立的設計系統轉換為開發人員可以直接使用的程式碼形式。

Supernova,一個自動化設計流程的平台

您能更詳細地說明使用者在此版本中可以期待的功能嗎?

在此版本中,您可以將 Figma 設計導入 Supernova,管理樣式、素材或其他元件,然後將它們輸出為程式碼。我們建立了一個文件編輯器,讓您可以根據您的設計系統建立文件,最後,還建立了一個自動化引擎,可以在設計更改時自動更新您的文件和程式碼。

Supernova 的文件編輯器

我們平台中的 所有內容 都是使用 Flutter 和 Dart 建立的。其中有很多複雜的邏輯,因此我們的應用程式比大多數應用程式更依賴于 Dart,Flutter 用於視覺化層。除了 Flutter 的 CanvasKit 渲染器之外,我們還擁有自己的 Skia 引擎,用於從 Figma 中的資料模型解析和渲染元件。

為什麼您選擇 Flutter 和 Dart 作為構建內容的不可或缺的一部分?

我們選擇 Flutter 的主要原因是我們能夠按照自己的意愿改造平台。

最初,Supernova 是一個原生 macOS 應用程式,但我們看到了對基於 Web 的解決方案的濃厚興趣。Flutter 承諾使用一個程式碼庫部署到其他平台,這使得它成為一個有吸引力的選擇。我們知道我們最終希望有一個桌面應用程式,因為我們的工具非常依賴於計算,並且與典型的網站相比,它對效能的要求更高。

我們選擇 Flutter 的主要原因是我們能夠按照自己的意愿改造平台。我們知道我們將擁有許多不常見的視覺操作,而不是使用 WebGL 從頭開始構建,Flutter 為我們提供了一種存取低層渲染引擎的方法,以便我們能夠以自己的方式構建它,使用經過良好測試的框架。

儘管我們在早期遇到了一些困難,但由於 Flutter 提供的靈活性,我們認為做出了正確的決定。

Supernova 不僅使用 Flutter 建立,還輸出 Flutter 程式碼

因此,這並不總是順風順水。你能描述一下這些困難以及你在這過程中學到了什麼嗎?

我們在開發 Flutter Web 應用程式時遇到的第一個技術挑戰之一是路由問題。當 Router API 發佈時,我們立即重新設計了路由系統以採用它,這被證明對 Web 應用程式更有效。雖然它提供了更好的整合到瀏覽器歷史記錄中,但它對於巢狀路由來說過於複雜。因此,我們讓在頁面之間共享狀態控制器(在本例中是 MobX 儲存)成為可能,以模擬巢狀路由。這種方法使維護和實作變得更加簡單。

我們學到的最主要的一點是 Dart 和 JavaScript 之間的互操作性對於 Web 應用程式的重要性。如果您還目標是 Flutter 支援的其他平台,那麼使用 Web 特定函式庫會有一些限制,但如果您的主要關注點是 Web 應用程式,那麼它可以節省大量時間。我們了解到,使用 JavaScript 函式庫和嵌入 HTML 可以彌補缺少 Web 支援的 Plugin。例如,我們使用這種方法來建立程式碼預覽 Widget,我們將其作為 HTML 嵌入,並使用 JavaScript 函式庫來進行語法高亮顯示。我認為,在建立 Flutter Web 應用程式時,熟悉 JavaScript 非常有幫助,同時還要了解 Dart/JavaScript 互操作性和 HTML 嵌入的優點和限制。

使用 Dart/JavaScript 互操作性建立的程式碼預覽 Widget

我們還了解到,使用 Flutter 為 Web 應用程式優化效能與為 Flutter 支援的其他平台優化效能並沒有太大區别。在大多數情况下,可以使用兩種方法解决问题:

  1. 將 Widget 重新建立定位到 Widget 樹的尽可能小的部分。使用 MobX 作為主要狀態管理系統意味著重新建立只會影響 UI 中必要的部分。
  2. 使用带有按需子项构建器的列表。我们尽力在任何地方使用 ListView 构建器和 Sliver,以便我们只构建当前在屏幕上可见的部分。

尽管有困难,但 Flutter 是否帮助你克服了某些特定的技术挑战?

透過 Flutter 使用 CanvasKit 渲染器比使用 WebGL 從頭開始撰寫著色器要愉快得多。CanvasKit 為我們提供了更多更高級别的渲染工具,讓我們可以專注於將 Figma 的非標準矢量模型轉換為與我們的 Skia 渲染引擎一起使用的細微差別。

開發人員通常低估了字體管理的挑戰。在本例中,我們必須支援設計師可能使用的所有字體。由於 google_fonts 套件 可以自動從 Google 直接下載字體,Flutter 在這方面幫助很大。

最後,Flutter 還允許我們共享驅動 Supernova 平台和我們建立的用於在 Figma 中運行的獨立 Plugin 的程式碼庫。雖然前端完全不同,但它們共享相同的資料模型和邏輯。

我很好奇,您如何看待使用 Flutter 在 Web 上的體驗與使用更傳統的框架相比?

總體而言,有很多相似之处。Flutter 中的概念,例如它对 UI 的反应式方法、Widget、Flexbox 布局,甚至状态管理,都可以在许多现代 Web 框架中找到。

然而,Flutter 有几方面不同。與其他 Web 框架不同,Flutter 不依賴於 HTML 或 CSS 來撰寫 UI 元件。相反,您必須了解哪些 Widget 存在,以及如何將它們組合在一起以實現與使用 CSS 才能實現的相同 UI。也就是说,Flutter 附带了对 Material 的开箱即用支持,因此如果您习惯了这种外观和感觉,它可以帮助您入门。

我发现 Dart 背后的理念大相径庭。

另一个重要区别是语言。虽然本质上 Dart 与 JavaScript 相似,但我发现 Dart 背后的理念大相径庭。在使用 Dart 编写代码一段时间后,您会发现它专为安全性、可预测性而设计。诸如健全的空安全之类的语言功能在 JavaScript 和与 JavaScript 兼容的语言世界中并不常见,我们发现它们对于使复杂应用程序稳定且可维护至关重要。另一个令我们惊喜的是 Dart 的注解处理和代码生成功能。

由于您是早期采用者,您觉得 Flutter 的 Web 支持有哪些改进?

我們一直從第一個技術預覽版開始就在 Web 上使用 Flutter。在那時,Web 目標不支持使用瀏覽器歷史記錄的導航,效能很差,缺少很多 Widget 和 Plugin,工具和偵錯程式也不存在。

現在有了穩定版本,我們在所有這些領域都看到了進步,並且對我們在 Web 上使用 Flutter 的決定充滿信心。如果您將技術預覽版與 Flutter 2.5 版本進行比較,您會發現效能有了實質性改进,特別是在渲染複雜佈局方面。

也就是说,仍然有提高的空间!特别是,我们期待看到用于检查 CanvasKit 渲染 Widget 的更好的工具,因为今天的浏览器工具不支持它。

最后一个问题,如何开始使用 Supernova?

隨著今天的發佈,Supernova 現在對每個人都可用!要開始使用,請訪問 supernova.io 註冊並提供您 Figma 設計檔案的連結。然後,從那裡開始,基本而言,整個世界都向您敞開了。您可以立即開始為 Flutter(或任何其他框架)輸出程式碼,或獲取圖示的素材以開始撰寫文件。

Supernova 的元件管理器與 Figma 整合在一起

我們很高興看到 Flutter 的早期採用者在 Web 上推出產品。請務必親自查看!如果您正在使用 Flutter 在 Web 上進行開發,請告訴我們。一如既往,您可以在 Twitter 上找到我們 FlutterDev。我們迫不及待地想看看您創作了什麼!

http://creativecommons.org/licenses/by/4.0/

【文章內容使用 Gemini 1.5 Pro 自動產生】

我們對未來版本優先考慮事項,重點關注效能、開發者體驗和 Web 整合

Flutter 的 Web 支援在 2021 年 3 月 達到了穩定里程碑。那麼接下來呢?

根據我們的使用者研究,Web 是超過 10% Flutter 專案的目標。因此,我們現在的重點是提高初始穩定版本的品質,並讓更多人能夠將他們的 Flutter Web 應用程式部署到生產環境。

我們根據 Flutter 季度調查的結果以及我們在問題、面對面討論和社交媒體上聽到的意見來確立這些優先事項。我們剛剛收到了第三季度調查的結果,我們很高興看到這個計畫符合您的回饋。

本文將更詳細地介紹我們的路線圖以及為每個優先事項規劃的工作。某些功能可能跨越幾個季度,而其他功能則需要 [調查 (🔍)] 才能確定解決方案。

WEB 外觀和感覺

Flutter Web 應用程式在 Web 上感覺自然至關重要。這包括諸如捲軸行為、文字功能、啟動畫面、超連結、SEO 和其他 Web 特定的 UI 功能等功能。

RTL 文字

先前,Flutter 為阿拉伯語和希伯來語等從右到左 (RTL) 語言提供了不完整的 Web 支援。雖然框架本身支援 RTL 文字,但 Web 引擎生成了未定義的行為,因為它忽略了 LTR 和 RTL 之間的區別。

最近的穩定版本 (2.5) 加入基本的 RTL 支援,因此所有主要 RTL 語言的使用案例都可以在 Flutter Web 應用程式中支援和使用。大部分與基本支援相關的問題都已解決,我們計畫修復剩餘的問題。

捲軸行為

雖然 Flutter 2 包含 多項改進 以支援桌面瀏覽器上的捲軸行為(例如捲軸列支援),但在瀏覽器或執行 Web 應用程式的作業系統上,捲軸行為有時看起來或感覺不對。

雖然其中一些行為取決於 Flutter 桌面支援的工作,但我們計畫在此路線圖中解決一些特定於捲軸物理和捲軸列的問題(如下所列)。我們也計畫調查觸控板支援。

🔍 研究觸控板支援

應用程式載入 API

某些 Web 應用程式在應用程式載入時或提供自訂體驗時,更喜歡顯示啟動畫面、載入指示器或登陸頁面。目前,沒有簡單的方法可以實作自訂體驗或指示 Flutter Web 應用程式正在載入;當使用 CanvasKit 的較大 bundle 大小渲染時,這變得更加重要。

我們在此的工作重點是為您提供應用程式啟動週期的明確 API,該 API 可用於預載應用程式、控制應用程式的載入週期,以及建立啟動畫面或載入指示器。

無障礙性

無障礙性是我們的首要任務之一;我們的目標是為您提供必要的工具來建立無障礙的 Web 應用程式,這些應用程式可以在最常見的螢幕閱讀器上良好運行。

Flutter 2.2 對無障礙性支援進行了重大改進。從那以後,我們聽到了一些嘗試使用螢幕閱讀器(例如 JAWS)瀏覽 Web 應用程式的最終使用者的意見。

在此路線圖中,我們專注於使用 支援的螢幕閱讀器(例如桌面瀏覽器的快顯通知)時出現的問題。我們還將繼續調查改進整體無障礙性支援的方法。

效能

效能是我們始終優先考慮的領域,以改善使用 Flutter Web 應用程式的使用者體驗。目前,我們的重點是改善捲軸卡頓,並加快 Web 應用程式的初始載入速度。

捲軸卡頓

根據我們最近的季度調查,捲軸卡頓是所報告的頂級效能問題之一。我們的目標是確保 Flutter Web 應用程式可以透過行動設備上的手勢或桌面上的滑鼠/鍵盤順暢捲軸,但這也取決於 Web 應用程式期望使用者捲軸瀏覽的內容類型和數量。

在接下來的幾個月裡,我們將主要專注於改善由於圖片解碼引起的卡頓,我們將繼續調查捲軸效能問題,以找出可以改善的其他使用案例。

捆綁 CanvasKit(離線支援)

目前,在 CanvasKit 中渲染的 Flutter Web 應用程式需要額外的步驟才能作為漸進式 Web 應用程式 (PWA) 在離線狀態下運行。為了讓應用程式在離線模式下能直接作為 PWA 完整運行,並確保應用程式符合嚴格的內容安全政策,我們需要捆綁 CanvasKit 和字體備份。

我們將首先捆綁 CanvasKit,然後捆綁字體,最後加入必要的工具以啟用離線模式。

CanvasKit 下載大小

CanvasKit 比基於 DOM 的方法具有更好的效能,因此它是在桌面瀏覽器上的預設渲染器。但是,下載應用程式所需的時間會影響初始載入效能(以及在 Web 上運行的 Flutter 應用程式的 Lighthouse 分數)。

在此路線圖中,我們將調查減少 CanvasKit 下載大小的方法,以改善初始載入效能。我們希望確保最終使用者的設備或瀏覽器不會處理過大的負載。

🔍 改進 CanvasKit 下載大小

🔍 尋找合理大小的 emoji 字體備份

與 HTML 內容整合

能夠與瀏覽器程式碼整合對於利用 Web 平台的優勢至關重要。Flutter Web 應用程式有兩種方式可以與 HTML 整合:1) 在 Flutter Web 應用程式中使用 HTML 平台視圖,或 2) 將 Flutter 作為內容島嵌入到現有的 Web 應用程式中(有點像 Web 的加到應用程式)。雖然前者現在存在,可能需要改進,但後者將是一個需要進一步設計和開發的新功能。

使用自訂元素嵌入(加到應用程式)

目前,將 Flutter Web 應用程式嵌入到現有的網站/Web 應用程式中的唯一方式是透過 iframe。雖然這對某些使用案例有效,但對於那些逐漸遷移 Web 應用程式以使用 Flutter 來說並不總是理想的解決方案。

在此路線圖中,我們將調查和設計自訂解決方案,讓您能夠嵌入 Flutter Web 應用程式,類似於加到應用程式的行動情境。

🔍 將應用程式渲染到自訂元素中

開發者體驗

Flutter 生態系統包括您在開發中使用的功能,但目前缺乏 Web 支援,例如外掛、除錯、熱重載等等。為了讓您在 Web 上擁有良好的開發者體驗,我們將繼續縮減行動和 Web 之間的差異。

相機外掛

自初始穩定版本發佈以來,相機外掛一直是最常被要求的外掛之一;許多人發現,當將 Flutter 行動應用程式移植到 Web 時,這種差異是一個障礙。

在 Flutter 2.5 穩定版本中,我們提供了一個 外掛 的早期版本,該外掛可以初始化相機、顯示相機預覽並拍照。隨著我們收到回饋,我們將改進此外掛。

感謝您!

雖然這是我們目前 Web 路線圖的概述,但我們的 GitHub 問題列表 仍然是我們正在進行的工作的真實來源。我們可能會根據我們的學習成果和您的意見來加入、延長或延遲功能。

與往常一樣,我們非常重視您的回饋,並感謝您的支援!


Flutter:Web 上的下一步是什麼? 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動產生】

Flutter 2.5 的新功能

您好,歡迎來到 Flutter 2.5!這是一個重大版本,在 Flutter 版本史上擁有第二高的統計數據:來自 252 位貢獻者和 216 位審查者的 4600 個已關閉議題和 3932 個已合併 PR。如果我們回顧過去一年,我們看到 1337 位貢獻者創建了 21,072 個 PR,其中 15,172 個已合併。雖然「Flutter 的新功能」部落格文章重點關注新功能,但我們在 Flutter 中的首要任務始終是確保您擁有所需功能的最高品質水準。

事實上,此版本繼續進行許多重要的效能和工具改進,以追蹤您自己應用程式中的效能問題。同時,還有一些新功能,包括 Android 全螢幕支援、更多 Material You(也稱為 v3)支援、更新的文字編輯以支援可切換的鍵盤快捷鍵、在 Widget Inspector 中更詳細地查看您的 Widget,以及在 Visual Studio Code 專案中新增相依性的新支援,以及在 IntelliJ/Android Studio 中從測試執行中獲取覆蓋率資訊的新支援,以及一個全新的應用程式範本,作為您真實世界 Flutter 應用程式的更好基礎。此版本充滿了令人興奮的最新更新,讓我們開始吧。

效能:iOS 著色器預熱、非同步任務、GC 和訊息傳遞

此版本附帶了一些效能改進。此列表中的第一項是關於從離線訓練執行中建立 Metal 著色器預編譯的 PR (#25644),如我們的基準測試所示,它將最差情況下的影格柵格化時間縮短了 2/3,將第 99 個百分位數的影格縮短了一半。我們繼續努力減少 iOS 卡頓,這是在這條道路上的又一步。然而,著色器預熱只是一個卡頓的來源。先前,處理來自網路、檔案系統、外掛或其他隔離區的非同步事件可能會中斷動畫,這是另一個卡頓的來源。在這個版本中,針對 UI 隔離區事件迴圈的排程策略進行了改進 (#25789),現在影格處理優先於處理其他非同步事件,在我們的測試中消除了這種來源的卡頓。

處理非同步事件結果之前和之後的影格延遲

另一個卡頓的原因是,當垃圾收集器 (GC) 暫停 UI 線程以回收記憶體時。先前,一些圖片的記憶體只會在 Dart VM 執行的 GC 響應中被懶惰地回收。作為先前版本中的解決方案,Flutter 引擎將提示 Dart VM,圖片記憶體可以透過 GC 回收,這在理論上可以導致更及時的記憶體回收。不幸的是,在實際中,這導致了過多的主要 GC,有時記憶體仍然無法快速回收,以避免記憶體有限設備上的低記憶體情況。在此版本中,未使用的圖片的記憶體被渴望地回收 (#26219#82883#84740),大幅減少 GC。

新增渴望回收未使用的巨大圖片記憶體的修復之前和之後的 GC

例如,在我們的其中一項測試中,播放 20 秒的動態 GIF 從需要 400 多個 GC 變為只需要 4 個 GC。較少的 GC 表示涉及圖片出現和消失的動畫會有更少的卡頓,並消耗更少的 CPU 和電源。

Flutter 2.5 中的另一個效能改進是 Dart 和 Objective-C/Swift (iOS) 或 Dart 和 Java/Kotlin (Android) 之間傳送訊息的延遲。作為 調整訊息通道 的一部分,從訊息編解碼器中移除不必要的複製操作會根據訊息大小和設備減少延遲,最多可達 50% (#25988#26331) 。

iOS 訊息延遲,之前和之後

您可以在 Aaron Clarke 的 改善 Flutter 中平台通道效能 部落格文章中閱讀有關此工作的更多詳細資訊。

如果您要定位 iOS,則最後一個效能更新是:在此版本中,在 Apple Silicon M1 Mac 上構建的 Flutter 應用程式可在 ARM iOS 模擬器上原生運行 (#pull/85642)。這表示在 Intel x86_64 指令與 ARM 之間沒有 Rosetta 轉換,這會提高 iOS 應用程式測試期間的效能,並讓您避免一些細微的 Rosetta 問題 (#74970#79641)。這是 Flutter 對 Apple Silicon 的全面支援之路上的又一步。敬請期待更多資訊。

Dart 2.14:格式化、語言功能、預設提供的 pub 和 lint

當然,沒有 Flutter 語言和運行時,Flutter 就不是 Flutter。此版本 Flutter 附帶了 Dart 2.14。Dart 的新版本 附帶了新的格式化,使 級聯 更加清晰,新的 pub 支援忽略檔案,以及新的語言功能,包括傳奇的三重移位運算子的回歸。此外,Dart 2.14 最棒的功能之一是,此版本創建了一組標準的 lint,在新的 Dart 和 Flutter 專案之間共享,這些 lint 預設提供。

`flutter create` 預設提供一個 `analysis_options.yaml` 檔案,其中預先填寫了推薦的 Flutter lint

您不僅在創建新的 Dart 或 Flutter 專案時會獲得這些 lint,而且透過 幾個步驟,您也可以將相同的分析添加到現有的應用程式中。有關這些 lint、新的語言功能等的詳細資訊,請查看 Dart 2.14 的版本發布公告

架構:Android 全螢幕、Material You 和文字編輯快捷鍵

Flutter 2.5 版本包含一些對框架的修復和改進。從 Android 開始,我們修復了一組與全螢幕模式相關的問題,這些問題總共獲得了將近 100 個好評。模式本身的名稱使它成為我們最喜歡的新功能之一:放鬆、粘性、粘性沉浸式和邊到邊。此更改還添加了一種方法,可以在其他模式下監聽全螢幕更改。例如,如果使用者與應用程式互動,當系統 UI 回來時,開發人員現在可以編寫程式碼以返回全螢幕或執行其他操作。

新的 Android 邊到邊模式:常規模式(左)、邊到邊模式(中間)、具有自訂 SystemUIOverlayStyle 的邊到邊模式(右)

在此版本中,我們繼續為新的 Material You(也稱為 v3)規範構建支援,包括對浮動動作按鈕大小和主題的更新 (#86441),以及一個新的 MaterialState.scrolledUnder 狀態,您可以在 PR 中的範例程式碼中看到 (#79999)。

新的 Material You FAB 大小
新的 MaterialState.scrolledUnder 狀態在動作中

在談到捲軸時,另一個改進是添加了捲軸指標通知 (#85221#85499),即使使用者沒有捲軸,它也會提供可捲軸區域的通知。例如,以下顯示了根據 ListView 的底層大小顯示或隱藏捲軸條:

新的捲軸指標通知,讓捲軸條可以在沒有捲軸的情況下自動顯示和隱藏

在這種情况下,您不需要编写任何代码,但如果您想要捕获 ScrollMetricNotification 变化,您可以这样做。特别感谢社区贡献者 xu-baolin,他为这部分工作付出了很多努力,并想出了一个很棒的解决方案。

社区的另一个优秀贡献是在 ScaffoldMessenger 中添加了 Material 横幅支持。您可能还记得 ScaffoldMessenger 来自 Flutter 2.0 版本发布公告,它是一种更健壮的方式,可以在屏幕底部显示 SnackBars 以向用户提供通知。在 Flutter 2.5 中,您现在可以在支架的顶部添加横幅,它会一直保持直到用户将其关闭。

您的应用程序可以通过调用 ScaffoldMessenger 的 showMaterialBanner 方法来获得此行为:

横幅的 Material 指南 指出您的应用程序应该一次只显示一个,因此如果您的应用程序多次调用 showMaterialBanner,ScaffoldMessenger 将维护一个队列,并在前一个横幅关闭时显示每个新的横幅。感谢 Calamity210 为 Flutter 中的 Material 支持添加了这个很棒的功能!

在 Flutter 2.0 及其新的文字编辑功能(如文字选择枢轴点以及在处理键盘事件后能够停止其传播)的基础上,在此版本中,我们添加了使文字编辑键盘快捷键可覆盖 (#85381) 的功能。如果您希望 Ctrl-A 执行自定义操作而不是选择所有文字,您可以执行此操作。DefaultTextEditingShortcuts 类包含 Flutter 在每个平台上支持的每个键盘快捷键的列表。如果您想覆盖任何内容,请使用 Flutter 现有的 Shortcuts Widget 将任何快捷键重新映射到现有的或自定义意图。您可以在想要应用覆盖的 Widget 树中的任何位置放置该 Widget。在 API 参考 中查看一些示例。

外掛:相機、圖片選取器和 Plus 外掛

另一個已進行大量改進的外掛是 相機外掛

  • 3795 [camera] android-rework part 1: 支持 Android 相机功能的基础类
  • 3796 [camera] android-rework part 2: Android 自动对焦功能
  • 3797 [camera] android-rework part 3: Android 与曝光相关的功能
  • 3798 [camera] android-rework part 4: Android 闪光灯和缩放功能
  • 3799 [camera] android-rework part 5: Android FPS 范围、分辨率和传感器方向功能
  • 4039 [camera] android-rework part 6: Android 曝光和对焦点功能
  • 4052 [camera] android-rework part 7: Android 降噪功能
  • 4054 [camera] android-rework part 8: 支持最终实现的模块
  • 4010 [camera] 不要在 iOS 上触发平坦的设备方向
  • 4158 [camera] 修复在 iOS 上设置对焦和曝光点的坐标旋转
  • 4197 [camera] 修复相机预览不始终在方向改变时重建
  • 3992 [camera] 阻止在设置不支持的对焦模式时崩溃
  • 4151 [camera] 引入 camera_web 包

image_picker 外掛 也進行了很多工作,重點關注端到端的相機體驗:

  • 3898 [image_picker] 图片选择器修复相机设备
  • 3956 [image_picker] 更改 Android 上相机捕获的存储位置为内部缓存,以符合新的 Google Play 存储要求
  • 4001 [image_picker] 删除对相机权限的冗余请求
  • 4019 [image_picker] 修复相机作为来源时的旋转

这项工作改进了相机和 image_picker 外掛在 Android 上的功能和稳健性。此外,您会注意到 camera 外掛 的早期版本已提供,其中包含 Web 支援 (#4151)。此預覽版為在 Web 上查看相机预览、拍照、使用闪光灯和缩放控件提供了基本支持。它目前不是 认可外掛,因此您需要 显式地添加它 才能在您的 Web 應用程式中使用。

最初的 Android 相機重寫工作是由 acoutts 貢獻的。相机和 image_picker 的工作由 Baseflow 完成,Baseflow 是一家专门从事 Flutter 的咨询公司,以 他们在 pub.dev 上的自己的包 而闻名。camera_web 的工作主要由 Very Good Ventures 完成,这是一家位于美国的 Flutter 咨询公司。非常感谢大家对 Flutter 社区的贡献!

Flutter 社区组织的另一个宝贵社区贡献是 “Plus” 外掛。在这个 Flutter 版本中,Flutter 团队的每个对应外掛现在都带有类似于 battery 的推荐:

此外,由于这些外掛不再积极维护,因此不再将其标记为 Flutter 收藏外掛。如果您还没这样做,我们建议您迁移到以下外掛的 Plus 版本:

Flutter DevTools:效能、Widget Inspector 和潤色

此版本的 Flutter 附帶了一些對 Flutter DevTools 的改進。最重要的是,DevTools 中添加了支援,以利用引擎更新 (#26205#26233#26237#26970#27074#26617)。這些更新中的一組使 Flutter 能夠更好地將追蹤事件與特定影格關聯起來,這有助於開發者確定影格超支的原因。您可以在 DevTools 影格圖表中看到這一點,該圖表已重建為「實時」的;影格會在您的應用程式中渲染時在該圖表中填充。從該圖表中選擇一個影格將導航到該影格的時間軸事件:

Flutter 引擎現在還會在時間軸中識別著色器編譯事件。Flutter DevTools 使用這些事件來幫助您診斷應用程式中的著色器編譯卡頓。

DevTools 檢測到由於著色器編譯而丟失的影格

有了這個新功能,DevTools 可以檢測到您是否由於著色器編譯而丟失了影格,以便您可以修復問題。要像第一次一樣運行您的應用程式(在您的著色器快取填充之前,就像它對任何使用者一樣),請使用 flutter run--purge-persistent-cache 標誌。這將清除快取,以確保您正在重現使用者在「首次運行」或「重新打開」(iOS)體驗時看到的環境。此功能仍在開發中,因此請 提交議題 報告您遇到的問題,或報告我們可以做出的任何改進,以幫助除錯著色器編譯卡頓。

此外,當您在應用程式中追蹤 CPU 效能問題時,您可能會被來自 Dart 和 Flutter 函式庫以及引擎的原生程式碼的分析資料淹沒。如果您想關閉這些資料中的任何資料以專注於您自己的程式碼,可以使用新的 CPU 分析工具 (#3236),它讓您能夠隱藏來自這些來源的分析資訊。

對於您沒有過濾掉的任何類別,它們現在都已使用顏色編碼 (#3310#3324),這樣您就可以輕鬆地看到 CPU 影格圖表中的哪些部分來自系統的哪些部分。

彩色影格圖表,用於識別應用程式與原生、Dart 與 Flutter 程式碼活動

效能不是您要除錯的唯一事項。此版本的 DevTools 附帶了對 Widget Inspector 的更新,它讓您可以在 Widget 上懸停以評估物件、查看屬性、Widget 狀態等。

而且,當您選取一個 Widget 時,它會自動填充到新的 Widget Inspector 主控台中,您可以在其中探索 Widget 的屬性。

在斷點處暫停時,您也可以從主控台中評估運算式。

除了新功能外,Widget Inspector 也經過了改頭換面。為了使 DevTools 成為一個更有用的目的地,用於了解和除錯您的 Flutter 應用程式,我們與位於芬蘭的創意科技公司 Codemate 合作,進行了一些更新。

Flutter DevTools 經過潤色的 UX,更易於使用

在此螢幕截圖中,您可以看到以下更改:

  • 更好地傳達除錯切換按鈕的作用 - 這些按鈕有新的圖示、以任務為導向的標籤,以及描述其作用和何時使用它們的豐富工具提示。每個工具提示都會進一步鏈接到功能的詳細文件。
  • 更容易扫描和定位感兴趣的 widget - Flutter 框架中常用的 widget 现在会在 Inspector 左侧的 widget 树视图中显示图标。它们还会根据其类别进行颜色编码。例如,布局 widget 将以蓝色显示,而内容 widget 将以绿色显示。此外,每个 Text widget 现在都会显示其内容的预览。
  • 将布局资源浏览器和 widget 树的颜色方案对齐 - 现在更容易从布局资源浏览器和 widget 树中识别同一个 widget。例如,以下屏幕截图中的“Column” widget 在布局资源浏览器中以蓝色背景显示,而在 widget 树视图中则以蓝色图标显示。

我們很樂意 聽取您有關這些更新產生的任何問題的意見回饋,以及我們可以做的任何其他改進,以確保 DevTools 運作良好。這些重點只是開始。若要完整列出此版本 Flutter 中 DevTools 的新增功能,請查看版本發布公告:

IntelliJ/Android Studio:整合測試、測試覆蓋率和圖示預覽

Flutter 的 IntelliJ/Android Studio 外掛在此版本中也進行了一些改進,首先是能夠運行整合測試 (#5459)。整合測試是整個應用程式測試,在設備上運行,位於 integration_test 目錄中,並使用與 Widget 單元測試中相同的 testWidgets() 功能。

在 IntelliJ/Android Studio 中整合測試您的 Flutter 應用程式

若要將整合測試添加到您的專案,請 按照 flutter.dev 上的說明操作。若要將測試與 IntelliJ 或 Android Studio 連接,請添加一個運行配置,該配置啟動整合測試,並連接一個設備供測試使用。運行配置允許您運行測試,包括設置斷點、單步執行等。

此外,Flutter 的最新 IJ/AS 外掛讓您能夠查看單元測試和整合測試運行的覆蓋率資訊。您可以從工具列按鈕中存取它,該按鈕位於「除錯」按鈕旁邊:

覆蓋率資訊使用編輯器邊緣的紅色和綠色條顯示。在此示例中,第 9-13 行已測試,但第 3 和 4 行未測試。

最新版本還包括能夠預覽從 pub.dev 建立的基於 TrueType 字體檔案的包中使用的圖示 (#5504#5595#5677#5704),就像 Material 和 Cupertino 圖示支持預覽一樣。

IntelliJ/Android Studio 中的圖示預覽

若要啟用圖示預覽,您需要告訴外掛您正在使用哪些包。外掛設定/偏好設定頁面中有一個新的文字欄位:

請注意,這適用於在類別中定義為靜態常數的圖示,如螢幕截圖中的範例程式碼所示。它不適用於運算式,例如 LineIcons.addressBook()LineIcons.values['code']。如果您是未支援此功能的圖示包的作者,請創建一個 議題

有關 Flutter 的 IntelliJ/Android Studio 外掛的更多更新資訊,請查看版本發布公告:

Visual Studio Code:相依性、修復全部和測試執行器

Flutter 的 Visual Studio Code 外掛也在此版本中得到了改進,首先是兩個新的命令「Dart: Add Dependency」和「Dart: Add Dev Dependency」 (#3306#3474)。

在 Visual Studio Code 中添加 Dart 相依性

這些命令提供了類似於 Jeroen Meijer 的 Pubspec Assist 外掛 一段時間以來提供的功能。這些新的命令預設提供,並提供一個從 pub.dev 定期獲取的包類型以過濾的列表。

您可能還會對「Fix All」命令感興趣 (#3445#3469),該命令可用于 Dart 檔案,並可以修復與 dart fix 相同的所有問題,以一步驟修復當前打開的檔案。

使用 Flutter Fix 規則修復程式碼中所有已知的問題

這也可以通過將 source.fixAll 添加到 editor.codeActionsOnSave VS Code 设置来设置为在保存时运行。

或者,如果您想尝试预览功能,可以启用 dart.previewVsCodeTestRunner 设置,并查看使用新的 Visual Studio Code 測試執行器運行的 Dart 和 Flutter

【文章內容使用 Gemini 1.5 Pro 自動產生】

柵格執行緒效能優化技巧

最近,我坐下來調整 FlutterFolio 的效能,這個應用程式是作為 Flutter Engage 的設計展示而建立的。只改變了一處,我就讓 FlutterFolio 變得快了很多。

不過,首先,我必須找出要改什麼。這篇文章就是關於這個搜尋的。

FlutterFolio 是一個功能完整的應用程式,從設計到實作,花了 6 個星期 (!) 時間完成,支援行動裝置、桌面和網頁。開發團隊顯然必須做一些妥協 - 沒有任何批評。專案的範圍和非常短的時程迫使他們這樣做。

事實上,這是一個絕佳的機會,因為這個應用程式比我想到的所有範例應用程式都更加「真實」。

而且,效能優化在真實應用程式上比在合成問題上更好解釋。

第 1 步:效能分析

任何優化的第一步是什麼?量測。知道一個應用程式看起來很慢是不夠的。你需要更精確一點。兩個原因:

  1. 量測可以將我們引導到最糟糕的肇事者。任何應用程式的每個部分都可以變得更快、更有效率。但是,你必須從某個地方開始。效能分析讓我們看到哪些部分做得還可以,哪些部分做得不好。然後,我們可以專注於做得不好的部分,並在有限的時間內取得更大的進展。
  2. 我們可以比較前後。有時,程式碼變更看起來像是個好主意,但實際上,它並沒有產生顯著的差異。有了基準(之前)就表示我們可以量化變更的影響。我們可以將之前與之後進行比較。

應用程式的效能分析很困難。我在 2019 年寫了一篇關於這方面的 長篇文章。所以,讓我們從簡單的開始。我們在 profile 模式下執行應用程式,開啟效能覆蓋圖,並使用應用程式,同時觀察效能覆蓋圖的圖表。

我們立即看到柵格執行緒正在努力。

這尤其發生在捲軸瀏覽應用程式的主頁時。您應該始終優先考慮使用者花費大部分時間的部分或效能問題對使用者特別明顯的部分。換句話說,如果您有兩個效能問題,其中一個發生在開始畫面,另一個埋藏在設定頁面中,請先修復開始畫面。

讓我們看看柵格執行緒在做什麼。

旁白:UI 執行緒 vs. 柵格執行緒

其實,讓我們先澄清柵格執行緒做什麼。

所有 Flutter 應用程式都在至少兩個並行執行緒上運行:UI 執行緒和柵格執行緒。UI 執行緒是您建立 Widget 和應用程式邏輯運行的地方。(您可以建立隔離區,這表示您可以在其他執行緒上運行邏輯,但為了簡單起見,我們將忽略這一點。)柵格執行緒是 Flutter 用來 柵格化 您的應用程式的方式。它接收來自 UI 執行緒的指令,並將它們轉換成可以傳送到圖形卡的東西。

為了更具體,讓我們看看一個 build 函數:

1
2
3
Widget build(BuildContext context) {
return Image.asset('dash.png');
}

上面的程式碼在 UI 執行緒上運行。Flutter 架構找出要放置 Widget 的位置、要給它什麼尺寸,等等 - 仍然在 UI 執行緒上。

然後,在 Flutter 知道有關畫面的所有資訊之後,它就會交給柵格執行緒。柵格執行緒會取得 dash.png 中的位元組,調整圖片的大小(如果需要),然後應用不透明度、混合模式、模糊等等,直到它取得最終的像素。然後,柵格執行緒將得到的資訊傳送到圖形卡,因此也傳送到螢幕。

第 2 步:深入時間軸

好的,回到 FlutterFolio。開啟 Flutter DevTools 讓我們可以更仔細地查看時間軸。

效能 標籤中,您可以看到 UI 執行緒(淡藍色條)做得很好,而柵格執行緒(深藍色和紅色條)在每個畫面中花費了令人驚訝的時間,特別是在捲軸向下瀏覽主頁時。因此,問題不在於低效的 build 方法或商業邏輯。問題是要求柵格執行緒做太多事。

事實上,每個畫面 都花費了很長時間在柵格執行緒上,這告訴了我們一些事情。它表示我們要求柵格執行緒做一些工作 一次又一次 - 它不是偶爾做一次的事情。

讓我們選擇一個畫面,看看 時間軸事件 面板。

時間軸的頂部,帶有淺灰色背景,是 UI 執行緒。再次,您可以看到 UI 執行緒不是問題。

在 UI 執行緒下方,您可以看到柵格執行緒上的事件,從 GPURasterizer:Draw 開始。不幸的是,這就是事情變得有點模糊的地方。有許多對異國情調名稱方法的呼叫,例如 TransformLayer::Preroll、OpacityLayer::Preroll、PhysicalShapeLayer::Paint 等等。沒有關於這些方法中發生了什麼事情的詳細資訊,而且這些不是大多數 Flutter 開發人員認識的名稱。

它們是來自 Flutter 引擎的 C++ 方法。如果您想,您可以 搜尋 這些方法名稱,閱讀程式碼和註解,看看幕後發生了什麼。有時,這可以讓您對柵格執行緒在做什麼有更多直覺。但是,這種研究對於找出效能問題來說並不嚴格需要。(我直到相對最近才做過這種研究,但仍然能夠優化許多應用程式的效能。)

然後,有一個標記為 SkCanvas::Flush 的長事件。它花了 18 毫秒,遠遠超過合理範圍。不幸的是,該事件也沒有任何詳細資訊,因此我們需要做一點偵探工作。

SkCanvas 中的 Sk 代表 Skia,這是 Flutter 用於在其堆疊最底層進行渲染的圖形引擎。SkCanvas 是一個低階 C++ 類別,類似於 Flutter 自己 的 Canvas(如果您使用 CustomPaint,您可能已經熟悉它)。您應用程式的所有像素、線條、漸變 - 所有 UI - 都會經過 SkCanvas。而且,SkCanvas::Flush 是這個類別在收集到所有需要的資訊後進行大部分工作的地方。文件 指出 Flush 方法「解決所有未決的 GPU 作業」。

讓我們回顧一下我們從效能時間軸中学到的东西:

  • 柵格執行緒是主要問題。UI 執行緒做得相對良好。
  • 在捲軸時,柵格執行緒在 每個畫面 中花費了很長時間。一些昂貴的柵格化工作一直在進行。
  • SkCanvas::Flush 花費了很長時間,這表示 Skia 正在做很多功課。

我們 不知道 那個功課是什麼。讓我們回顧一下程式碼。

第 3 步:閱讀程式碼

有了知識,讓我們看看原始碼。如果程式碼不熟悉(就像我對 FlutterFolio 的情況一樣),最好從 profile 模式切換到 debug 模式,並使用 Flutter Inspector 跳轉到相關 Widget 的原始碼。

FlutterFolio 的主頁,至少在行動裝置上,似乎基本上是一個由 BookCoverWidgets 填充的垂直 PageView。查看 BookCoverWidget,您可以看到它本質上是一個 各種 Widget 的堆疊,從底部的大型圖片開始,繼續是一些動畫覆蓋層、主要文字內容,最後是頂部的滑鼠懸停覆蓋層。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
child: Stack(fit: StackFit.expand, children: [
/// /////////////////////////////<br>
/// 背景圖片
// 當我們滑鼠懸停時,動畫縮放
AnimatedScale(
duration: Times.slow,
begin: 1,
end: isClickable ? 1.1 : 1,
child: BookCoverImage(widget.data),
),

/// 黑色覆蓋層,在滑鼠懸停時淡出
AnimatedContainer(duration: Times.slow,
color: Colors.black.withOpacity(overlayOpacity)),

/// 當處於大型模式時,顯示一些漸變,
/// 應該位於文字元素下方
if (widget.largeMode) ...[
FadeInLeft(
duration: Times.slower,
child: _SideGradient(Colors.black),
),
FadeInUp(child: _BottomGradientLg(Colors.black))
] else ...[
FadeInUp(child: _BottomGradientSm(Colors.black)),
],

/// 放在文字內容下方,點擊時取消焦點。
GestureDetector(behavior: HitTestBehavior.translucent,
onTap: InputUtils.unFocus),

/// BookContent,顯示大型封面或小型封面
Align(
alignment: widget.topTitle ? Alignment.topLeft : Alignment.bottomLeft,
// 根據我們處於哪種模式,對填補進行動畫轉場
child: AnimatedContainer(
duration: Times.slow,
padding: EdgeInsets.all(widget.largeMode ? Insets.offset : Insets.sm),
child: (widget.largeMode)
? LargeBookCover(widget.data)
: SmallBookCover(widget.data, topTitle: widget.topTitle),
),
),

/// 滑鼠懸停效果
if (isClickable) ...[
Positioned.fill(child: FadeIn(child: RoundedBorder(color: theme.accent1, ignorePointer: false))),
],
]),

現在,請記住:您正在尋找的是在每個畫面中都會發生的東西(也就是說,它始終存在),並且可能對於 Skia 渲染器來說繪製起來很昂貴(圖片、模糊、混合等等)。

第 4 步:深入探討

現在,您需要深入探討以找出可能存在问题的 Widget。一種方法是暫時從應用程式中移除各種 Widget,看看這對效能有什麼影響。

請記住,堆疊的第一個子元素是背景,每個後續子元素都是之前 Widget 上方的圖層。所以,第一個子元素是背景圖片,由 BookCoverImage 表示。您可以移除它,但主頁看起來會像這樣:

這就失去了整個頁面的意義。仔細查看 BookCoverImage,您可以看到它只是一個簡單的 Image 函式包裝器。除了一个值得注意的例外(本文稍後會提到)之外,這裡沒有什麼可以改進的地方。

繼續,有這段程式碼:

1
2
3
/// 黑色覆蓋層,在滑鼠懸停時淡出
AnimatedContainer(duration: Times.slow,
color: Colors.black.withOpacity(overlayOpacity)),

這是一個用透明黑色覆蓋整個圖片的 Widget。overlayOpacity 預設情況下為 0(而且大部分時間都是如此),所以這個圖層是完全透明的。嗯。讓我們將它移除,並再次在 profile 模式下執行應用程式。

有趣!柵格執行緒仍然承擔了大量負載,但效能有了顯著改善。

我決定為 FlutterFolio 實作一個更強大的效能分析工具,以便我可以證明改進是真實的,而不仅仅是偶然。這個變更讓我柵格化所花費的 CPU 時間整體減少了 20%,潛在的卡頓減少了 50%。

總的來說,這是一個巨大的變化,因為移除了一個大部分時間都無效的單個 Widget。

修復 很簡單:

1
2
3
4
/// 黑色覆蓋層,在滑鼠懸停時淡出
if (overlayOpacity > 0)
AnimatedContainer(duration: Times.slow,
color: Colors.black.withOpacity(overlayOpacity)),

現在,您只在它具有非零不透明度(也就是說,它至少部分可見)時才會加入透明覆蓋層。您避免了(非常常見的!)情況,即建立並柵格化一個完全透明的圖層,但它没有任何效果。

就這樣,應用程式變得更加流暢,也更節省電量。

注意:為什麼你需要這樣做?Flutter 不應該足夠聰明地為我們執行這個優化嗎?請閱讀 此處 的議題,了解為什麼它做不到。為什麼透明不透明度在第一時間會變慢?這超出了本文的範圍,但它與堆疊中更上方的 BackdropFilter Widget 相關,它會與下方每個 Widget 相互作用。

本文的主要目的不是教你關於這個特定效能陷阱的知識。你可能永遠不會再看到它。我的目標是教你如何一般性地優化柵格執行緒效能。

第 5 步:概括

在繼續處理一個完全不同的問題之前,通常最好查看專案中的其他地方,看看是否有類似的問題。我們的應用程式中是否有其他地方使用了大面積覆蓋層?你能避免它們嗎?

在這種情況下,接下來的幾行會建立在您捲軸時會淡入的大型漸變:

1
2
3
4
5
6
7
8
9
10
11
/// 當處於大型模式時,顯示一些漸變,
/// 應該位於文字元素下方
if (widget.largeMode) ...[
FadeInLeft(
duration: Times.slower,
child: _SideGradient(Colors.black),
),
FadeInUp(child: _BottomGradientLg(Colors.black))
] else ...[
FadeInUp(child: _BottomGradientSm(Colors.black)),
],

而且,果然,移除這些動畫的、幾乎佔據全螢幕的漸變會顯著改善捲軸效能。不幸的是,在這種情況下,解決方案不像之前的範例那样简单。這些漸變不是無形的。它們會在使用者到達特定封面時開始淡入。移除它们 确实 会造成視覺上的差異。

一個想法是稍微延遲淡入,這樣動畫只会在使用者降落在特定 BookCover 上時才會開始。這樣一來,您就可以減輕柵格執行緒的負載,而使用者在捲軸時,也希望可以避免一些潛在的卡頓。

但是,這涉及到對應用程式的動態設計進行修改,因此需要與更廣泛的團隊討論。許多效能優化都會屬於這類。效能優化通常是折衷的過程。

重複步驟 2-5 直到滿意

到目前為止,我們只查看了一種類型的問題。總是會有更多問題。

以下是一個關於下一步的想法:應用程式的圖片資產是否太大?請記住,柵格執行緒負責取得圖片位元組、解碼它們、調整大小、應用濾鏡等等。如果它將一個 20 MB 的高解析度圖片載入到螢幕上的一個小頭像圖片中,那麼您就是在浪費資源。

當您的應用程式在 debug 模式下執行時,您可以使用 Flutter Inspector 來 反轉過大的圖片

這將會反轉顏色並翻轉應用程式中對於實際用途來說太大的所有圖片。然后,您可以仔细检查应用程序,并注意不自然的变化。

debug 模式還會在每次遇到這種圖片時報告一個錯誤,例如:

[錯誤] 圖片 assets/images/empty-background.png 的顯示大小為 411×706,但解碼大小為 2560×1600,額外使用了 19818KB。

不過,這裡的解決方案並不直接。在行動裝置上,您不需要 2560×1600 的圖片,但在桌面上,您可能需要。請記住,FlutterFolio 是一個在所有 Flutter 目標上執行的應用程式,包括桌面。如有疑問,請 閱讀 API 文件

結語

如您所見,優化效能是一门艺术和科学。強大的基準測試以及對框架及其內建 Widget 的深刻理解都有助於這一點。

最终,熟能生巧。優化足夠多的應用程式,你就會變得更好。

祝您狩獵愉快。


柵格執行緒效能優化技巧 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

https://creativecommons.org/licenses/by-nc/4.0/

【文章內容使用 Gemini 1.5 Pro 自動產生】

寫一個好的程式碼範例

撰寫好的程式碼範例很難。讓我說明一下,製作一個範例來演示 API 的用法或展示 UI 慣用語可能很快且容易。難度來自於你需要添加到你的儲存庫中的額外部分,以保持程式碼範例新鮮並吸引你的開發人員。

首先,你需要在你的程式碼中添加一個好的 README,從一個摘要開始,說明開發人員為什麼應該投入時間和精力來研究和理解這個範例。接著,提供使用說明和連結到問題追蹤器,開發人員可以在其中提出錯誤並要求澄清,如果遇到令人困惑的部分。

接下來是程式碼本身。程式碼是否遵循佈局和使用的慣例?這可能像程式碼放在哪裡一樣簡單,也可能像 linter 等適當的設定一樣複雜。我喜歡已經採用了強大的程式碼格式、佈局和 linting 規則集標準的程式設計社群語言。這些標準使程式碼的外觀保持一致,從而更容易導航。若要強制執行 Dart 和 Flutter 的程式碼格式,你可以將以下命令添加到你的 CI 管線,如果格式不正確,則會使構建失敗:

1
$ dart format --output none --set-exit-if-changed .

在程式碼格式化後,下一步是強制執行一套好的 lints。對於 Dart,我強烈建議研究 lints 套件,對於 Flutter,我同樣建議研究 flutter_lints 套件。若要確保 lints 在 CI 管線中通過,請添加以下命令:

1
$ dart analyze

測試。哦,這麼多測試。單元測試、整合測試,對於 Flutter,我們還有 Widget 測試。測試對於範例來說非常棒,因為測試傳達了如何使用程式碼片段的意圖。測試還與前面提到的 CI 管線結合使用,可以使程式碼庫保持常青。想要了解更多關於 Flutter 測試功能的資訊,可以到 如何測試 Flutter 應用程式 codelab 了解更多。Dart 和 Flutter 的 CI 命令分別為:

1
2
$ flutter test   # 適用於 Flutter 專案
$ dart test # 適用於純 Dart 專案

如果你的程式碼儲存庫託管在 GitHub 上,那麼我建議你如果擁有 Dart 專案,請使用 Dart Setup 操作,如果擁有 Flutter 專案,請使用 Flutter Action。為了獲得額外積分,請考慮添加 Very Good Coverage 操作以保持高測試覆蓋率。既然你已經做到了這一步,你可能應該使用 工作流程狀態徽章 來宣傳你的 CI 狀態。

希望以上添加的項目清單能夠指導你,讓你的程式碼範例對你的目標受眾更有價值。


Writing a good code sample was originally published in Flutter on Medium, where people are continuing the conversation by highlighting and responding to this story.

【文章內容使用 Gemini 1.5 Pro 自動產生】

GSoC ‘21:為 Flutter 建立桌面樣本

Google Summer of Code (GSoC) 是一個由 Google 贊助的開源夏季程式。在此程式期間,學生在開源組織的指導下進行專案。

這是 Dart 團隊第二次作為 GSoC 的指導組織參與。不幸的是,由於指導老師的可用性問題,今年的 點子列表 上沒有任何 Flutter 專案。由於這是我的第二次參與 GSoC,我聯絡了去年的指導老師 Brett,看看是否有機會參與一個專案。在與他以及組織管理員討論後,我找到了一個可以參與的專案。

此部落格展示了我為專案所做的工作。查看今年 Dart 下的所有專案。所有產生的原始碼都位於 GitHub 儲存庫中,您可以透過查看個別的拉取請求 (PR) 來查看工作的進展。

專案詳情:Flutter 桌面工具

今年,在 Flutter Engage 中,Flutter 桌面支援的 beta 快照已包含在穩定版本中。這導致需要為 samples 儲存庫建立一個桌面樣本,該樣本日後可以在桌面應用程式商店(如 macOS App StoreMicrosoft StoreLinux Snap Store)上發佈。在與 Brett 和團隊討論後,我們決定建立一個桌面樣本,它也將是一個工具,可以幫助開發人員管理其專案的 linting 規則。請查看 GitHub 上的 linting_tool

應用程式的螢幕截圖

該應用程式使用 Dart 團隊從 dart-lang/linter 儲存庫中託管的 linting 規則,並且該應用程式可以執行以下操作:

顯示可用的 Dart linting 規則列表。

您可以從 API 獲取 linting 規則列表。該應用程式使用 flutter_markdown 套件來格式化詳細資訊和程式碼片段。

相關 PR:#856

將規則儲存到不同的設定檔。

您可以為不同類型的專案建立不同的規則設定檔。該應用程式使用 hive 資料庫來維持持久性儲存。

已儲存設定檔的列表

相關 PR:#860

修改和匯出設定檔。

設定檔可以根據您的喜好進行修改,然後匯出到 Flutter 專案中,作為 analysis_options.yaml 檔案。 套件 yamljson2yamlfile_selector 用於完成此操作。

相關 PR:#874#869

顯示預設設定檔。

您可以查看 effective_dartpedanticlintsflutter_lints 使用的預設設定檔列表。

相關 PR:**#871**

若要查看專案在 GSoC 期間的進展,請查看 這組 PR

我還想為專案添加一些其他內容。兩項主要内容是:能夠從現有的 analysis_options.yaml 檔案載入設定檔,以及能夠在規則列表中搜索特定規則。我還正在製作一個新的影片系列,將涵蓋如何部署 Flutter 桌面應用程式。影片系列製作完成後,我會更新此部落格,也會在 Twitter 帳戶 上分享。

經驗

我這次的 Google Summer of Code (GSoC) 經驗比去年更好。(查看 Learn testing with the new Flutter sample 以閱讀更多有關我去年參與的專案的資訊。)我認為今年的經驗更好,因為專案更具技術性,涵蓋了更多概念,並且與我之前從未參與過的內容相關。這次我也認識團隊中的更多人。我認為 GSoC 將會是我永遠不會忘記的人生的一部分。

指導

Brett 一直都在,幫助我解決所有問題和阻礙。當我需要更多時間來處理其他承諾(如學校、其他工作和家庭相關事項)時,他非常理解。我認為最棒的是,他給了我完全的自由去探索事物,如果我認為某些內容對專案來說是寶貴的補充,我就可以調整專案的範圍。這次我做了一件很酷的事情,就是在每次每周會議結束時,我問他一些與專案無直接關聯的一般軟體工程相關的問題,例如「Google 如何處理估計?」和「你對遠端工作怎麼看?」。這幫助我學習了一些與程式設計無關的軟體產業知識。我強烈建議你在實習或 GSoC 類專案期間嘗試一下。而且,是的,他仍然是我合作過的最酷的人。

學習

學習是開源軟體最棒的部分。我這次做的事情,很多都無法透過 Google 搜尋找到答案。我閱讀了大量的程式碼,找出不同的套件如何執行相同的事情,以及它們之間的差異模式。我還學會了如何在大量可用的套件中為專案找到合適的套件。由於這個類別對我來說是全新的,因此這次的經驗幫助我在短時間內學到很多東西。

挑戰

我這次遇到了一些挑戰,從中學到了一些經驗。第一個挑戰與時間表有關。今年,GSoC 的時間表縮短了一半,這迫使我們重新排序優先順序,跳過許多可能會成為專案中寶貴補充的功能。其他挑戰是:處理一個全新的類別,以及在一個不太流行的類別中,找到關於如何執行某些事情的良好資源。

結語

在過去的一年中,我與 Flutter 和 samples 儲存庫合作,獲得了難以置信的經驗。我想要感謝 Brett、Flutter 團隊以及 GSoC 團隊。

關於作者:Abdullah 是一位來自印度浦那的電腦工程應屆畢業生。他在過去 4 年中一直在開發行動應用程式,並且喜歡與行動應用程式相關的內容合作。您可以在 TwitterLinkedInGitHub 上聯繫他.


GSoC ‘21:為 Flutter 建立桌面樣本 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

http://creativecommons.org/licenses/by/4.0/

【文章內容使用 Gemini 1.5 Pro 自動產生】

Flutter 熱重載:原理與效能提升

Flutter 的熱重載功能非常棒,按下鍵盤上的 “r” 鍵,就能在幾秒鐘內看到變更的效果。在終端機(或 IDE 底部)您可能會看到類似的訊息:Reloaded 1 of 553 libraries in 297ms。但熱重載背後的運作原理是什麼?Dart 和 Flutter 團隊又是如何讓它變得更快呢?

熱重載概述

Flutter 的熱重載主要包含以下五個步驟:

  1. flutter_tools 掃描需要的檔案變更。 它會檢查每個必要的檔案,並確認它的最後修改時間戳記是否比之前的編譯時間戳記更新。
  2. flutter_tools 指示正在執行的 Dart 編譯器重新編譯應用程式,並告知它哪些檔案已變更。 Dart 編譯器開始重新編譯。
  3. flutter_tools 將更新的檔案傳送到設備。 這包括任何變更的資產和新編譯的 _delta kernel 檔案_(編譯的輸出,Dart VM 可以理解的檔案)。
  4. flutter_tools 要求設備上的 Dart VM 中的所有隔離區重新載入它們的來源(讀取變更的 delta kernel 檔案並執行其魔力)。
  5. flutter_tools 指示設備上的應用程式重新組合 - 重新建立螢幕上的所有 Widget、重新載入資產、重新執行佈局、重新繪製等等。

在我的開發者機器上,目標設備為 Linux(也就是本地執行的桌面應用程式),在只更新 `lib/main.dart` 檔案的時間戳記後,在一個新建立的 `flutter create` 應用程式上執行第一次熱重載,我得到了以下時序(從 `flutter run -v` 中提取):

  1. 掃描檔案需要 ~13 ms。
  2. 重新編譯需要 ~67 ms。
  3. 將檔案傳送到設備需要 ~2 ms。
  4. Dart VM 重新載入來源需要 ~96 ms。
  5. 重新組合需要 ~114 ms。

如果我改用一個更大的應用程式(並變更其他檔案),我可能會得到以下時序:

  1. 掃描檔案需要 ~12 ms。
  2. 重新編譯需要 ~386 ms。
  3. 將檔案傳送到設備需要 ~2 ms。
  4. Dart VM 重新載入來源需要 ~171 ms。
  5. 重新組合需要 ~229 ms。

在兩種情況下,以下步驟佔用了最多的時間:

  • 重新編譯
  • 重新載入
  • 重新組合

要使熱重載速度更快,我們必須讓這三個步驟中的其中一個或多個步驟變得更快。 這裡我將重點關注第一部分:將變更的原始碼檔案重新編譯成 Dart VM 可以使用的內容。

重新編譯

從邏輯上來說,如果我作為使用者變更了一個檔案,例如 `foo.dart`,我可能會期望重新編譯看起來像這樣:

  1. 編譯器在記憶體中保留了舊的狀態。
  2. 編譯器被告知 foo.dart 已變更。
  3. 編譯器丟棄它對 foo.dart 的內部狀態。
  4. 編譯器重新編譯 foo.dart
  5. 完成。

這將非常棒。這意味著,無論我變更了哪個檔案,我只會重新編譯那個檔案,並且(可能)重新編譯速度很快。

不幸的是,重新編譯通常不會像這樣運作。以下是有兩個例子說明了為什麼重新編譯可能沒有那麼簡單:

  • foo.dart 曾經包含類別 Foo,該類別在各處使用。變更後的檔案不包含此類別(也許它被手動重新命名),並且每個使用此類別的檔案都應該出現編譯錯誤。
  • foo.dart 曾經定義了一個字段為 var z = 42。另一個檔案使用此字段:var z2 = z * 2。Dart 類型推斷確定 z 是一個整數,而 z2 是一個整數,因為 z 是一個整數。現在,字段變更為 var z = 42.2。這次 Dart 類型推斷將會確定該字段是一個雙精度數,但如果不重新編譯另一個函式庫,那么 z2 將仍然(錯誤地)被標記為一個整數。

因此,Dart 中的重新編譯長期以來都是像這樣執行的:

  1. 編譯器在記憶體中保留了舊的狀態。
  2. 編譯器被告知 foo.dart 已變更。
  3. 編譯器丟棄它對 foo.dart 的內部狀態。
  4. 編譯器檢查哪些檔案匯入或匯出 foo.dart,並將這些檔案也丟棄。
  5. 編譯器檢查哪些檔案匯入或匯出步驟 4 中的檔案,並將這些檔案也丟棄。
  6. 不斷重複:丟棄所有傳遞性匯入者和匯出者。
  7. 編譯器重新編譯所有(現在)“遺漏”的函式庫。
  8. 完成。

這聽起來可能很糟糕,但在許多情況下並非如此。雖然變更您自己的自訂 Widget 組合可能會導致重新編譯您編寫的所有程式碼,但它不會導致重新編譯 Flutter 框架本身,例如,因為 Flutter 框架不匯入或匯出您的函式庫。另一方面,如果您變更了 Flutter 框架的核心檔案,那麼您最終將重新編譯(幾乎)所有內容。

回顧一下為什麼只重新編譯單一變更檔案不起作用的原因的不完整列表,我們可能會看到一個模式:它不起作用是因為您進行了 _全局_ 變更 - 影響其他函式庫的變更。但是,如果您只變更了註釋呢?或者在您的建構方法中加入了另一個除錯列印?或者修正了實用方法中的一處錯誤?這些變更不是全局的,我們應該可以做得更好!

改進

對於非全局變更(不能影響其他函式庫編譯的變更),我們實際上可以只重新編譯變更的函式庫,並且仍然保持語義。主要問題是確定何時變更是全局的,何時不是(並且要快速完成)。幸運的是,這可以通過增量步驟來完成:我們不必立即(或根本不必)使其完美。

第一步可能是比較檔案的舊版本和新版本,同時忽略兩個版本的檔案中的註釋。如果,用這種方式比較時,兩個版本的檔案相同,那麼我們認為沒有全局變更,然後我們繼續重新編譯單一變更的檔案,而不是傳遞性匯入匯出圖。這種技術並不完美。例如,它仍然會在修正實用方法中的錯誤時觸發所有傳遞性匯入者和匯出者的重新編譯。但它允許您在只重新編譯一個檔案的同時修正註釋中的拼寫錯誤。

這裡快速說明一下:如果我們只變更了註釋,為什麼我們必須重新編譯?主要原因是堆疊追蹤。在內部,一些節點(表示您的程式碼)包含 _偏移量_ - 有關它們在檔案中的位置的資訊。如果此資訊過時,您的堆疊追蹤將包含無效的資訊。例如,它可能會聲稱某件事發生在第 42 行,而實際上並非如此。

若要達到可以實際上修正實用方法中的錯誤,同時仍然只重新編譯該檔案的狀態,我們必須在檢查全局變更時忽略另一件事:函數體。我們將再次比較變更檔案的之前版本和之後版本,這次同時忽略註釋和函數體。如果它們相同,我們將只重新編譯該檔案。

現在,我們實際上可以進行一些有用的變更,而無需重新編譯超出您變更的檔案的內容。您可以加入、移除或以其他方式變更註釋。您可以在您的建構方法中加入(和移除)除錯列印。您甚至可以修正實用方法中的錯誤。

好消息!

事實證明,這些對重新編譯的改進已經實現。如果您使用的是 Flutter 2.2,您可能已經注意到它了。如果沒有,您可能現在就會注意到。老實說,對於小型應用程式,您可能不會注意到多少速度提升,但對於大型應用程式,您應該會注意到。

我已經製作了一些非全局變更的示例,以評估其效果。

對於 [Veggie Seasons](https://github.com/flutter/samples/tree/master/veggieseasons) 示例應用程式(一個相對較小的應用程式):

  • 變更 lib/main.dart 沒有改善。它之前編譯一個檔案,現在仍然編譯一個檔案。
  • 變更 lib/data/veggie.dart 會帶來 30% 的改善。在我的電腦上,實際的編譯時間從 100 多毫秒下降到不到 20 毫秒(它以前編譯 18 個檔案,現在只編譯 1 個檔案)。這自然遠遠超過 30%,但由於重新編譯只是三個時間消耗中的其中一個(另外兩個是重新載入和重新組合),所以總體變更大約是 30%。

對於 [Flutter Gallery](https://github.com/flutter/gallery)(一個相對較大的應用程式):

  • 變更 lib/main.dart 會產生非常小的改善(它編譯 1 個檔案而不是 2 個檔案)。
  • 變更 lib/layout/adaptive.dart 會導致重新載入時間幾乎減半。僅重新編譯時間從近 400 毫秒下降到 40 毫秒(重新編譯 1 個檔案而不是 47 個檔案)。

您應該期望在實際情況中,Flutter 2.2 中的熱重載平均速度比 Flutter 2.0 中快 30%。從這個角度來看,這個變更為 Flutter 開發人員節省了超過一年時間,每 5 天就少等一次熱重載。

注意事項

我們對熱重載的變更並不總是意味著編譯器做的事情更少。例如,如果您加入或移除了一個方法,編譯器不會做的事情更少。如果您變更了字段的初始化器,編譯器不會做的事情更少。如果您變更了類別層級,編譯器不會做的事情更少。如果您變更了函數體(編譯器通常應該做的事情更少),由於混合和 FFI 方面的技術問題,編譯器可能仍然需要做同樣多的工作。

此外,當我們討論比較檔案時,我們跳過了幾個技術細節。首先,我們不能忽略 _所有_ 註釋:我們需要保留 [語言版本選擇器](https://dart.dev/guides/language/evolution#per-library-language-version-selection) `@dart version 標記` ,因為它具有語義意義。其次,我們不能忽略所有函數體,因為混合和 FFI 方面存在實作上的問題。

結語

Flutter 2.0 中的熱重載速度很快,但 Flutter 2.2 中的速度更快。平均而言,Flutter 2.2 中的熱重載速度比 Flutter 2.0 快約 30%,這為 Flutter 開發人員節省了超過一年時間,每 5 天就少等一次熱重載。

如果您尚未更新(甚至尚未嘗試使用 Flutter),現在可能是參觀 [flutter.dev](http://flutter.dev) 並嘗試一下的好時機。


Flutter 熱重載 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動產生】

我們如何才能讓 Flutter 變得更好?- 2021 年第二季使用者調查結果

撰寫者: JaYoungMariam

Flutter 團隊每季都會進行使用者調查,以聽取 Flutter 使用者的意見並為未來做出規劃。最近一次的調查是在 5 月份進行的,這是我們第 13 次使用者調查,收集了超過 7,000 份回覆。我們請 Flutter 開發人員評估他們對 Flutter 各部分的滿意度,對於那些不完全滿意的開發人員,我們請他們說明原因。團隊不僅總結了多選題調查回覆,還閱讀了調查中留下的開放式意見。總共有 3,403 則意見。我們再次回來分享我們從您那裡學到的東西。

滿意度

總體而言,92% 的受訪者表示他們對 Flutter 持正面滿意度(39% 有點滿意,53% 非常滿意)。Flutter 的子系統滿意度得分不同,範圍從 72% 到 93%,如下圖所示。雖然我們很高興分享我們正在維持高滿意度,但我們也關注可以改進的特定領域。

開放式問題

調查中有兩個開放式問題:

  • 「您為什麼對 Flutter 不滿意?請分享您的原因:」
  • 「您想對 Flutter 團隊說些什麼嗎? 」

我們從第一個問題收集了 847 則意見,從第二個問題收集了 2,556 則意見。這些意見首先使用機器學習演算法被劃分為各種主題(例如文件、生態系統、Web 支援等)。然後,每個主題會被分配給在該領域工作的團隊進行分析。

當然,團隊不能一次承擔所有工作並解決所有問題。然而,每個團隊都試圖從意見中提取見解並優先處理想法,仔細評估需要達成的效果和付出的努力。在以下章節中,我們將分享一些我們得到的結論的範例。

範例 1:關於一般的開發者體驗

在高層面上,透過閱讀書面回饋對我們有三個幫助:

  1. 確認現有問題
  2. 找出新的關注領域和想法
  3. 察覺到對額外資訊和證據的需求

首先,我們確認了在許多其他來源(例如 GitHub Issues 標籤、Reddit 和 Stack Overflow)中經常觀察到的痛點。這些範例包括但不限於記憶體消耗和升級技能的範例。我們非常了解這些挑戰,並在每個小組內為它們設定優先順序。同時,我們希望再次宣傳現有的資源,例如 使用記憶體檢視學習中心

其次,我們發現了一些令我們驚訝的主題。例如,有幾則意見表達了對擴展到桌面和 Web 的擔憂。這些意見要求我們專注於讓 iOS 和 Android 變得更好,不要分散我們的注意力。我們理解這種擔憂,並且會注意這個問題。我們也會更好地傳達我們如何努力成為一種強大的行動技術,以及隨著時間的推移如何擴展對其他平台的支援。

最後,我們希望獲得有關調查中報告的一些問題的更多資訊,特別是那些與效能和文件相關的問題。如果您想與 Flutter 貢獻者進行對話,並為您的問題添加更多細節,請考慮將它們發佈到 GitHub Issues 標籤。這個標籤會定期由團隊進行分類。在您發佈時,請遵循 為所有問題提交錯誤 中的規則。(當然,在 Stack Overflow 或 Discord 等支援論壇中回答「如何」類別的問題效果會更好!請查看 社群 頁面以獲取更多資訊。)

範例 2:關於 Flutter 的 Web 支援

團隊發現,圍繞缺乏穩定版本 Web 支援而提出的不滿意回饋,在 Flutter 2.0 於 3 月份 穩定發佈 Web 支援 之後已得到緩解。我們為 Flutter Web 支援的穩定版本新增的許多功能都是基於去年的 調查,在那裡我們詢問了您可能需要的 Web 應用程式功能。

2020 年第三季調查 中的 Web 特定問題是基於我們去年第二季調查中收到的開放式回饋。您的意見幫助我們確定了我們應該優先考慮的領域,以作為 Flutter Web 支援的初始穩定版本。

在 2020 年,15% 的意見是關於效能和卡頓的,因此我們優先改進 HTML 渲染器的效能,以及穩定 CanvasKit 渲染器。另外 15% 的意見是關於我們的 Plugin 差距,以及對 GoogleMaps、Firestore 等等的要求,因此我們確保了在 Web 穩定版本中支援了大多數 Google 自有 Plugin,包括 GoogleMaps、Firebase_analytics、cloud_firestore 等等。有幾則意見是關於捲軸和文字渲染的。雖然我們在這些領域仍然需要做更多工作,但我們能夠為支援 桌面外觀尺寸上的捲軸 以及支援 富文本功能(例如文字欄位中的多行文字選取)奠定堅實的基礎。

本季,我們得知使用者認為 Flutter 的 Web 支援仍然需要改進。Web 應用程式的「外觀和感覺」是最常被提及的不滿意 Flutter Web 支援的原因。我們也被要求提供更多 Web 特定 Widget、直觀的響應式 Widget 等等。搜尋引擎優化 (SEO) 的要求也被注意到。其他的不滿意原因是從右到左 (RTL) 文字支援、程式碼大小、路由和除錯。

我們計劃在下一次使用者調查中詢問一些這些領域,因為我們正在規劃下一個版本。對於其他的問題,我們已經取得了進展,例如新增 RTL 文字支援,透過我們目前的 UXR 研究 了解路由問題,以及研究降低程式碼大小的方法。

接下來要做什麼?

每季調查計劃是讓我們更好地了解您的需求的結構化方式,但它不是我們聽取 Flutter 開發者意見的唯一管道。如果您有需要後續處理的緊急問題,請在 GitHub 上提交它們。

Flutter UXR 團隊將繼續透過 flutter.dev 上的公告、Flutter IDE Plugin 或 Twitter @FlutterDev 每季進行調查。請繼續提供您的想法,因為團隊正在尋找重要問題的答案。您也可以透過 註冊即將到來的 UXR 研究 來參與其他研究。

再次感謝所有參與本次調查並提供寶貴意見的各位。我們的目標是建立您喜歡的產品,我們感謝您撥冗幫助我們。


我們如何才能讓 Flutter 變得更好?- 2021 年第二季使用者調查結果 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動產生】

將 Flutter 加入現有的 iOS 和 Android 程式碼庫

無論您是為小型代理商工作,為眾多客戶建立行動應用程式,還是為擁有數百個內部應用程式的龐大企業開發,支援多個程式碼庫都可能很困難且昂貴(如果真的要做到)。我們驚訝地發現,一個常見的場景是,一些公司有數十甚至數百個應用程式是用於一個主要的行動平台,但沒有為另一個平台開發。這會讓他們許多使用者(通常是員工)無法像其他人一樣高效地執行相同的任務,從而可能導致價值流失。

Flutter 是一個可以從單一程式碼庫支援多個平台的 UI 架構,它可以幫助解決這些問題。雖然在理想情況下,您可以使用 Flutter 從頭開始建立應用程式,但當公司已經投入時間和金錢開發一個或多個平台的應用程式時,這個計劃通常行不通。

在本教程中,我們將透過學習稱為 add-to-app 的功能,探討一種更務實的方法,即隨著時間推移逐步將現有的 iOS 或 Android 應用程式轉換為 Flutter。雖然此功能並不能立即為您提供完整的 Flutter 應用程式,但它允許您在轉換過程中保持與當前程式碼庫的功能一致性和穩定性,而不是需要完整的重構,而重構可能會充滿意想不到的問題和「陷阱」。

在我們逐步完成本教程時,我們將從一個簡單的基本案例應用程式開始,與您期望從「你好,世界!」範例中得到的結果類似,適用於 iOS 和 Android,而不是加入已經存在的實際應用程式的複雜性。之後,我們將建立將為每個平台新增新視圖(用 Flutter 編寫)所需的基礎架構。在實作 add-to-app 時,預期您已經具備一些 Flutter 的經驗,但本教程盡可能保持簡單,以便專注於如何將 Flutter 加入非 Flutter 應用程式。透過這種方式,您最終將擁有必要的詞彙,並知道在您準備好自己嘗試時該去哪裡尋找。

讓我們開始進行吧!

Flutter 安裝

如果您尚未在電腦上安裝 Flutter SDK,現在正是時候。請按照 此連結 中的說明來設定您的機器。我會在這裡等您:) 如果你想在沒有跟著做的情況下繼續閱讀,那也是完全可以的。

都準備好了嗎?太棒了!

因此,將 Flutter 加入現有應用程式的第一步是,毫不意外地,建立一個要加入應用程式的 Flutter 元件。從命令列介面,導航到您想要儲存 Flutter 模組的目錄,並使用 Flutter CLI 工具執行以下命令:

1
flutter create --template module add_to_app_flutter_module

這會建立一個基本 Flutter 應用程式並將其放置在一個名為 add_to_app_flutter_module 的新目錄中,不過您可以隨意為模組命名 - 本教程假設您使用的是 add_to_app_flutter_module 名稱。

iOS 安裝

通常,在實作 add-to-app 功能時,您已經有現有的 iOS 或 Android 應用程式。在本教程中,您將從頭開始建立新的應用程式,以便專注於實作基礎。您將從 Xcode 中建立一個全新的 iOS 應用程式開始。如果您沒有使用 Mac 電腦或為 iOS 開發,請隨時跳到 Android 部分,或繼續閱讀以了解此過程。我只會有點失望您沒有閱讀全部內容。啟動 Xcode。當出現第一個選項螢幕時,選擇 App 並點擊 Next

在下一頁,適當地填寫文字欄位。在本教程中,使用 Storyboard 介面、Swift 作為語言,以及 UIKit App Delegate 作為生命週期。

此時,系統會提示您在電腦上的某個位置建立一個新的目錄,並將您的應用程式放置到其中。在本教程中,將新的 iOS 專案資料夾儲存在與您之前建立的 Flutter 模組相同的父目錄中。建立該目錄後,您將進入 Xcode 專案螢幕,資料夾結構類似於此:

回到命令列,導航到您在上一步中建立的新的 iOS 專案目錄,並使用以下命令初始化 CocoaPods:

1
pod init

初始化 Podfile 後,從 CLI 打開它,並將其內容替換為以下內容(請記住將目標名稱從 Add-to-App 更改為反映您自己的應用程式名稱,並將 flutter_application_path 更改為與 Flutter 模組的路徑匹配,如果您使用的是不同的值):

更新 Podfile 後,儲存檔案並執行以下命令將 Flutter 模組連結到新的 iOS 專案:

1
pod install

從 iOS 應用程式打開預設 Flutter 頁面

現在 Flutter 模組和 iOS 專案已連結,是時候學習如何從 iOS ViewController 導航到行動應用程式中的 Flutter 頁面了。首先打開 AppDelegate.swift 檔案,將類別設定為繼承 FlutterAppDelegate 而不是預設的 UIAppDelegate。您還想定義一個新的 FlutterEngine 物件,它是用於橋接原生 iOS 應用程式和 Flutter 類別的 Flutter 環境容器:

要完成 simpleFlutterAppDelegate 類別,請建立一個新的應用程式函數,在啟動 iOS 應用程式時註冊 FlutterEngine:

在 AppDelegate 類別中,您只需要執行這些操作(暫時如此)。要啟動預設的 Flutter 螢幕,請轉到專案的 ViewController.swift 檔案(不過,在更完善的應用程式中,您可以使用任何 ViewController)。新增一個名為 showFlutter() 的函數,該函數會擷取 FlutterEngine,並使用預設的 Flutter 輸入點建立一個新的 FlutterViewController 物件,然後顯示它:

接下來,您需要一種方法來呼叫該函數。為簡單起見,直接在 Swift 程式碼中為螢幕定義一個按鈕,不過您可以使用任何適合您的其他導航模式或技術。在本教程中,建立一個新的 UIButton,將其置於螢幕中間,將新的 showFlutter() 函數指定給按鈕的動作,然後將其附加到視圖,所有這些都來自 viewDidLoad() 生命週期函數:

現在嘗試執行應用程式。如果一切按預期進行(祈禱吧!),那麼您應該能夠啟動 iOS 應用程式,點擊 顯示 Flutter! 按鈕,然後觀看新的 Flutter 螢幕彈出:

Android 安裝

現在您已經使 iOS/Flutter 組合正常運作,是時候嘗試設定 Android 應用程式了。就像您對 iOS 專案所做的那樣,建立一個新的 Android 專案,並在第一個螢幕上選擇 基本活動 模板。

在下一頁,為名稱和套件名稱填寫適當的資訊。為了使一切與此範例的 iOS 版本保持一致,請將您的專案儲存在與 Flutter 模組和 iOS 應用程式相同的父目錄下。您還需要確保在本教程中將專案的語言設定為 Kotlin,儘管相同 add-to-app 邏輯適用於使用 Java 編寫的 Android 應用程式。準備好後,點擊藍色的 完成 按鈕。

現在您有了基礎 Android 專案,新增您之前建立的 Flutter 模組。您可以透過轉到 檔案 -> 新建 -> 新模組… 來完成此操作。

從那裡,轉到新視窗底部的 匯入 Flutter 模組 選項,加入 Flutter 模組位置,然後點擊藍色的 完成 按鈕。

接下來,打開 settings.gradle 檔案,並將其內容替換為以下內容:

這裡主要的部分是綁定並將 include_flutter.groovy 檔案加入到專案中。完成後,轉到專案級別的 build.gradle 檔案(位於 Android 專案的根目錄中),新增一個 allprojects 區塊,以便您可以編譯應用程式(這可能在以後不需要,但我遇到了 Android Studio Arctic Fox 的問題,因此我在這裡寫下來,以防有人用得上:))

最後,打開 應用程式級別build.gradle 檔案(位於 your_project_name/app 目錄中),並在 dependencies 節點中新增一行,以將 Flutter 模組作為來源加入到 Android 專案中:

此時,Android 應用程式應該可以編譯和建構,而且您會在 IDE 中看到 Flutter 模組。

從 Android 應用程式打開預設 Flutter 頁面

現在 Android 應用程式的安裝過程已完成,您需要準備好使用新的 Flutter 元件來啟動應用程式。幸運的是,現在安裝已完成,因此這相對容易。首先打開 AndroidManifest.xml 檔案。Flutter add-to-app 使用自訂 FlutterActivity 在 Android 中顯示 Flutter 內容,因此您需要確保在清單中宣告 FlutterActivity,方法是在 application 標籤內新增以下區塊:

接下來,打開 MainActivity.kt 檔案,並將應用程式 FloatingActionButton 顯示的 Snackbar 替換為以下程式碼,以啟動新的 FlutterActivity。

現在,當您點擊 FloatingActionButton 時,您應該會看到 Flutter 頁面直接在您的應用程式中彈出!

除了能夠啟動完整的活動螢幕(類似於您之前在 iOS 中所做的),Android 的額外好處是能夠將 Flutter 元件作為 Fragment 或自訂視圖的一部分來啟動。雖然這兩種技術超出了本教程的範圍,但您可以在官方文件找到如何使用 FlutterFragmentFlutterView

開啟其他 Flutter 螢幕

雖然能夠直接從原生 iOS 或 Android 應用程式開啟 Flutter 螢幕很棒,但考慮到使用 add-to-app 的整個想法是您可以慢慢地實作各種 Flutter 功能,因此 它實際上並沒有達到您想要的程度。要做到這一點,您很可能需要多個輸入點和多個 Flutter 元件。幸運的是,有一種方法可以在原生應用程式中建立多個 Flutter 實例,不過值得注意的是,在撰寫本文時,此功能 處於實驗階段。雖然表面層面的東西很有可能保持不變,但也可能語法或其他細節會在日後發生變化。

首先,透過打開 flutter 模組/lib 目錄中的 main.dart,更新 Flutter 模組中的程式碼以支援第二個螢幕。在 main.dart 中,透過在 main() 的宣告下方新增以下幾行,宣告您的第一個新的輸入點。請注意,此程式碼片段包含一個註釋,將此方法指定為應用程式中的新輸入點。

MySecondAppScreen 只會返回一個具有綠色主題和新標題的新 MaterialApp,以便您可以區分它和 main() 輸入點。

接下來,您可能會注意到您需要為 MySecondaryHomePage 建立另一個程式碼塊。這是一個新的 StatefulWidget,它包含 Flutter 螢幕的狀態物件。

最後,建立新的狀態物件。在本範例中,Widget 會顯示 AppBar 和 Text Widget。

您現在有兩個不同的 Flutter 螢幕可以從原生應用程式啟動。接下來,您將在現有的 Android 範例應用程式中實作新螢幕。

Android 中的多個 Flutter 輸入點

此擴展的 add-to-app 功能透過建立 FlutterEngine 類別的多個實例(與用於顯示單一預設 Flutter 螢幕的工具相同)並將它們儲存在 FlutterEngineGroup 中來執行,然後在需要時呼叫適當的引擎。首先,建立一個新的應用程式類別來初始化 FlutterEngineGroup。

接下來,建立一個輔助類別,在本例中名為 EngineBindings,它會接收輸入點的名稱,並將其懶加載到 FlutterEngineGroup 中,以便可以在原生應用程式中顯示它。這是懶加載的,因為您需要確保應用程式已完全載入,然後再開始建立 FlutterEngine,否則您可能會遇到意想不到的(且難以除錯)競爭條件。

您需要加入的最後一個類別會擴展您在上一節 Android 中使用的 FlutterActivity。建立一個名為 SingleFlutterActivity.kt 的新的 Kotlin 類別檔案,它會擴展 FlutterActivity:

在此檔案中,透過傳入新的輸入點名稱(在本例中為 “secondary”)來初始化 EngineBindings,以匹配您在 Dart 檔案中新增的輸入點的名稱,並為擷取適當的引擎撰寫一個輔助方法:

要完成 FlutterActivity,請使用新建立的引擎從 onCreate() 顯示 Flutter 螢幕。

接下來,您只需要再做幾件事就能完成此範例應用程式。回到 MainActivity,轉到原本用於啟動主要 Flutter 螢幕的 FloatingActionButton,並更改 Intent,使其啟動新的 SingleFlutterActivity 類別。

最後,打開 AndroidManifest.xml,將新的應用程式類別與 application 標籤關聯起來,並新增 SingleFlutterActivity 的活動標籤。

您現在應該能夠執行應用程式,點擊 FloatingActionButton,並看到新的螢幕,而不是預設的 Flutter Widget。

iOS 中的多個 Flutter 輸入點

您還在嗎?太好了!

接下來,您將在 iOS 範例應用程式中新增對多個輸入點的支援。回到 Xcode,打開 AppDelegate 類別,並將所有程式碼替換為這個簡化的版本,它會建立一個單一的 FlutterEngineGroup,可以在整個應用程式中訪問。

類似於您在 Android 應用程式中所做的,建立一個名為 SingleFlutterViewController.swift 的新檔案,它會擴展標準的 FlutterViewController。此類別會接收一個包含您想要使用的輸入點名稱的字串,然後建立並顯示一個新的 FlutterEngine。

最後,返回基礎 ViewController 類別,並更新 showFlutter() 函數,以便它使用指定的輸入點顯示新的 SingleFlutterViewController 類別。

更新完程式碼後,更新 Podfile 以使用 Flutter 模組的最新版本,因為您已將新的輸入點和螢幕程式碼新增到 main.dart 中。完成後,您應該能夠建構和執行 iOS 應用程式,以查看您的原生程式碼切換到新的 Flutter 元件。

總結

嘿,您做到了!恭喜!

在本教程中,您學習了如何將 Flutter 逐步新增到現有的 Android 和 iOS 應用程式中,以建立一個統一且更易於維護的程式碼庫。您已經了解了如何在兩個平台上從單一輸入點新增 Flutter,以及如何建立多個輸入點。如果您有興趣了解更多資訊,我在下方包含了一個連結,連結到 Flutter 的官方文件頁面,該頁面提供了有關 add-to-app 的更多細節資訊,以及討論 平台通道 的連結,平台通道允許您在 Flutter 和原生級別程式碼之間來回通訊。最後,請查看討論 Plugin 和如何撰寫自己的 Plugin 的連結,這些連結可以讓使用 Flutter 為多個平台開發變得更容易,無論是對您還是對開發人員社群。

我們期待看到您的跨平台應用程式實際運作!


將 Flutter 加入現有的 iOS 和 Android 程式碼庫 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動產生】

Google I/O 聚焦:字节跳动 Flutter 实战

Note: This article was originally written in Chinese by the ByteDance team and translated into English.

Flutter, a technology that ByteDance has been utilizing and contributing to for several years now, was recently highlighted on the main stage of Google I/O. Developed and open-sourced by Google, the multi-platform framework for front-end UI development has garnered over 120,000 stars on GitHub.

At Google I/O, Zoey Fan, one of Flutter's product managers, talked about how the framework was adopted at ByteDance.
More than 70 apps by ByteDance use Flutter as their multi-platform solution.
Flutter has reduced development time by 33%, as compared to developing separate apps for Android and iOS.

Today, there are over 500 Flutter developers at ByteDance, with over 200 actively developing with the framework. These developers utilize Flutter not only for mobile apps but also experiment with it on web, desktop, and embedded platforms.

Beyond this, ByteDance has conducted fundamental work throughout the organization and made significant contributions to the Flutter project by submitting dozens of pull requests (PRs).

How did ByteDance make Flutter truly work for them?

The story of Flutter at ByteDance began two years ago.

At the time, the ByteDance front-end engineering team noticed that many teams within the company needed to develop for multiple platforms, but lacked a tool to achieve high-efficiency, high-performance, multi-platform development.

When Google open-sourced Flutter, the ByteDance team discovered that with Flutter, they only needed to develop the app once to support platforms such as Android, iOS, and web. Also, because Flutter has its own rendering engine, they could achieve more consistent performance across platforms.

With Flutter, the Android, iOS, and web versions of an app automatically stay in sync. There is no need to design and program the UI separately for each platform, eliminating a significant portion of redundant work.

To support business development more efficiently, the ByteDance team performed fundamental work on the framework itself, such as optimizing performance, creating app frameworks, containerizing, and supporting “add to app.” They also improved Flutter performance tools, including improvements to the Frames Per Second (FPS) info in the Frame chart and the timeline events chart. Both of these charts are part of the Performance View in Flutter DevTools.

When adopting Flutter, the ByteDance team encountered some unique challenges. For example, Flutter must be added to the app installation package, increasing the size of the app downloaded by users. Additionally, Flutter uses the Dart programming language, which is larger in size than native code, further increasing the package size.

The ByteDance team started a special plan to optimize the package size by compressing the iOS data section and stripping out the Skia library and other libraries (such as BoringSSL, ICU, text rendering, and libwebp). They analyzed Flutter Dart code against iOS native code and found that to implement the same business feature, the Dart code generated more machine code instructions. To close the gap, they reduced alignment of instructions, removed debugging trap instructions, dropped redundant initialization of stores with null, removed RawInstruction headers with bare instructions mode, compressed StackMaps, removed CodeSourceMap, and so on.

Individually, each of these optimizations reduced the package size by 0.2 to 4 MB and significantly reduced the total package size when combined. The ByteDance team shared their experience with Google engineers, and many improvements made their way to the Flutter open source project for the benefit of the larger community.

However, when ByteDance released their first Flutter app, new issues emerged. Users asked: ‘Why is the UI so janky when I scroll in the app?’

When the ByteDance team looked into the issue, they saw that when a FlutterView extended a TextureView, the UI was noticeably jankier than when it extended SurfaceView. However, in the official Timeline tool, the UI thread time and GPU thread time for each rendered frame are about the same, with TextureView pulling a bit ahead occasionally.

The metrics contradicted the real-world user experience, which puzzled the team.

At first, the team used the Timeline tool to troubleshoot the issue, to no avail. After digging into the tool’s source code, they discovered the root cause of the issue.

SurfaceView had better performance than TextureView. Because SurfaceView had its own surface, and rendering was performed in its own OpenGL context, it could interact with SurfaceFlinger independently and took full advantage of triple-buffering. On the other hand, TextureView was a regular view that depended on the surface of its host window for rendering. That meant the rendering wasn’t performed immediately after the UI and GPU threads had finished their work but needed to wait for the native main thread and renderThread before the view could interact with SurfaceFlinger. That was a much longer rendering pipeline than that of SurfaceView.

These findings not only helped the team eliminate the jank but led to 10 PRs being submitted to the Flutter open source project. With this fundamental work done, Flutter eventually became the go-to framework for multi-platform app development at ByteDance. Soon, the ByteDance team’s work with Flutter will be available to external developers using their mobile development framework, veMARS, benefiting the entire developer community.

From experiment to production: How ByteDance put Flutter into use

It wasn’t exactly smooth sailing for ByteDance to put Flutter into real-world use.

At first, the ByteDance team chose a mature product and planned to re-implement the app’s video playback feature with Flutter.

The feature, originally written in native code for Android and iOS, wasn’t straightforward to rewrite with Flutter. After six months, the team came to the conclusion that it would be difficult to make all the live data compatible and challenging to update the existing business logic.

The team decided that it wasn’t productive to update the existing features of a mature product with the new framework. Flutter’s strengths would be better used in a brand-new app. The team lead said, “In a mature product, everything is already well built with native Android or iOS technology. There isn’t much gain in re-implementing the features with Flutter only to make minor improvements. It also increases the package size since the Flutter engine is included in the package. In new products or new scenarios, however, Flutter can greatly increase our productivity.”

With this changed mindset, the team turned their focus to new business areas such as education.

One of their education apps in China helps students learn the order of strokes of Chinese characters; the team wanted to add a stroke tracking feature.

To implement it, the team took inspiration from some open-source projects and decided to use SVG paths to represent strokes. The paths would then be adjusted and positioned to compose the characters:

They defined the skeleton of each stroke to guide the movement of the virtual brush pen, so the pen moves just like it would in calligraphy:

Based on the defined order of the skeletons, a circle with a certain radius is drawn along each skeleton, and together these circles form the stroke. After that, the team added keyframes to ensure that the frame rate of the animation is high enough to avoid jank.

That is how they created the smooth tracking effect, as shown in the following GIF:

The feature, built with Flutter, now supports over 9,000 Chinese characters, including most of the commonly used characters. Compared to developing with native code, Flutter saved time and resources.

Today, many apps by ByteDance employ a hybrid approach to development, combining the strengths of Flutter and other technologies, with newer apps leaning towards pure Flutter. For apps such as Xigua Video, TikTok Volcano, and Open Language, Flutter increased the productivity of the teams by about 33%.

ByteDancers Embrace the Latest Technology

Even now, the Flutter team at ByteDance continues to explore the latest technologies. According to the team lead, “We have in our team many tech enthusiasts with global vision, and will continue to explore global technology developments and discuss the implementation of technology. We have close connections and collaborations with many tech companies. We have quarterly sync meetings with Google, for instance, to exchange progress, thoughts, needs, and ideas from both sides.”

One day, the maintainer of the Dart open source project on GitHub came to the ByteDance team lead with the following remarks, “Someone from your team submitted more than a dozen PRs to Dart and they’re all very good and well thought out.”

The Dart open source project maintainer was talking about Frank. Frank is a passionate open-source contributor and just got his bachelor’s degree three years ago. His journey in the open source world first started during his first year of university in 2015. One of the projects he created and open-sourced on GitHub had over 700 stars. “It has had hundreds of downloads per year, and many game developers use it to create demos”, Frank said.

After graduation, Frank joined the Flutter team at ByteDance and became one of the most active open-source contributors on the team, contributing a number of PRs to Dart and Flutter. Frank remembers that when he was working on the package size issues, he proactively followed up on a relevant issue on the Dart GitHub project and noticed that the Specializer component could use some further tuning. He created a patch with his improvements to the Dart compiler middleware and submitted it to the project. The patch wasn’t accepted initially because of the large number of code blocks affected and a few minor concerns. He modified the patch seven times before it was accepted, and it was merged into the code base a week later.

There are many other open-source enthusiasts like Frank in the Flutter team at ByteDance.

The ByteDance team summarized this passionate attitude toward innovation with the following words:

“There are indeed many people in the industry who prefer mature technology, but it takes time for every technology to mature, and there will always be people like us who love to stay on the cutting edge.”

This is especially true for something as novel as Flutter. There needs to be some daring people who take the first steps. At ByteDance, the Flutter engineering team, as well as the engineering teams that they support, actively try and embrace new technologies. Doing this benefited ByteDance tremendously and greatly increased our productivity.

ByteDance has always wanted to be part of things that could push the industry forward, and Flutter is likely to be one of those things.


Google I/O spotlight: Flutter in action at ByteDance was originally published in Flutter on Medium, where people are continuing the conversation by highlighting and responding to this story.