概述
Flutter是Google推出的一套開源跨平臺UI框架,可以快速地在Android、iOS和Web平臺上構(gòu)建高質(zhì)量的原生用戶界面。在過去的一年里,F(xiàn)lutter的更新頻率是相當(dāng)?shù)目欤灿泻芏嗟墓鹃_始使用它來進(jìn)行跨平臺應(yīng)用開發(fā),可以說,將Flutter稱為2019年最流行的跨平臺技術(shù)也不為過。
在研究了Hybrid APP、React Native和Weex等技術(shù)之后,我也在今年的早些時候也入了Flutter的坑??偟膩碚f,不管是從社群和社區(qū)的活躍來看,還是從技術(shù)的水準(zhǔn)上來看,F(xiàn)lutter無疑是最優(yōu)秀的,特別是Google將Flutter列為重點(diǎn)推廣項目之后,全世界掀起了一股學(xué)習(xí)Flutter的熱潮。
在國內(nèi),除了阿里、騰訊、美團(tuán)等大廠外,國內(nèi)很多的中小團(tuán)隊也開始使用Flutter來作為移動應(yīng)用開發(fā)的首選,并且很多公司在移動招聘方面也要求具有Flutter開發(fā)的背景。Flutter 的面試題主要分為兩個Dart和Flutter部分,下面是一些常見的面試題。
Dart
1. Dart 當(dāng)中的 「..」表示什么意思?
Dart 當(dāng)中的 「..」意思是 「級聯(lián)操作符」,為了方便配置而使用?!?.」和「.」不同的是 調(diào)用「..」后返回的相當(dāng)于是 this,而「.」返回的則是該方法返回的值 。
2. Dart 的作用域
Dart 沒有 「public」「private」等關(guān)鍵字,默認(rèn)就是公開的,私有變量使用 下劃線 _開頭。
3. Dart 是不是單線程模型?是如何運(yùn)行的?
Dart 是單線程模型,運(yùn)行的的流程如下圖。

簡單來說,Dart 在單線程中是以消息循環(huán)機(jī)制來運(yùn)行的,包含兩個任務(wù)隊列,一個是“微任務(wù)隊列” microtask queue,另一個叫做“事件隊列” event queue。
當(dāng)Flutter應(yīng)用啟動后,消息循環(huán)機(jī)制便啟動了。首先會按照先進(jìn)先出的順序逐個執(zhí)行微任務(wù)隊列中的任務(wù),當(dāng)所有微任務(wù)隊列執(zhí)行完后便開始執(zhí)行事件隊列中的任務(wù),事件任務(wù)執(zhí)行完畢后再去執(zhí)行微任務(wù),如此循環(huán)往復(fù),生生不息。
4. Dart 是如何實(shí)現(xiàn)多任務(wù)并行的?
前面說過, Dart 是單線程的,不存在多線程,那如何進(jìn)行多任務(wù)并行的呢?其實(shí),Dart的多線程和前端的多線程有很多的相似之處。Flutter的多線程主要依賴Dart的并發(fā)編程、異步和事件驅(qū)動機(jī)制。

簡單的說,在Dart中,一個Isolate對象其實(shí)就是一個isolate執(zhí)行環(huán)境的引用,一般來說我們都是通過當(dāng)前的isolate去控制其他的isolate完成彼此之間的交互,而當(dāng)我們想要創(chuàng)建一個新的Isolate可以使用Isolate.spawn方法獲取返回的一個新的isolate對象,兩個isolate之間使用SendPort相互發(fā)送消息,而isolate中也存在了一個與之對應(yīng)的ReceivePort接受消息用來處理,但是我們需要注意的是,ReceivePort和SendPort在每個isolate都有一對,只有同一個isolate中的ReceivePort才能接受到當(dāng)前類的SendPort發(fā)送的消息并且處理。
5. 說一下Dart異步編程中的 Future關(guān)鍵字?
前面說過,Dart 在單線程中是以消息循環(huán)機(jī)制來運(yùn)行的,其中包含兩個任務(wù)隊列,一個是“微任務(wù)隊列” microtask queue,另一個叫做“事件隊列” event queue。
在Java并發(fā)編程開發(fā)中,經(jīng)常會使用Future來處理異步或者延遲處理任務(wù)等操作。而在Dart中,執(zhí)行一個異步任務(wù)同樣也可以使用Future來處理。在 Dart 的每一個 Isolate 當(dāng)中,執(zhí)行的優(yōu)先級為 : Main > MicroTask > EventQueue。
6. 說一下Dart異步編程中的 Stream數(shù)據(jù)流?
在Dart中,Stream 和 Future 一樣,都是用來處理異步編程的工具。它們的區(qū)別在于,Stream 可以接收多個異步結(jié)果,而Future 只有一個。
Stream 的創(chuàng)建可以使用 Stream.fromFuture,也可以使用 StreamController 來創(chuàng)建和控制。還有一個注意點(diǎn)是:普通的 Stream 只可以有一個訂閱者,如果想要多訂閱的話,要使用 asBroadcastStream()。
7. Stream 有哪兩種訂閱模式?分別是怎么調(diào)用的?
Stream有兩種訂閱模式:單訂閱(single) 和 多訂閱(broadcast)。單訂閱就是只能有一個訂閱者,而廣播是可以有多個訂閱者。這就有點(diǎn)類似于消息服務(wù)(Message Service)的處理模式。單訂閱類似于點(diǎn)對點(diǎn),在訂閱者出現(xiàn)之前會持有數(shù)據(jù),在訂閱者出現(xiàn)之后就才轉(zhuǎn)交給它。而廣播類似于發(fā)布訂閱模式,可以同時有多個訂閱者,當(dāng)有數(shù)據(jù)時就會傳遞給所有的訂閱者,而不管當(dāng)前是否已有訂閱者存在。
Stream 默認(rèn)處于單訂閱模式,所以同一個 stream 上的 listen 和其它大多數(shù)方法只能調(diào)用一次,調(diào)用第二次就會報錯。但 Stream 可以通過 transform() 方法(返回另一個 Stream)進(jìn)行連續(xù)調(diào)用。通過 Stream.asBroadcastStream() 可以將一個單訂閱模式的 Stream 轉(zhuǎn)換成一個多訂閱模式的 Stream,isBroadcast 屬性可以判斷當(dāng)前 Stream 所處的模式。
8. await for 如何使用?
await for是不斷獲取stream流中的數(shù)據(jù),然后執(zhí)行循環(huán)體中的操作。它一般用在直到stream什么時候完成,并且必須等待傳遞完成之后才能使用,不然就會一直阻塞。
Stream<String> stream = new Stream<String>.fromIterable(['不開心', '面試', '沒', '過']);
main() async{
await for(String s in stream){
print(s);
}
}
9. 說一下 mixin機(jī)制?
mixin 是Dart 2.1 加入的特性,以前版本通常使用abstract class代替。簡單來說,mixin是為了解決繼承方面的問題而引入的機(jī)制,Dart為了支持多重繼承,引入了mixin關(guān)鍵字,它最大的特殊處在于: mixin定義的類不能有構(gòu)造方法,這樣可以避免繼承多個類而產(chǎn)生的父類構(gòu)造方法沖突。
mixins的對象是類,mixins絕不是繼承,也不是接口,而是一種全新的特性,可以mixins多個類,mixins的使用需要滿足一定條件。
Flutter
1. 請簡單介紹下Flutter框架,以及它的優(yōu)缺點(diǎn)?
Flutter是Google推出的一套開源跨平臺UI框架,可以快速地在Android、iOS和Web平臺上構(gòu)建高質(zhì)量的原生用戶界面。同時,F(xiàn)lutter還是Google新研發(fā)的Fuchsia操作系統(tǒng)的默認(rèn)開發(fā)套件。在全世界,F(xiàn)lutter正在被越來越多的開發(fā)者和組織使用,并且Flutter是完全免費(fèi)、開源的。Flutter采用現(xiàn)代響應(yīng)式框架構(gòu)建,其中心思想是使用組件來構(gòu)建應(yīng)用的UI。當(dāng)組件的狀態(tài)發(fā)生改變時,組件會重構(gòu)它的描述,F(xiàn)lutter會對比之前的描述,以確定底層渲染樹從當(dāng)前狀態(tài)轉(zhuǎn)換到下一個狀態(tài)所需要的最小更改。
優(yōu)點(diǎn)
- 熱重載(Hot Reload),利用Android Studio直接一個ctrl+s就可以保存并重載,模擬器立馬就可以看見效果,相比原生冗長的編譯過程強(qiáng)很多;
- 一切皆為Widget的理念,對于Flutter來說,手機(jī)應(yīng)用里的所有東西都是Widget,通過可組合的空間集合、豐富的動畫庫以及分層課擴(kuò)展的架構(gòu)實(shí)現(xiàn)了富有感染力的靈活界面設(shè)計;
- 借助可移植的GPU加速的渲染引擎以及高性能本地代碼運(yùn)行時以達(dá)到跨平臺設(shè)備的高質(zhì)量用戶體驗(yàn)。 簡單來說就是:最終結(jié)果就是利用Flutter構(gòu)建的應(yīng)用在運(yùn)行效率上會和原生應(yīng)用差不多。
缺點(diǎn)
- 不支持熱更新;
- 三方庫有限,需要自己造輪子;
- Dart語言編寫,增加了學(xué)習(xí)難度,并且學(xué)習(xí)了Dart之后無其他用處,相比JS和Java來說。
2. 介紹下Flutter的理念架構(gòu)
其實(shí)也就是下面這張圖。

由上圖可知,F(xiàn)lutter框架自下而上分為Embedder、Engine和Framework三層。其中,Embedder是操作系統(tǒng)適配層,實(shí)現(xiàn)了渲染 Surface設(shè)置,線程設(shè)置,以及平臺插件等平臺相關(guān)特性的適配;Engine層負(fù)責(zé)圖形繪制、文字排版和提供Dart運(yùn)行時,Engine層具有獨(dú)立虛擬機(jī),正是由于它的存在,F(xiàn)lutter程序才能運(yùn)行在不同的平臺上,實(shí)現(xiàn)跨平臺運(yùn)行;Framework層則是使用Dart編寫的一套基礎(chǔ)視圖庫,包含了動畫、圖形繪制和手勢識別等功能,是使用頻率最高的一層。
3. 介紹下FFlutter的FrameWork層和Engine層,以及它們的作用
Flutter的FrameWork層是用Drat編寫的框架(SDK),它實(shí)現(xiàn)了一套基礎(chǔ)庫,包含Material(Android風(fēng)格UI)和Cupertino(iOS風(fēng)格)的UI界面,下面是通用的Widgets(組件),之后是一些動畫、繪制、渲染、手勢庫等。這個純 Dart實(shí)現(xiàn)的 SDK被封裝為了一個叫作 dart:ui的 Dart庫。我們在使用 Flutter寫 App的時候,直接導(dǎo)入這個庫即可使用組件等功能。
Flutter的Engine層是Skia 2D的繪圖引擎庫,其前身是一個向量繪圖軟件,Chrome和 Android均采用 Skia作為繪圖引擎。Skia提供了非常友好的 API,并且在圖形轉(zhuǎn)換、文字渲染、位圖渲染方面都提供了友好、高效的表現(xiàn)。Skia是跨平臺的,所以可以被嵌入到 Flutter的 iOS SDK中,而不用去研究 iOS閉源的 Core Graphics / Core Animation。Android自帶了 Skia,所以 Flutter Android SDK要比 iOS SDK小很多。
4. 介紹下Widget、State、Context 概念
- Widget:在Flutter中,幾乎所有東西都是Widget。將一個Widget想象為一個可視化的組件(或與應(yīng)用可視化方面交互的組件),當(dāng)你需要構(gòu)建與布局直接或間接相關(guān)的任何內(nèi)容時,你正在使用Widget。
- Widget樹:Widget以樹結(jié)構(gòu)進(jìn)行組織。包含其他Widget的widget被稱為父Widget(或widget容器)。包含在父widget中的widget被稱為子Widget。
- Context:僅僅是已創(chuàng)建的所有Widget樹結(jié)構(gòu)中的某個Widget的位置引用。簡而言之,將context作為widget樹的一部分,其中context所對應(yīng)的widget被添加到此樹中。一個context只從屬于一個widget,它和widget一樣是鏈接在一起的,并且會形成一個context樹。
- State:定義了StatefulWidget實(shí)例的行為,它包含了用于”交互/干預(yù)“Widget信息的行為和布局。應(yīng)用于State的任何更改都會強(qiáng)制重建Widget。
5. 簡述Widget的StatelessWidget和StatefulWidget兩種狀態(tài)組件類
StatelessWidget: 一旦創(chuàng)建就不關(guān)心任何變化,在下次構(gòu)建之前都不會改變。它們除了依賴于自身的配置信息(在父節(jié)點(diǎn)構(gòu)建時提供)外不再依賴于任何其他信息。比如典型的Text、Row、Column、Container等,都是StatelessWidget。它的生命周期相當(dāng)簡單:初始化、通過build()渲染。
StatefulWidget: 在生命周期內(nèi),該類Widget所持有的數(shù)據(jù)可能會發(fā)生變化,這樣的數(shù)據(jù)被稱為State,這些擁有動態(tài)內(nèi)部數(shù)據(jù)的Widget被稱為StatefulWidget。比如復(fù)選框、Button等。State會與Context相關(guān)聯(lián),并且此關(guān)聯(lián)是永久性的,State對象將永遠(yuǎn)不會改變其Context,即使可以在樹結(jié)構(gòu)周圍移動,也仍將與該context相關(guān)聯(lián)。當(dāng)state與context關(guān)聯(lián)時,state被視為已掛載。StatefulWidget由兩部分組成,在初始化時必須要在createState()時初始化一個與之相關(guān)的State對象。
6. StatefulWidget 的生命周期
Flutter的Widget分為StatelessWidget和StatefulWidget兩種。其中,StatelessWidget是無狀態(tài)的,StatefulWidget是有狀態(tài)的,因此實(shí)際使用時,更多的是StatefulWidget。StatefulWidget的生命周期如下圖。

- initState():Widget 初始化當(dāng)前 State,在當(dāng)前方法中是不能獲取到 Context 的,如想獲取,可以試試 Future.delayed()
- didChangeDependencies():在 initState() 后調(diào)用,State對象依賴關(guān)系發(fā)生變化的時候也會調(diào)用。
- deactivate():當(dāng) State 被暫時從視圖樹中移除時會調(diào)用這個方法,頁面切換時也會調(diào)用該方法,和Android里的 onPause 差不多。
- dispose():Widget 銷毀時調(diào)用。
- didUpdateWidget:Widget 狀態(tài)發(fā)生變化的時候調(diào)用。
7. 簡述Widgets、RenderObjects 和 Elements的關(guān)系
首先看一下這幾個對象的含義及作用。
- Widget :僅用于存儲渲染所需要的信息。
- RenderObject :負(fù)責(zé)管理布局、繪制等操作。
- Element :才是這顆巨大的控件樹上的實(shí)體。
Widget會被inflate(填充)到Element,并由Element管理底層渲染樹。Widget并不會直接管理狀態(tài)及渲染,而是通過State這個對象來管理狀態(tài)。Flutter創(chuàng)建Element的可見樹,相對于Widget來說,是可變的,通常界面開發(fā)中,我們不用直接操作Element,而是由框架層實(shí)現(xiàn)內(nèi)部邏輯。就如一個UI視圖樹中,可能包含有多個TextWidget(Widget被使用多次),但是放在內(nèi)部視圖樹的視角,這些TextWidget都是填充到一個個獨(dú)立的Element中。Element會持有renderObject和widget的實(shí)例。記住,Widget 只是一個配置,RenderObject 負(fù)責(zé)管理布局、繪制等操作。
在第一次創(chuàng)建 Widget 的時候,會對應(yīng)創(chuàng)建一個 Element, 然后將該元素插入樹中。如果之后 Widget 發(fā)生了變化,則將其與舊的 Widget 進(jìn)行比較,并且相應(yīng)地更新 Element。重要的是,Element 不會被重建,只是更新而已。
8. 什么是狀態(tài)管理,你了解哪些狀態(tài)管理框架?
Flutter中的狀態(tài)和前端React中的狀態(tài)概念是一致的。React框架的核心思想是組件化,應(yīng)用由組件搭建而成,組件最重要的概念就是狀態(tài),狀態(tài)是一個組件的UI數(shù)據(jù)模型,是組件渲染時的數(shù)據(jù)依據(jù)。
Flutter的狀態(tài)可以分為全局狀態(tài)和局部狀態(tài)兩種。常用的狀態(tài)管理有ScopedModel、BLoC、Redux / FishRedux和Provider。詳細(xì)使用情況和差異可以自行了解。
9. 簡述Flutter的繪制流程
Flutter的繪制流程如下圖所示。

Flutter只關(guān)心向 GPU提供視圖數(shù)據(jù),GPU的 VSync信號同步到 UI線程,UI線程使用 Dart來構(gòu)建抽象的視圖結(jié)構(gòu),這份數(shù)據(jù)結(jié)構(gòu)在 GPU線程進(jìn)行圖層合成,視圖數(shù)據(jù)提供給 Skia引擎渲染為 GPU數(shù)據(jù),這些數(shù)據(jù)通過 OpenGL或者 Vulkan提供給 GPU。
10. 簡述Flutter的線程管理模型
默認(rèn)情況下,F(xiàn)lutter Engine層會創(chuàng)建一個Isolate,并且Dart代碼默認(rèn)就運(yùn)行在這個主Isolate上。必要時可以使用spawnUri和spawn兩種方式來創(chuàng)建新的Isolate,在Flutter中,新創(chuàng)建的Isolate由Flutter進(jìn)行統(tǒng)一的管理。
事實(shí)上,F(xiàn)lutter Engine自己不創(chuàng)建和管理線程,F(xiàn)lutter Engine線程的創(chuàng)建和管理是Embeder負(fù)責(zé)的,Embeder指的是將引擎移植到平臺的中間層代碼,F(xiàn)lutter Engine層的架構(gòu)示意圖如下圖所示。

在Flutter的架構(gòu)中,Embeder提供四個Task Runner,分別是Platform Task Runner、UI Task Runner Thread、GPU Task Runner和IO Task Runner,每個Task Runner負(fù)責(zé)不同的任務(wù),F(xiàn)lutter Engine不在乎Task Runner運(yùn)行在哪個線程,但是它需要線程在整個生命周期里面保持穩(wěn)定。
11. Flutter 是如何與原生Android、iOS進(jìn)行通信的?
Flutter 通過 PlatformChannel 與原生進(jìn)行交互,其中 PlatformChannel 分為三種:
- BasicMessageChannel :用于傳遞字符串和半結(jié)構(gòu)化的信息。
- MethodChannel :用于傳遞方法調(diào)用(method invocation)。
- EventChannel : 用于數(shù)據(jù)流(event streams)的通信。
12. 簡述Flutter 的熱重載
Flutter 的熱重載是基于 JIT 編譯模式的代碼增量同步。由于 JIT 屬于動態(tài)編譯,能夠?qū)?Dart 代碼編譯成生成中間代碼,讓 Dart VM 在運(yùn)行時解釋執(zhí)行,因此可以通過動態(tài)更新中間代碼實(shí)現(xiàn)增量同步。
熱重載的流程可以分為 5 步,包括:掃描工程改動、增量編譯、推送更新、代碼合并、Widget 重建。Flutter 在接收到代碼變更后,并不會讓 App 重新啟動執(zhí)行,而只會觸發(fā) Widget 樹的重新繪制,因此可以保持改動前的狀態(tài),大大縮短了從代碼修改到看到修改產(chǎn)生的變化之間所需要的時間。
另一方面,由于涉及到狀態(tài)的保存與恢復(fù),涉及狀態(tài)兼容與狀態(tài)初始化的場景,熱重載是無法支持的,如改動前后 Widget 狀態(tài)無法兼容、全局變量與靜態(tài)屬性的更改、main 方法里的更改、initState 方法里的更改、枚舉和泛型的更改等。
可以發(fā)現(xiàn),熱重載提高了調(diào)試 UI 的效率,非常適合寫界面樣式這樣需要反復(fù)查看修改效果的場景。但由于其狀態(tài)保存的機(jī)制所限,熱重載本身也有一些無法支持的邊界。