如何縮減接近 50% 的 Flutter 包體積 | Flutter 沙龍回顧

11 月 23 日,字節跳動技術沙龍 | Flutter 技術專場 在北京後山藝術空間圓滿結束。咱們邀請到字節跳動移動平臺部 Flutter 架構師袁輝輝,Google Flutter 團隊工程師 Justin McCandless,字節跳動移動平臺部 Flutter 資深工程師李夢雲,阿里巴巴高級技術專家王樹彬和你們進行分享交流。html

如下是字節跳動移動平臺部 Flutter 資深工程師李夢雲的分享主題沉澱,《如何縮減接近 50% 的 Flutter 包體積》。前端

演講內容大綱:git

  1. 包體積問題現狀
  2. Dart 編譯產物優化
  3. Flutter 引擎編譯產物優化
  4. 機器碼指令優化
  5. 總結與展望

我的介紹

我叫李夢雲,任職於字節跳動移動平臺部,負責移動端部分基礎設施平臺的建設與落地,前兩年落地插件化平臺和熱修復平臺,這兩個平臺如今基本字節跳動全部的 APP 都在使用,也已經比較成熟了,如今個人主要精力在 Flutter 這邊,負責 Flutter Engine 和 Dart Runtime 這兩個底層方向上的一些工做。github

今天在座的各位必定正在用 Flutter 或者想用 Flutter,發現 Flutter 包體積有點偏大、有點控制不住。可能還有一部分同窗並無注意到這個問題,可是隨着使用 Flutter 的深刻程度增多,你們最終都會發現這個痛點的,那咱們今天就來解決這個痛點。算法

這是我今天分享的 5 個組成部分:後端

第一部分,針對 Flutter 包體積給你們講講 Flutter 包體積現狀,以及它由哪幾個部分組成。網絡

接下來三個部分會針對這幾個組成部分作針對性的優化。架構

最後一部分,總結優化手段,展望 Flutter 包體積的將來。app

1、包體積問題現狀

咱們先來統一認識,包體積到底重要不重要?結論是很重要。框架

右圖是 Google 2016 年公佈的研究報告,核心思想是包體積每上升 6MB 就會帶來下載轉化率下降 1%,當包體積增大到 100MB 時就會有斷崖式的下跌。這是 2016 年的數據,如今流量雖然變得更廉價一點,可是用戶的心理是不會變的。可能 6MB 這個數據如今變成 10MB 或者 20MB,可是當你 APP 出如今應用市場的相同位置時,包體積越大,用戶下載意願就越低,這是毫無疑問的。因此咱們的結論是:包體積很重要,須要優化。

那現狀是什麼?結合今日頭條的數據:Android 能夠動態下發,咱們如今使用的插件化框架,包體積增量約等於 0,即使是你們沒有插件化,也能夠用各類方式使包體積增量約等於 0。至於爲何安卓能夠作咱們後續會講到。但 iOS 上是什麼狀況呢?今日頭條 APP 優化前包體積是 167M,Flutter 產物佔 18MB,佔比超過了 10%。那看到這個數據的結論就是:現階段須要重點關注並優化 Flutter 在 iOS 平臺上的包體積問題。

那咱們引用 Flutter 以後會對現有的包體積產生多大影響呢?結論很出乎意料,iOS 平臺上,若是用 OC 寫,它大概是一個線性增加的關係,隨着代碼量增長,包體積也會這樣增長;可是 Flutter 不是,它不是一個線性的關係,它是這樣的一個曲線:初始增加速度極快,隨着代碼增多,增加速度逐漸減緩,最終趨近線性增加。緣由是 Flutter 有一個 Tree Shaking 機制,從 Main 方法開始,逐級引用,最終沒有被引用的代碼,諸如類和函數都會被裁剪掉。

這個機制在 iOS 裏沒有,可是在 Android 裏挺常見的,相似 ProGuard,安卓開發工程師應該很熟悉這個概念。一開始引入 Flutter 以後隨便寫一個業務,你就會大量用到 Flutter/Dart SDK 代碼,這樣初期 Flutter 包體積極速增長,可是過了一個臨界點,用戶包體積的增長就基本取決於你 Flutter 業務代碼增量,不會增加得那麼快。

因此咱們分析 Flutter Release 產物的時候是不能用太簡單的 Demo 的,若是你只是在屏幕上繪製一個 Hello World Text,包體積就會很是小,脫離實際的小,由於大部分 Flutter SDK 就都會被 Tree Shaken 掉了。可是實際的項目不是這樣的,咱們須要寫個稍微複雜一點的項目讓包體積超過臨界點,可是又不能超過太多,不然編譯時間就會很是長,優化包體積時須要反覆的編譯,這樣開發效率和優化效率就會下降。咱們就寫了這麼一個簡單的 Demo,這個 Demo 有一個按鈕、用到了 Material Design 庫的一些控件,屏幕背後還用一些類作了一些別的事情,最終編譯出來以後長成這樣子。

組成部分是兩個 Framework,一個是 APP Framework,還有一個是 Flutter Framework,後面會講這兩個 Framework 主要由什麼組成。

畫了一張圖給你們詳細解釋一下:

第一部分是 App Framework,裏面的 App 在我這個 Demo 工程下是 9.2M,主要來源是 Dart 代碼 AOT 編譯產物,它是一個動態連接庫;還有一部分是 Flutter 靜態資源,內含圖片,字體等,注意這一部分是一個變值,它是隨着你的業務變化而變化的,有可能增長,有可能減小。在個人這個工程裏,flutter_assets 基本沒有東西,可是不等於你的項目 flutter_assets 沒有東西,一樣這個 9.2M 的 App 在你的工程裏可能就不是 9.2MB 了。

而 Flutter.Framework 裏則是一個定值,一個固定的值。第一部分是 Flutter 這個動態編譯庫,也就是咱們的 Flutter Engine,他是由用 Flutter 底層和 Dart 語言的的 C++代碼編譯而成的。這個部分的大小主要是看用哪一個分支或者哪一個版本打出來的,基本上編譯 100 次,無數次都是這麼大,咱們如今是 7.3MB。還有一個 icudtl.dat,國際化支持相關數據文件,883KB,基本能夠忽略不計。

2、Dart 編譯產物生成與優化

在咱們講包體積優化前,先講一下包體積優化的方法論。啓動速度有方法論,包體積也有方法論。包體積的優化無非是三個方式:刪、縮、挪。

刪就是移除無用代碼和無用資源,刪有多是你人肉手動刪,有多是機器自動刪,或者編譯的時候刪除,好比剛纔的 Tree Shaking 機制就是編譯時自動刪除。

當你刪不動時能夠想一下壓縮,壓縮典型的有壓縮圖片資源等。

當刪和縮都沒有辦法解決問題時,最有效的辦法就是挪,從包裏直接挪出去,挪到遠端,典型是遠端下發插件或者安卓裏拆 App Bundle。這個挪,難度是三個中最大的,由於功能是有損的,須要特殊處理,並且一個功能挪出去以後,須要再動態下發才能跑起來。雖然功能是有損的,可是它的收益每每是最大的,隨隨便便挪一個插件或者挪一個 App Bundle 出去就能夠帶來幾 MB 或者十幾 MB 收益,只是它的技術難度大而已,並非作不了。

結合 Flutter Tree Shaking 作,能刪的代碼刪掉,能壓的代碼也壓縮,還有其餘的什麼手段嗎?能不能在 Flutter 中挪?事實是能夠的。以下圖動畫,讓你們感覺一下 Flutter 是怎麼「挪」的:

  1. 第一就是將 Dart 的編譯產物分紅兩部分,Part1 和 Part2,把 Part2 挪出去;
  2. 第二是把 flutter_assets 這個文件夾挪出去,也是動態下發;
  3. 第三是把 icudtl.dat 挪出去,這樣包體積就只剩下了最後這兩部分。

核心思想是:移除非必要產物,動態下發。

那爲何能夠挪?我先結合這張 Dart 編譯流程圖詳細解釋一下 Dart 的編譯流程:

這是 Dart 的源碼,灰色是編譯工具,藍色是編譯產物或者編譯中間文件,黃色表示編譯內層。當 Dart 代碼通過 front server 之後,編譯成 Dart Kernel,安卓上叫 app.dill,這部分屬於 Debug 編譯,編譯完成以後 Dart 代碼的 Debug 編譯就結束了,front server 主要作了詞法分析和語法分析,注意這是編譯原理的 front。通過在 Debug 編譯以後,在 Release 就多了 precompile 的流程,把抽象語法樹給編譯成中間代碼,這個時候就至關因而編譯原理的中層,底下是生成機器代碼,這至關於編譯原理的後端,編譯流程也符合現代的編譯思想分三層。

今天畢竟不是講編譯原理,咱們主要關注編譯完成以後編譯產物的生成,編譯產物爲編譯期生成機器碼內存數據的文件形態,最終咱們須要把內存打包到成文件。

有兩種模式:

第一種是 Blob Mode,僅在安卓平臺上支持,Flutter 1.7.8 版本以前 Android 平臺上的默認模式,分四個部分:兩個指令段,兩個數據段。第二種是 Library Mode,安卓和 iOS 都支持,須要把機器碼導出成彙編而後使用平臺提供的工具編譯成動態庫。iOS 是 xcrun,Android 是 ndkCompiler,注意這三種形態,內容是同樣的,用 nm App 查看動態庫能夠發現它裏面有隻有 4 個符號,跟 Blob Mode 的這 4 個 snapshot 是徹底對應的。咱們只須要知道 AOT 的編譯產物編譯出來至關於四塊機器碼內存。

那編譯完以後咱們須要把它拼起來,拼起來的話首先須要把打到包裏面的東西讓它加載起來,這是 Flutter 加載 Isolate 的代碼,Android 是從第二段裏面讀的,最終從默認 Native Library 裏讀,iOS 就是在最後讀的。

那因此答案出來了,爲何能夠挪?咱們只須要把動態包下載完成,解壓以後設置 Settings 各項路徑,原來的時候是默認設置成包裏的路徑,如今你下載完成以後你強行改爲本身的下載路徑,再開始啓動就能夠了。那剛纔咱們提到,安卓爲何在沒有插件化的狀況下也能夠把包體積縮小很是小?由於安卓的 so 文件原本就能夠動態下發,那這樣的 snapshot 文件也能夠動態下發,資源文件,icudlt.dat 什麼均可以動態下發的,包裏基本就什麼都沒有了,插件化惟一比它好的是 flutter.jar 也能夠動態下發,具體到 iOS 基本能夠把大部分挪出去。

爲何不全挪?安卓能夠全挪,iOS 爲何不能全挪?Part1 和 Part2 又是什麼?

問題出在加載後的運行時階段。咱們看一下這段運行代碼:

加載到內存之後,所謂指令段是須要可執行權限,你們能夠看到這裏設置了 ImagePage 的 Executable 等於 True,兩個指令段須要可執行權限。iOS 不像安卓,沒有辦法隨意標記執行,指令段那必須在動態庫裏下發,才能得到可執行權限,這就是爲何不能把 iOS 包裏面的內容所有給挪出去,就是由於這是平臺限制,雖然 Flutter 提供了完整的 Settings 擴展支持,但仍然必須保證它可執行權限的那部份內存必定放在動態庫裏的。

那咱們就完整回顧動態下發方案的原貌,原來至關於 APP 裏分紅四個組成部分,咱們挪了兩個 Data 過來,挪了資源過來,挪了 icudtl 過來,以下圖所示:

那這個收益是多少?收益就是剛纔的 APP 從 9.2M 變成了 3.8M,Data 段通常狀況來講它的體積是要大於指令段的,你們能夠本身隨便拿一個 Flutter 工程編譯一下安卓的包,安卓會編譯成 4 個 snapshot,Data 段體積通常是要比指令段大的。因此若是你採用動態下發方案,對於 App 動態庫文件優化收益必定是大於 50%的,但我不肯定結合你們具體實際工程的話會不會有這樣的收益,只是在咱們 Demo 裏肯定可使 App 的體積縮減一半以上。那咱們還能夠看到 flutter_assets 已經沒了,總體移出來了,雖然在 Demo 上收益很小,可是實際中收益應該很大,由於實際項目中不太可能沒有資源。最後 icudtl 總體移除,優化 883KB,在用 nm App 查看一下動態庫就會發現動態庫就只有兩個指令段,沒有了兩個 Data 段。

動態下發模式示例,引擎下發動態包演示,你們能夠看咱們把引擎挪出來的部分壓縮成了一個 Zip 包。這裏有一個須要注意的地方,就是你打出來機器碼是分架構的,32 位和 64 位的 Data 段是不同的。那你就須要生成兩個 Zip 文件,根據本身 iOS 設備作針對性下發。

那就有一個問題了,我是一個純 Flutter 應用,或者我一啓動就馬上要用這個功能,接受不了 Flutter 須要動態下發,這時候怎麼辦?咱們能夠變通一下,把這個引擎 Zip 包直接從遠端放到包裏,這樣首次使用須要解壓,會犧牲首次使用的啓動速度,那收益會比動態下發模式要小,就達不到標題所說的接近 50%了,可是仍然不失爲一個有很大收益的方法。

動態下發模式包體積減小 6.3MB,這個減小部分壓縮以後體積是 2.5MB,咱們內置壓縮須要把 2.5MB 再放回到到包裏去,這樣優化收益就少了 2.5MB,方案收益就變成了 3.8MB,固然 3.8M 這也是一個不小的包優化收益了。

一樣的問題,若是你想在 iOS 上支持 32 和 64 雙架構的話,Data 段文件不通用,最終仍是有兩份 Zip 文件,不可能內置兩份 Zip 包,而後根據設備針對性的解壓,那包體積可能不減反增。解決思路是將引擎 Zip 包置於 APP 動態庫內來規避這個問題。而後 App Store 能夠針對動態庫自動實現分架構下發,就是你上傳的雙架構,但實際用戶下載的仍是單架構,咱們能夠巧妙利用這點讓 App Store 替咱們完成這個事情。參考方案挺多,典型的 Dart 有一個 Observatory Server 的 Web 靜態資源,是整個直接打到 Dart 的運行時裏的。

風險應對。不管你採用哪一種方案必定有風險的,好比下載失敗、解壓失敗。應對策略也是,咱們確定須要提供引擎是否 ready 的 API,可是很難解釋清楚,功能雖然打進包裏,但仍然可能用不了。研發須要轉換思惟,這兩種模式下不要假設 Flutter 必定可用,由於動態下發或者內置壓縮就絕對達不到百分之百的成功率,由於總有用戶的磁盤是滿的,總有網絡不可達的狀況。這時候 PM 就會說接受不了這部分損失,但實際上你的功能沒那麼重要了。最終實際損失是用 Flutter 覆蓋率乘以 Flutter 功能的滲透率。Flutter 覆蓋率目前應該能夠達到三個 9,由於咱們用了內部壓縮方案。Flutter 功能的滲透率,有用戶雖然沒有用 Flutter,可是他若是不用 Flutter 這個功能那等於沒有損失,這一塊須要辯證來看,包體積優化以後是全部用戶都收益,而損失的只是少部分用戶,你須要平衡一下,看哪部分損失大,這個狀況是否是能夠接受,若是能夠接受,你想要求穩就用內置壓縮,若是你想更激進一點,那就用動態下發。

3、優化 Engine 編譯產物

接下來包變成這個樣子,是否是就沒有優化空間了?並非。尚未動心思優化的都是有優化空間的,只不過多和少而已。

在 Flutter 引擎編譯時,安卓和 iOS 的編譯參數不一樣,安卓是-OZ,iOS 是-OS。若是想追求極致包體積是須要用 OZ 的,不能用 OS,OZ 只是性能稍微差一點,可是基本能夠忍受。爲何 iOS 性能廣泛都比安卓好一點,可是爲何它反而在這個性能好的平臺上反而用 -OS 呢?它實際上是以前的 build-tools 不統一,考慮到連接時優化的順序問題,OZ 反而增長了包大小。只須要升級最新的 build-tools,改 OS 爲 OZ,收益爲 723.17KB,這是頭條本身的數據,你們的狀況可能不同,可是這個收益是確定有的。

除了統一編譯參數以後,第二部分是定製化編譯,這塊結合各個廠商、各個 APP 可能不同。可是有兩點你們均可以借鑑的:

第一部分,移除 boringSSL,可用 Method Channel 調用源生網絡庫來替代 Dart Http 功能,就跟在 Android 上咱們基本上歷來不會裸用 OKHttp 同樣,咱們總得作點動態選路、失敗重連這種,還有各類對國內網絡作針對性的優化,Dart 的原生網絡庫性能必定是比不過 Native 針對國內環境作過專門優化的網絡庫的,這時候咱們就能夠用 Method Channel 調用源生網絡庫替代 Dart Http 功能,這樣性能絕對有提高,不會反而降低,同時還能帶來包體積的收益。具體到 Flutter Engine 收益是 0.5MB。官方也發現這個問題,他們也已經計劃把 Dart 的網絡功能交給上層來代理實現。

第二部分是 Skia,它的參數不少,其中有 3 個咱們試過了,去掉以後在 Benchmark 上看不會對性能產生影響,把它禁掉的話最終獲得收益不到 200KB。你們能夠根據本身的狀況作針對定製優化。官方有更高端的概念叫模塊化編譯,核心思想是把 Engine 拆成不一樣的 Modular,根據本身的狀況選擇哪些打進去、哪些不能打進去,這樣就能保證 Flutter Engine 裏的全部東西都是必要的、必須的,但這隻停留在計劃階段,將來 Google 的方向是這樣,若是你們等不及能夠先採用定製化編譯思路。

4、機器碼指令優化

如今 Flutter.Framework 裏的 Flutter 動態庫也獲得了優化,還有最後一部分是這兩個指令段。

這兩個指令段能不能優化呢?實際上是能夠的,要深刻 Dart 的編譯原理、機器碼生成等一大堆。咱們一開始並非特別在乎這個,都是機器碼,那 OC 出來的機器碼就比 Dart 厲害嗎?結論還真是,目前 OC 寫出來的機器碼就是厲害一點。

咱們作了包體積增量對比實驗,爲何作這個實驗?是由於未來若是有一天 Flutter 鋪開之後,全部的業務代碼都用 Flutter 寫,那就涉及一個問題,以前用 OC 開發一個業務可能包體積是 200KB,如今用 Flutter 開發一樣一個業務發現包體積變成 400KB,翻倍怎麼辦?會不會有這個風險?實際上是有的。

作個簡單的實驗,這樣一個函數返回自定義的 View,不停的複製,一直複製到 1000,這時候沒有引用任何新增代碼,包體積增量徹底取決於你本身 Copy 的新增代碼,這個時候你的增加就是徹底線性的。可是這個線性的斜率是不同的,Dart 的斜率遠高於 OC。

這一塊是怎麼優化的?咱們先分析了一下這個背後的緣由,寫了一個更簡單的函數,返回一個自定義的 View 太複雜,咱們就直接打印了一個 Int:

OC 的版本用 Hopper 反編譯,獲得 11 條指令,由於 a=3+4 在編譯期直接被優化,0×3 是我當時編譯時用 1+2 得出來的,第 4 個指令 orr 應該是改爲 0×7,結論是:函數生成 11 條彙編指令。

Dart 呢?咱們寫了這個相同的函數,若是你想獲得機器碼指令須要修改 Dart 源碼,在編譯時把指令打出來,咱們看它不是 11 條,而是 32 條,這時候包體積斜率不同的緣由就找到了。Dart 彙編指令多了不少。

咱們發了一個 Issue,這是標題(The ipa size grows too fast due to Flutter's incompact machine code instructions #40345)。Google 很快給咱們回覆了,這是負責 Dart 團隊的 Mraleph 分析的結果。

核心的結論說全部函數前 8 個都有指令對齊頭,後 6 位都有對齊指令,有的是基於歷史的緣由,有的多是基於性能的緣由,可是一頭一尾都是能夠移除。若是是一個特別小的函數,中間制定機器碼指令反而沒有這先後加起來的 14 條指令多。若是你的包裏面所有是小函數的話,那 Dart 和 OC 差出來就比較多,若是正常寫的話確定不是這樣。我這個實驗其實對 Dart 很是不友好,但變相放大暴露這個問題其實挺好的,讓咱們知道指令裏頭有這麼大的優化空間。中間還有 18 條指令,其中 5 條是爲了作棧溢出檢查,OC 沒有這個指令,還剩 13 條必要指令,基本與 OC 11 條持平,也存在優化空間。

最終你們能夠跟進一下這個 Issue,Google 的大神 Mraleph 給建立了 5 個 Task,有一些已經落地了,有一些還在推動中,這個問題他們很是重視,會持續性跟進。他們給出的結論是最終認爲 OC 的機器碼應該跟 Dart 基本持平,不存在誰更厲害的問題。

5、總結與展望

總結

第一部分,咱們分析了 Dart 的編譯產物,對 Dart 編譯產物作了針對性優化。

有兩個優化思路:

  1. 動態下發:剝離 Data 段及一切非必要產物,打包後動態下發。

  2. 內置壓縮:以二進制形態內置動態下發包。

第二部分是 Flutter 引擎編譯產物優化,主要優化思路有升級 Bulild Tools 統一雙編譯參數,定製化編譯裁剪引擎內部部分特定無用功能。

第三部分是機器碼指令優化,精簡機器碼指令,Google 也回覆稱將來 Dart 與 OC 基本持平。

展望

剛纔咱們看到 Dart 是這樣的,Dart 將來這個斜率低於 iOS,若是 OC 跟 Dart 基本持平,咱們能夠把佔包體積一半的 Dart 指令的 Data 段挪出去,即使是 OC 的機器碼只有它的一半,咱們仍然能夠保證最終的包體積 Dart 和 OC 基本持平。若是 Google 可以優化得更好,能到跟 OC 持平,那 Flutter 將來包體積增量必定比 iOS 小,這個問題在將來就可能不是一個問題了。固然,這個前提是發生在動態下發。內置壓縮這個斜率稍微高一點,可是至於比 OC 高仍是低,我沒辦法準確預測,可是我以爲應該是一個能夠接受的程度。

今天的分享就到這裏。

Q&A

提問:谷歌的引擎是一直在迭代的,若是我從現代的版本開始修改引擎,之後谷歌的引擎更新了,我要不要立刻跟進?

回答:這個問題不少人都問過咱們,在 Flutter 團隊引擎不停迭代時,你的自定義引擎如何跟上節奏?這個問題是咱們不須要緊跟潮流,咱們挑一個穩定版本,154 作有針對性的優化,過兩個月再判斷一下,好比如今 191 適合不適合作適配、作遷移,若是適合,那咱們就作,若是不適合,或者業務方沒有緊急需求,那就不升。爲何有些團隊升到 178?由於海外要支持雙架構,原來的不夠,只能支持單架構,那麼 178 默認模式就改爲 Library Mode,支持 32 位和 64 位,是爲了這個事情。若是你沒有這個強烈的需求,反而用本身公司內部的引擎更穩定一點。


提問:我是一名移動端研發。咱們經過「挪」的方式使包體積變小了,可是用戶在使用實際模塊當中又要挪回來,我想問的是用戶在使用某個模塊時,把咱們挪出去的這部分挪回來的時候,這個轉場咱們應該怎麼去處理?或者有什麼更好的方案讓用戶無感知的加載咱們挪出去的這部分東西?

回答:仍是跟剛纔的問題同樣。這個問題對於字節跳動的 Android 研發來說還挺司空見慣的,一個功能挪出去之後,構成插件之後,也會面臨你這個相同的問題。這很簡單,就判斷一下插件是否存在,iOS 也要判斷引擎是否存在,要麼是否展現接口、是否展現功能入口。你若是必定要在啓動階段首頁展現這個功能的話,那你就只能阻塞一下了。


提問:您提到有一個字節碼優化的問題,剛纔講到 Dart 語言應該是運行在虛擬機上的,這個字節碼優化是優化編譯的中間語言?仍是由 Dart 虛擬機最終生成?

回答:不,它是機器,在 Release 模式下運行的是機器碼。在編譯器由 Dart 虛擬機生成的,可是實際運行的時候它是一個完徹底全的機器碼。


提問:關於剛纔提到字節碼指令的問題,Dart 針對你舉的例子裏,同一條 OC 是 11 條,Dart 是 32 條,這種狀況對於咱們來講,咱們本身沒辦法作這個優化,可是實際過程當中要不要儘可能減小小函數,這樣是否是也是一種方式?

回答:實際使用中應該不會寫到像我今天展現這麼小的函數,應該不會直接打出一個 Int,實際使用的函數遠比這個要複雜,在實際使用過程當中 Dart 的代碼和 OC 的區別沒有今天展現的那麼大,我只是爲了演示冗餘指令,專門挑了一個特別對 Dart 不友好的 demo,但實際上沒有那麼大的區別。若是已經用上動態下發模式的話,留在包裏的指令段真的是很是少的一部分,咱們只是追求極致把事情作到盡善盡美,可是這部分就算不優化也應該是能夠接受的,咱們線上已經在跑着、已經在普遍用 Flutter 了,這應該不是阻礙你發佈或者採用它這個技術棧的緣由。

現場視頻

v.qq.com/x/page/b302…

更多精彩分享

Flutter 沙龍回顧 | 跨平臺技術趨勢及字節跳動 Flutter 架構實踐

Flutter 沙龍回顧 | Custom Widgets in Flutter

上海沙龍回顧 | 字節跳動在Spark SQL上的核心優化實踐

上海沙龍回顧 | 字節跳動如何優化萬級節點HDFS平臺


字節跳動技術沙龍

字節跳動技術沙龍是由字節跳動技術學院發起,字節跳動技術學院、掘金技術社區聯合主辦的技術交流活動。

字節跳動技術沙龍邀請來自字節跳動及業內互聯網公司的技術專家,分享熱門技術話題與一線實踐經驗,內容覆蓋架構、大數據、前端、測試、運維、算法、系統等技術領域。

字節跳動技術沙龍旨在爲技術領域人才提供一個開放、自由的交流學習平臺,幫助技術人學習成長,不斷進階。


歡迎關注「字節跳動技術團隊」

相關文章
相關標籤/搜索