Flutter 面試知識點集錦

戀貓de小郭發表於2019-05-13

谷歌大會之後,有不少人諮詢了我 Flutter 相關的問題,其中有不少是和麵試相關的,如今一些招聘上也開始羅列 Flutter 相關要求,最後想了想還是寫一期總結吧,也算是 Flutter 的階段複習。

⚠️系統完整的學習是必須需要的,這裡只能幫你總結一些知識點,更多的還請查閱 Dart/Flutter 官網。

本篇主要是知識點總結,如有疑問可點選各文章連結瞭解詳情,或者查閱我 掘金專欄

Dart 部分

其實學習過 JavaScript 或者 Java/Kotlin 的人,在學習 Dart 上幾乎是沒什麼難度的,Dart 綜合了動態語言和靜態語言的特性, 這裡主要提供一些不一樣,或者有意思的概念。

  • 1、Dart 屬於是強型別語言 ,但可以用 var 來宣告變數,Dart自推匯出資料型別var 實際上是編譯期的“語法糖”。dynamic 表示動態型別, 被編譯後,實際是一個 object 型別,在編譯期間不進行任何的型別檢查,而是在執行期進行型別檢查。

  • 2、Dartif 等語句只支援 bool 型別,switch 支援 String 型別。

  • 3、Dart陣列和 List 是一樣的。

  • 4、Dart 中,Runes 代表符號文字 , 是 UTF-32 編碼的字串, 用於如 Runes input = new Runes('\u{1f596} \u{1f44d}');

  • 5、Dart 支援閉包。

  • 6、Dart 中 number 型別分為 int 和 double ,沒有 float 型別。

  • 7、Dart級聯操作符 可以方便配置邏輯,如下程式碼:

event
  ..id = 1
  ..type = ""
  ..actor = "";
複製程式碼
  • 8、賦值操作符

比較有意思的賦值操作符有:

AA ?? "999"  ///表示如果 AA 為空,返回999
AA ??= "999" ///表示如果 AA 為空,給 AA 設定成 999
AA ~/999 ///AA 對於 999 整除
複製程式碼
  • 9、可選方法引數

Dart 方法可以設定 引數預設值指定名稱

比如: getDetail(Sting userName, reposName, {branch = "master"}){} 方法,這裡 branch 不設定的話,預設是 “master” 。引數型別 可以指定或者不指定。呼叫效果: getRepositoryDetailDao(“aaa", "bbbb", branch: "dev");

  • 10、作用域

Dart 沒有關鍵詞 publicprivate 等修飾符,_ 下橫向直接代表 private ,但是有 @protected 註解 。

  • 11、構造方法

Dart 中的多構造方法,可以通過命名方法實現。

預設構造方法只能有一個,而通過 Model.empty() 方法可以建立一個空引數的類,其實方法名稱隨你喜歡,而變數初始化值時,只需要通過 this.name 在構造方法中指定即可:

class ModelA {
  String name;
  String tag;
  
  //預設構造方法,賦值給name和tag
  ModelA(this.name, this.tag);

  //返回一個空的ModelA
  ModelA.empty();
  
  //返回一個設定了name的ModelA
  ModelA.forName(this.name);
}
複製程式碼
  • 12、getter setter 重寫

Dart 中所有的基礎型別、類等都繼承 Object ,預設值是 NULL, 自帶 gettersetter ,而如果是 final 或者 const 的話,那麼它只有一個 getter 方法,Object 都支援 getter、setter 重寫:

  @override
  Size get preferredSize {
    return Size.fromHeight(kTabHeight + indicatorWeight);
  }
複製程式碼
  • 13、Assert(斷言)

assert 只在檢查模式有效,在開發過程中,assert(unicorn == null); 只有條件為真才正常,否則直接丟擲異常,一般用在開發過程中,某些地方不應該出現什麼狀態的判斷。

  • 14、重寫運算子,如下所示過載 operator 後對類進行 +/- 操作。
class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
  
  ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}
複製程式碼

支援過載的操作符 :

Flutter 面試知識點集錦

  • 類、介面、繼承

Dart 中沒有介面,類都可以作為介面,把某個類當做介面實現時,只需要使用 implements ,然後複寫父類方法即可。

Dart 中支援 mixins ,按照出現順序應該為extendsmixinsimplements

  • Zone

Dart 中可通過 Zone 表示指定程式碼執行的環境,類似一個沙盒概念,在 FlutterC++ 執行 Dart 也是在 _runMainZoned 內執行 runZoned 方法啟動,而我們也可以通過 Zone ,在執行環境內捕獲全域性異常等資訊:

  runZoned(() {
    runApp(FlutterReduxApp());
  }, onError: (Object obj, StackTrace stack) {
    print(obj);
    print(stack);
  });
複製程式碼

同時你可以給 runZoned 註冊方法,在需要時執行回撥,如下程式碼所示,這樣的在一個 Zone 內任何地方,只要能獲取 onData 這個 ZoneUnaryCallback,就都可以呼叫到 handleData

///最終需要處理的地方
handleData(result) {
  print("VVVVVVVVVVVVVVVVVVVVVVVVVVV");
  print(result);
}

///返回得到一個 ZoneUnaryCallback 
var onData = Zone.current.registerUnaryCallback<dynamic, int>(handleData);

///執行 ZoneUnaryCallback 返回資料
Zone.current.runUnary(onData, 2);

複製程式碼

非同步邏輯可以通過 scheduleMicrotask 可以插入非同步執行方法:

Zone.current.scheduleMicrotask((){
  //todo something
});
複製程式碼

更多可參看 :《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》

  • Future

Future 簡單了說就是對 Zone 的封裝使用。

比如 Future.microtask 中主要是執行了 ZonescheduleMicrotask ,而 result._complete 最後呼叫的是 _zone.runUnary 等等。

  factory Future.microtask(FutureOr<T> computation()) {
    _Future<T> result = new _Future<T>();
    scheduleMicrotask(() {
      try {
        result._complete(computation());
      } catch (e, s) {
        _completeWithErrorCallback(result, e, s);
      }
    });
    return result;
  }

複製程式碼

Dart 中可通過 async/await 或者 Future 定義非同步操作,而事實上 async/await 也只是語法糖,最終還是通過編譯器轉為 Future

有興趣看這裡 :

generators

code_generator.dart

Flutter完整開發實戰詳解(十一、全面深入理解Stream)

  • Stream

Stream 也是有對Zone 的另外一種封裝使用。

Dart 中另外一種非同步操作, async* / yield 或者 Stream 可定義 Stream 非同步, async* / yield 也只是語法糖,最終還是通過編譯器轉為 Stream Stream 還支援同步操作。

1)、Stream 中主要有 StreamStreamControllerStreamSinkStreamSubscription 四個關鍵物件,大致可以總結為:

  • StreamController :如類名描述,用於整個 Stream 過程的控制,提供各類介面用於建立各種事件流。

  • StreamSink :一般作為事件的入口,提供如 addaddStream 等。

  • Stream :事件源本身,一般可用於監聽事件或者對事件進行轉換,如 listenwhere

  • StreamSubscription :事件訂閱後的物件,表面上用於管理訂閱過等各類操作,如 cacenlpause ,同時在內部也是事件的中轉關鍵。

2)、一般通過 StreamController 建立 Stream;通過 StreamSink 新增事件;通過 Stream 監聽事件;通過 StreamSubscription 管理訂閱。

3)、Stream 中支援各種變化,比如mapexpandwheretake 等操作,同時支援轉換為 Future

更多可參看 :《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》

Flutter 部分

Flutter 和 React Native 不同主要在於 Flutter UI是直接通過 skia 渲染的 ,而 React Native 是將 js 中的控制元件轉化為原生控制元件,通過原生去渲染的 ,相關更多可檢視:《移動端跨平臺開發的深度解析》

  • Flutter 中存在 WidgetElementRenderObjectLayer 四棵樹,其中 WidgetElement 是多對一的關係

  • Element 中持有WidgetRenderObject , 而 ElementRenderObject 是一一對應的關係

  • RenderObjectisRepaintBoundarytrue 時,那麼個區域形成一個 Layer,所以不是每個 RenderObject 都具有 Layer 的,因為這受 isRepaintBoundary 的影響。

更多相關可查閱 《Flutter完整開發實戰詳解(九、 深入繪製原理)》

  • Flutter 中 Widget 不可變,每次保持在一幀,如果發生改變是通過 State 實現跨幀狀態儲存,而真實完成佈局和繪製陣列的是 RenderObject Element 充當兩者的橋樑, State 就是儲存在 Element 中。

  • Flutter 中的 BuildContext 只是介面,而 Element 實現了它。

  • Flutter 中 setState 其實是呼叫了 markNeedsBuild ,該方法內部標記此ElementDirty ,然後在下一幀 WidgetsBinding.drawFrame 才會被繪製,這可以看出 setState 並不是立即生效的。

  • Flutter 中 RenderObjectattch/layout 之後會通過 markNeedsPaint(); 使得頁面重繪,流程大概如下:

Flutter 面試知識點集錦

通過isRepaintBoundary 往上確定了更新區域,通過 requestVisualUpdate 方法觸發更新往下繪製。

  • 正常情況 RenderObject 的佈局相關方法呼叫順序是 : layout -> performResize -> performLayout -> markNeedsPaint , 但是使用者一般不會直接呼叫 layout,而是通過 markNeedsLayout ,具體流程如下:

Flutter 面試知識點集錦

  • Flutter 中一般 json 資料從 String 轉為 Object 的過程中都需要先經過 Map 型別。

  • Flutter 中 InheritedWidget 一般用於狀態共享,如ThemeLocalizationsMediaQuery 等,都是通過它實現共享狀態,這樣我們可以通過 context 去獲取共享的狀態,比如 ThemeData theme = Theme.of(context);

ElementinheritFromWidgetOfExactType 方法實現裡,有一個 Map<Type, InheritedElement> _inheritedWidgets 的物件。

_inheritedWidgets 一般情況下是空的,只有當父控制元件是 InheritedWidget 或者本身是 InheritedWidgets 時才會有被初始化,而當父控制元件是 InheritedWidget 時,這個 Map 會被一級一級往下傳遞與合併 。

所以當我們通過 context 呼叫 inheritFromWidgetOfExactType 時,就可以往上查詢到父控制元件的 Widget

  • Flutter 中預設主要通過 runtimeTypekey 判斷更新:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}
複製程式碼

Flutter 中的生命週期

  • initState() 表示當前 State 將和一個 BuildContext 產生關聯,但是此時BuildContext 沒有完全裝載完成,如果你需要在該方法中獲取 BuildContext ,可以 new Future.delayed(const Duration(seconds: 0, (){//context}); 一下。

  • didChangeDependencies()initState() 之後呼叫,當 State 物件的依賴關係發生變化時,該方法被呼叫,初始化時也會呼叫。

  • deactivate()State 被暫時從檢視樹中移除時,會呼叫這個方法,同時頁面切換時,也會呼叫。

  • dispose() Widget 銷燬了,在呼叫這個方法之前,總會先呼叫 deactivate()。

  • didUpdateWidgewidget 狀態發生變化時,會呼叫。

Flutter 面試知識點集錦


  • 通過 StreamBuilderFutureBuilder 我們可以快速使用 StreamFuture 快速構建我們的非同步控制元件: 《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》

  • Flutter 中 runApp 啟動入口其實是一個 WidgetsFlutterBinding ,它主要是通過 BindingBase 的子類 GestureBindingServicesBindingSchedulerBindingPaintingBindingSemanticsBindingRendererBindingWidgetsBinding 等,通過 mixins 的組合而成的。

  • Flutter 中的 Dart 的執行緒是以事件迴圈和訊息佇列的形式存在,包含兩個任務佇列,一個是 microtask 內部佇列,一個是 event 外部佇列,而 microtask 的優先順序又高於 event 。

因為 microtask 的優先順序又高於 event, 同時會阻塞event 佇列,所以如果 microtask 太多就可能會對觸控、繪製等外部事件造成阻塞卡頓哦。

  • Flutter 中存在四大執行緒,分別為 UI RunnerGPU RunnerIO RunnerPlatform Runner (原生主執行緒) ,同時在 Flutter 中可以通過 isolate 或者 compute 執行真正的跨執行緒非同步操作。

PlatformView

Flutter 中通過 PlatformView 可以巢狀原生 ViewFlutter UI 中,這裡面其實是使用了 Presentation + VirtualDisplay + Surface 等實現的,大致原理就是:

使用了類似副屏顯示的技術,VirtualDisplay 類代表一個虛擬顯示器,呼叫 DisplayManagercreateVirtualDisplay() 方法,將虛擬顯示器的內容渲染在一個 Surface 控制元件上,然後將 Surface 的 id 通知給 Dart,讓 engine 繪製時,在記憶體中找到對應的 Surface 畫面記憶體資料,然後繪製出來。em... 實時控制元件截圖渲染顯示技術。


  • Flutter 的 Debug 下是 JIT 模式,release下是AOT模式。

  • Flutter 中可以通過 mixins AutomaticKeepAliveClientMixin ,然後重寫 wantKeepAlive 保持住頁面,記得在被保持住的頁面 build 中呼叫 super.build 。(因為 mixins 特性)。

  • Flutter 手勢事件主要是通過競技判斷的:

主要有 hitTest 把所有需要處理的控制元件對應的 RenderObject , 從 childparent 全部組合成列表,從最裡面一直新增到最外層。

然後從佇列頭的 child 開始 for 迴圈執行 handleEvent 方法,執行 handleEvent 的過程不會被攔截打斷。

一般情況下 Down 事件不會決出勝利者,大部分時候是在 MOVE 或者 UP 的時候才會決出勝利者。

競技場關閉時只有一個的就直接勝出響應,沒有勝利者就拿排在佇列第一個強制勝利響應。

同時還有 didExceedDeadline 處理按住時的 Down 事件額外處理,同時手勢處理一般在 GestureRecognizer 的子類進行。

更多詳細請檢視:《Flutter完整開發實戰詳解(十三、全面深入觸控和滑動原理)》

Platform Channel

Flutter 中可以通過 Platform Channel 讓 Dart 程式碼和原生程式碼通訊的:

  • BasicMessageChannel :用於傳遞字串和半結構化的資訊。
  • MethodChannel :用於傳遞方法呼叫(method invocation)。
  • EventChanne l: 用於資料流(event streams)的通訊。

同時 Platform Channel 並非是執行緒安全的 ,更多詳細可查閱閒魚技術的 《深入理解Flutter Platform Channel》

其中基礎資料型別對映如下:

Flutter 面試知識點集錦


Android 啟動頁

Android 中 Flutter 預設啟動時會在 FlutterActivityDelegate.java 中讀取 AndroidManifset.xml 內 meta-data 標籤,其中 io.flutter.app.android.SplashScreenUntilFirstFrame 標誌位如果為 ture ,就會啟動 Splash 畫面效果(類似IOS的啟動頁面)。

啟動時原生程式碼會讀取 android.R.attr.windowBackground 得到指定的 Drawable , 用於顯示啟動閃屏效果,之後並且通過 flutterView.addFirstFrameListener,在onFirstFrame 中移除閃屏。

好了,暫時都這裡了,有問題修改會或則補充的,後面再加上。

資源推薦

完整開源專案推薦:
文章

《Flutter完整開發實戰詳解(一、Dart語言和Flutter基礎)》

《Flutter完整開發實戰詳解(二、 快速開發實戰篇)》

《Flutter完整開發實戰詳解(三、 打包與填坑篇)》

《Flutter完整開發實戰詳解(四、Redux、主題、國際化)》

《Flutter完整開發實戰詳解(五、 深入探索)》

《Flutter完整開發實戰詳解(六、 深入Widget原理)》

《Flutter完整開發實戰詳解(七、 深入佈局原理)》

《Flutter完整開發實戰詳解(八、 實用技巧與填坑)》

《Flutter完整開發實戰詳解(九、 深入繪製原理)》

《Flutter完整開發實戰詳解(十、 深入圖片載入流程)》

《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》

《Flutter完整開發實戰詳解(十二、全面深入理解狀態管理設計)》

《Flutter完整開發實戰詳解(十三、全面深入觸控和滑動原理)》

《跨平臺專案開源專案推薦》

《移動端跨平臺開發的深度解析》

我們還會再見嗎?

相關文章