Flutter 開發實戰與前景展望 - RTC Dev Meetup

你們好,我是郭樹煜,Github GSY 系列開源項目的做者,系列包括有 GSYVideoPlayer 、GSYGitGithubApp(Flutter\ReactNative\Kotlin\Weex)四大版本,目前總 star 在 17 k+ 左右,主要活躍在掘金社區,id 是戀貓的小郭,主要專欄有《Flutter完整開發實戰詳解》系列等,平時工做負責移動端項目的開發,工做經歷從 Android 到 React Native 、Weex 再到現在的 Flutter ,期間也參與過 React 、 Vue 、小程序等相關的開發,算是一個大前端的選手吧。前端

此次主要是給你們分享 Flutter 相關的內容,主要涉及作一些實戰和科普性質的內容。java

1、移動開發的現狀

恰逢最近谷歌 IO 大會結束,大會後也在線上線下和你們有過交流,總結了下你們最關係的問題有:node

一、谷歌在 Kotlin-First 的口號下又推廣 Dart + Flutter 衝突嗎?

這個問題算是被問得最多的一個,先說觀點:我我的認爲其實這並不衝突,由於有個 誤區就是認爲跨平臺開發就能夠拋棄原生開發!react

若是從事過跨平臺開發的同窗應該知道,平臺提供的功能向來是有限的,而面對產品經理的各類 「點歪技能樹」 的需求,不少時候你是須要基於框架外提供支持,常見的就是 混合開發或者原生插件支持git

因此這裏我表達的是,目前 KotlinDart 更可能是相輔相成 ,而一旦業務複雜度到必定程度,跨平臺框架還可能存在下降工做效率的問題,好比針對新需求,須要重複開發 Android/IOS 的原生插件作支持,這也是 Aribnb 曾經選擇放棄 React Native 的緣由之一。github

與我而言,跨平臺的意義在於解決的是端邏輯的統一 ,至少避免了邏輯重複實現,或者 IOSAndroid 之間爭論 誰對誰錯 的問題,甚至能夠統一到 web 端等等。web

二、React Native 和 Flutter 之間的對比

Flutter 做爲後來者,不免會被用來和 React Native 進行對比,在這個萬物皆是 JS 的時代,DartFlutter 的出現顯得尤其扎眼。redux

在設計上它們有着許多類似之處,響應式設計/async支持/setState更新 等等,同時也有着各類的差別,而你們最爲關心的,無非 性能、支持、上手難易、穩定性程度 這四方面:小程序

  • 性能上 Flutter 的確實會比 React Native 好 ,以下圖所示,這是由框架底層決定的,固然目前 React Native 也在進行下一代的優化, 而對此最直觀的數據就是:GSY系列 在18年用於閒魚測試下的對比數據了

image10.png

image11.png

同時注意不要用模擬器測試性能,特別是IOS模擬器作性能測試,由於 Flutter 在 IOS模擬器中純 CPU ,而實際設備會是 GPU 硬件加速,同時只在 Release 下對比性能。react-native

  • 支持上 Flutter 和 React Native , 都存在第三方包質量良莠不齊的問題,而目前在這一塊 Flutter 是弱於 React Native 的 ,畢竟 React Native 發展已久,雖然版本號一直不到 1.0,可是在 JS 的加持下生態豐富,同時也是由於平臺特性的緣由,諸如 WebView 、地圖等控件的支持上如今依舊不夠好,這個後面也會說道。

  • 上手難易度上,Flutter 配置環境和運行的「成功率」比 React Native 高很多 ,這裏面有 node_module 黑洞這個坑,也有 React Native 自己依賴平臺控件致使的,至少我曾經試過接手一個 React Native 跑了一天都沒跑起來的經歷,同時 Flutter 在運行和SDK版本升級的陣痛也會少不少。

  • 穩定性:Flutter 中大部分異常是不會引發應用崩潰 ,更多會在 Debug 上體現爲紅色錯誤堆棧,Release 上 UI 異常等等。

若是你是前端,我會推薦你先學 React Native,若是你是原生開發,我推薦你學 Flutter

在 React Native 0.59.x 版本開始,React 已經將許多內置控件和庫移出主項目,但願模糊 React 和 React Native 的界線,統一開發,這裏的理念和 Flutter 很像。

Flutter 暫時不支持熱更新!!!!!!!!

2、Flutter 實戰

一、Dart 中有意思的一些東西

1.一、var 的語法糖和 dynamic

var 的語法糖是在賦值時才自推導出類型的 ,而 dynamic 是動態聲明,在運行時檢測,它們的使用有時候容易出現錯誤。

以下圖因此說,

  • var 初始化時被指定爲 dynamic 類型的。
  • 而後賦值的時候初始化爲 String 類型,這時候進行 ++ 操做就會出現運行時報錯,
  • 以下圖2若是在初始化指定類型的,那麼編譯時就會告訴你錯誤了。

圖1

圖2

1.二、各種操做符

以下圖所示,Dart 支持不少有意思的操做符,以下圖:

  • 執行的時候首先是判斷 AA 若是爲空,就返回 999
  • 以後若是 AA 爲空,就爲 AA 賦值 999
  • 以後對 AA 進行整除 999 ,輸出結果 10

1.三、支持操做符重載

以下圖所示,Dart 中是支持操做符重載的,這樣能夠比較直觀咱們的代碼邏輯,而且簡化代碼時的調用。

image15.png

1.四、方法當作參數傳遞

以下圖所示,在 Dart 中方法時能夠做爲參數傳遞的,這樣的形式可讓咱們更靈活的組織代碼的邏輯。

1.五、async await / async* yield

Dartasync await / async* yield 等語法糖,表明 Dart 中的 FutureStream 操做,它們對應 Dart 中的異步邏輯支持。

sync* / yield 對應 Stream 的同步操做。

1.六、Mixins

Dart 中支持混入的模式,以下圖所示,混入時的基礎順序是從右到左依次執行的,並且和 super 有關,同時 Dart 還支持 mixin 關鍵字的定義。

Flutter 的啓動類用的就是 mixins 方式

1.七、isolate

Dart 中單線程模式中增長了 isolate 提供跨線程的真異步操做,而由於 Dart 中線程不會共享內存,因此也不存在死鎖,從而也致使了 isolate 之間數據只能經過 port 的端口方式發送接口,相似於 Scoket 的方式,同時提供了 compute 的封裝接口方便調用。

1.8 call

Dart 爲了讓類能夠像函數同樣調用,默認均可以實現 call() 方法,一樣 typedef 定義的方法也是具有 call() 條件。

好比我定義了一個 CallObject

class CallObject {

  List<Widget> footerButton = [];

  call(int i, double e) => "$i xxxx $e";
}

複製代碼

就能夠經過如下執行

CallObject callObject = CallObject();
print(callObject(11, 11.0));
print(callObject?.call(11, 11.0));
複製代碼

而後我定義了

typedef void ValueFunction(int i);

  ValueFunction vt = (int i){
    print("zzz $i");
  };

複製代碼

就能夠經過直接執行和判空執行處理

vt(666);
 vt?.call(777);

複製代碼

二、Flutter 中常見的

2.一、ChangeNotifier

以下圖所示,ChangeNotifier 模式在 Flutter 中是十分常見的,好比 TextField 控件中,經過 TextEditingController 能夠快速設置值的顯示,這是爲何呢?

image18.png

以下圖所示,這是由於 TextEditingController 它是 ChangeNotifier 的子類,而 TextField 的內部對其進行了 addListener,同時咱們改變值的時候調用了notifyListener,觸發內部 setState

image19.png

2.二、InheritedWidget

Flutter 中全部的狀態共享都是經過它實現的,如自帶的 ThemeLocalizations ,或者狀態管理的 scoope_modelflutter_redux 等等,都是基於它實現的。

以下圖是 SliderTheme 的自定義實現邏輯,默認 Theme 中是包含了 SliderTheme,可是咱們能夠經過覆蓋一個新的 SliderTheme 嵌套去實現自定義,而後經過 SliderTheme theme = SliderTheme(context); 獲取,其中而 context 的實現就是 Element

image20.png

ElementinheritFromWidgetOfExactType 方法實現裏,有一個 Map<Type, InheritedElement> _inheritedWidgets 的對象。

_inheritedWidgets 通常狀況下是空的,只有當父控件是 InheritedWidget 或者自己是 InheritedWidgets 時纔會有被初始化,而當父控件是 InheritedWidget 時,這個 Map 會被一級一級往下傳遞與合併 。 因此當咱們經過 context 調用 inheritFromWidgetOfExactType 時,就能夠往上查找到父控件的 Widget

2.三、StreamBuilder

StreamBuilder 通常用於經過 Stream 異步構建頁面的,以下圖所示,經過點擊以後,綠色方框的文字會變成 addNewxxx,由於 Stream 進行了 map 變化,同時通常實現 bloc 模式的時候,常常會用到它們。

image21.png

相似的還有 FutureBuilder

2.四、State 中的參數使用

通常 Widget 都是一幀的,而 State 實現了 Widget 的跨幀繪製,通常定義的時候,咱們能夠以下圖同樣實現,而以下圖尖頭所示,這時候咱們點擊 setState 改變的時候,是不會出現效果的,爲何呢?

其實 State 對象的建立和更新時機致使的:

  • 一、createState 只在 StatefulElement 建立時纔會被建立的。

  • 二、StatefulElement 的 createElement 通常只在 inflateWidget 調用。

  • 三、updateChild 執行 inflateWidget 時, 若是 child 存在能夠更新的話,不會執行 inflateWidget。

三、四棵樹

Flutter 中主要有 WidgetElementRenderObjectLayer 四棵樹,它們的做用是:

  • Widget :就是咱們日常寫的控件,Flutter 宇宙中萬物皆 Widget ,它們都是不可變一幀,同時也是被人吐槽不少的嵌套模式,固然換個角度,事實上你把他看成 Widget 配置文件來寫或者就好理解了。

  • Element :它是 BuildContext 的實現類,Widget 實現跨幀保存的 state 就是存放在這裏,同時它也充當了 WidgetRenderObject 之間的橋樑。

  • RenderObject :它纔是真正幹活(layout、paint)等,同時它纔是真實的 「dom」 。

  • Layer :一整塊的重繪區域(isRepaintBoundary),決定重繪的影響區域。

skia 在繪製的時候,saveLayer 是比較消耗性能的,好比透明合成、clipRRect 等等都會可能須要 saveLayer 的調用, 而 saveLayer 會清空GPU繪製的緩存,致使性能上的損耗,因此開發過程當中若是掉幀嚴重,能夠針對這一塊進行優化。

四、手勢

Flutter 在手勢中引入了競技的概念, Down 事件在 Flutter 中尤其重要。

  • PointerDownEvent 是一切的起源,在 Down 事件中通常不會決出勝利者。

  • MOVEUP 的時候才競爭獲得響應。

  • 以點擊爲例子:Down 時添加進去參與競爭,UP 的時候才決定誰勝利,勝利條件是:

I、UP 的時候若是隻有一個,那麼就是它了。

II、UP 的時候若是有多個,那麼強制隊列裏第一個直接勝利。

  • 這裏包含了有趣的點就是,都在 UP 的時候才響應,那麼 Down 事件怎麼先傳遞出去了?

FLutter 在這裏作了一個 didExceedDeadline 機制 ,事實上在上面的 addPointer 的時候,會啓動了一個定時器,默認 100 ms,若是超過指定時間沒 UP ,那就先執行這個 didExceedDeadline 響應 Down 事件。

  • 那問題又來了,若是這時候隊列裏兩個呢?

它們的 onTapDown 都會被觸發,可是 onTap 只有一個得到。

  • 若是有兩個滑動 ScrollView 嵌套呢?

舉個簡單的例子,兩個 SingleChildScrollView 的嵌套時,在佈局會經歷:

performLayout -> applyContentDimensions -> applyNewDimensions -> context.setCanDrag(physics.shouldAcceptUserOffset(this));

只有 shouldAcceptUserOffsetture 時,纔會添加 VerticalDragGestureRecognizer 去處理手勢。

而判斷條件主要是 return math.max(0.0, child.size.height - size.height); ,也就是若是 child Scroll 的 height 小於父控件 Scroll 的時候,就會出現 child 不添加 VerticalDragGestureRecognizer 的狀況,這時候根本就沒有競爭了。

五、動畫

Flutter 中的動畫是怎麼執行的呢?

咱們先看一段代碼,而後這段代碼執行的效果以下圖2所示。

那既然 Widget 都是一幀,那麼動畫確定有 setState 的地方了。

首先這裏有個地方能夠看下,這時候 200 這個數值執行後是會報錯的,由於白框內可見 Tween 中的 T 在這時候會出現既有 int 又有 double ,沒法判斷的問題,因此真實應該是 200.0 。

image28.GIF

同時你發現沒有,代碼中 parentContainer 在 只有100的狀況下,它的 child 能夠正常的畫 200,這是由於咱們的 paint 沒有跟着 RenerObjcet 的大小走, 因此通常狀況下,整個屏幕都是咱們的畫版,Canvas 繪製與父控件大小能夠不要緊。

同時動畫是經過 vsync 同步信號去觸發的,就是咱們 mixin 的 SingleTickerProviderStateMixin,它內部的 Ticker 會經過 SchedulerBindingscheduleFrameCallback 同步信號觸發重繪 。

動畫後的控件的點擊區域,和你的動畫數據改變的是 paint 仍是 layout 有關 。

六、狀態管理

scope_modelflutter_reduxfish_redux 、甚至還有有 dva_flutter 等等,能夠看出狀態管理在 flutter 中和前端十分相近。

這裏簡單說說 scope_model ,它只有一個文件,可是很巧妙,它利用的就是 AnimationBuilder 的特性。

以下圖是使用代碼,在前面咱們知道,狀態管理使用的是 InheritedWidget 實現共享的,而當咱們對 Model 進行數據改變時,經過調用 notifyListeners 通知頁面更新了。

這裏的原理是什麼呢?

  • 其實 scope_model 內部利用了 AnimationBuilder ,而 Model 實現了 Listenable 接口。

  • Model 設置給了 AnimationBuilder 時, AnimationBuilder 會執行 addListener 添加監聽,而監聽方法裏會執行 setState

  • 因此咱們改變 set 方法時調用 notifyListeners 就觸發了 setState 去更新了,這樣體現出了前面說的 FLutter 常見的開發模式。

3、混合開發

Android 的角度來講,從方便調試和解耦集成上,咱們通常會以 aar 的形式集成混合開發,這裏就會涉及到 gradle 打包的一個概念。

一、以下代碼所示,在項目中進行 gradle 腳本修改,組件化開發模式,用 apk 開發,用 aar 提供集成,正常修改 gradle 代碼便可快速打包。

那若是 Flutter 的項目插件帶有本地代碼呢?

若是開發過 React Native 的應該知道,在原生插件安裝時會須要執行 react-native link ,而這時候會修改項目的gradle 和java代碼。

二、 和 React Native 頗有侵入性相比, Flutter 就很巧妙了。

以下圖所示,安裝過的插件會出如今 .flutter_plugins 文件中,而後經過讀取文件,動態在 setting.gradleflutter.gradle 中引入和依賴:

因此這時候咱們能夠參考打包,修改咱們的gradle腳本,利用 fat-aar 插件將本地 projcet 也打包的 aar 裏。

官方將來將有 Flutter build aar 的方法可提供使用。

三、混合開發的最大痛點是什麼?

確定是堆棧管理!!! 因此項目開發了 flutter_boost 來解決這個問題。

  • 堆棧統一到了原生層。
  • 經過一個惟一 engine ,切換 Surface 渲染顯示。
  • 每一個 Activity 就是一個 Surface ,不渲染的頁面經過截圖緩存畫面。

flutter_boost 截止到我測試的時間 2019-05-16, 只支持 1.2以前的版本

4、PlatformView

混合開發除了集成到原生工程,也有將原生控件集成到 Flutter 渲染樹裏裏的需求。

首先咱們看看沒有 PlatformView 以前是如何實現 WebView 的,這樣會有什麼問題?

以下圖所示,事實上 dart 中僅僅是用了一個 SingleChildRenderObjectWidget 用於佔位,將大小傳遞給原生代碼,而後在原生代碼裏顯示出來而已。

這樣的時候一定會代碼畫面堆棧問題,由於這個顯示脫離了 Flutter 的渲染樹,經過出現動畫確定會不一致。

4.1 AndroidView

AndroidView -> TextureLayer,利用Android 上的副屏顯示與虛擬內存顯示原理。

  • 共享內存,實時截圖渲染技術。

  • 存在問題,耗費內存,頁面複雜時慢。

這部分由於以前之前聊過,就不贅述了

3、Flutter Web

RN由於是原生控件,因此在react 和react native 整合這件事上存在難度。

flutter 做爲一個UI 框架,與平臺無關,在web上利用的是dart2js的能力。 好比Image

  • 由於 Flutter 是一套 UI 框架,總體 UI 幾乎和平臺無關,這和 React Native 有很大的區別。(我在開發過程當中幾乎無知覺)
  • 在 flutter_web 中 UI 層面與渲染邏輯和 Flutter 幾乎沒有什麼區別,底層的一些區別如: flutter_web 中的 Canvas 是 EngineCanvas 抽象,內部會藉助 dart2js 的能力去生成標籤。
  • React Native 平臺關聯性太強,而 Flutter 在多平臺上優點明顯。咱們期待官方幫咱們解決大部分的適配問題。

image38.png

image39.png

image40.GIF

Flutter 的平臺無關能力能帶來什麼?

  • 一、某些功能頁面,能夠一套代碼實現,利用插件安裝引入,在web、移動app、甚至 pc 上,均可以編譯出對應平臺的高性能代碼,而不會像 Weex 等同樣存在各類兼容問題。

  • 二、在應用上能夠快速實現「降級策略」,好比某種狀況下應用產生奔潰了,能夠替換爲同等 UI 的 h5 顯示,而這些代碼只須要維護一份。

資源推薦

咱們還會再見嗎?
相關文章
相關標籤/搜索