Flutter App 軟件調試指南

前言

推薦:Android學習PDF+架構視頻+面試文檔+源碼筆記android

在實際開發中,測試和調試所佔的時間比例,在總開發時間中仍是比較高的。在修復產品缺陷時,咱們一般須要實時觀察某個對象的值。雖然能夠經過Log的形式進行輸出,但在某些情形下,使用更好的調試工具可使觀察這些值變得更加方便。
想象一下,若是須要觀察一個集合,或者一個對象中全部變量的值,單純地使用Log須要怎麼作?
可能會想到用循環,也可能會在輸出Log的代碼中屢次運用「.」運算符對對象內的變量取值。這使得編寫Log輸出語句自己變得複雜,再加上可能還會冒着空指針的風險。面試

本文涵蓋了 Flutter App 代碼的全部調試方式,經過本場 Chat 的學習,您將會獲得如下知識:架構

  1. 認識 Dart 語言檢查器;
  2. 如何在 IDE 中進行單步調試;
  3. 打印 Log 的技巧;
  4. 利用 Dart 語言中的「斷言」;
  5. 如何查看界面 Widget 樹形層級;
  6. 怎樣獲取語義樹。

下面咱們來逐一進行學習。app

認識 Dart 語言檢查器

在運行應用程序前,使用Dart語言檢查器,經過分析代碼,能夠幫助開發者排除一些代碼隱患。
固然,若是讀者使用的是Android Studio,Dart檢查器在默認狀況下會自動啓用。
若要手動測試代碼,能夠在工程根目錄下執行:框架

flutter analyze

命令,檢查結果會稍後顯示在命令行對話框中。 好比,在默認新建的計數器應用中,去掉一個語句結尾的分號:less

void _incrementCounter() {
  setState(() {
    _counter++
  });
}

看到_counter++後面少了一個分號了嗎?
此時,運行Dart檢查器,命令行輸出:ide

error - Expected to find ';' - lib\main.dart:32:15 - expected_token
1 issue found. (ran in 8.9s)

如何在 IDE 中進行單步調試

在某些時候,咱們須要進行單步調試。單步調試可讓程序逐條語句地進行,並能夠看到當前運行的位置。另外,在單步調試過程當中,還能實時關注相應範圍內全部變量值的詳細變化過程。工具

Android Studio中提供了單步調試功能。這和開發原生Android平臺App時的單步調試方法同樣,其具體步驟能夠分爲三步進行,第一步是標記斷點,第二步是運行程序到斷點處,第三步則是使用Debug工具進行調試。
下面以默認的計數器應用爲例,觀察代碼中_counter值的變化,體會單步調試的全過程。性能

第一步是標記斷點,既然要觀察_counter值的變化,則在每次_counter值發生變化後添加斷點,觀察數值變化是最理想的,所以在行號稍右側點擊鼠標,把斷點加載下圖所示的位置。學習

在這裏插入圖片描述

添加斷點後,相應的行號右側將會出現圓形的斷點標記,而且整行將會高亮顯示。

到此,斷點就添加好了,固然,還能夠同時添加多個斷點,以便實現多個位置的調試。

接下來則是運行程序。和以前的運行方式不一樣,這一次須要以調試模式啓動App。方法是點擊Android Studio上方工具欄的小蟲子圖標,以下圖所示:

在這裏插入圖片描述

稍等片刻,程序就啓動了。因爲咱們添加斷點的位置在程序啓動後會被當即運行到,所以,無需其餘操做,便可進入調試視圖。若是斷點位置並非在程序一啓動就執行,則須要手動讓程序運行到斷點位置。
下圖展現了代碼運行到斷點位置時的IDE視圖,它自動進入了Debug視圖模式:

在這裏插入圖片描述

這裏介紹兩種方法來獲取_counter的值,一種是在代碼處,經過執行表達式的方式,以下圖所示:

在這裏插入圖片描述

在相應的變量上點右鍵,接着在彈出的菜單中選擇計算表達式(Evaluate Expression),最後在彈出的對話框中點擊Evaluate按鈕,獲得運算結果以下圖所示:

在這裏插入圖片描述

除此以外,若是所取得值是一個對象,還能夠在該窗口上方的表達式輸入框中使用「.」運算符調用該對象的方法,獲取相應的運算結果。
另一種方式則是經過窗口下方的調試視圖,在這裏會有較爲完整的變量值顯示,它們以樹形的方式呈現。
咱們能夠依次展開這個樹形結構,找到_counter值,以下圖所示:

在這裏插入圖片描述

接下來,保持App繼續運行,而後點擊界面右下角的FloatingActionButton,驗證一下點擊後_counter值的準確性。此時,須要點擊「運行到下一個斷點處」按鈕,以下圖所示:

在這裏插入圖片描述

點擊該按鈕後,程序會繼續運行,直到下一個斷點處。

本例只添加了一個斷點,所以,程序再次停留在此處。以下圖所示,點擊FloatingActionButton後,程序再次停留在斷點位置,此時,_counter值已經發生了變化,完成了自增1的計算,結果無誤。

在這裏插入圖片描述

 咱們能夠在任什麼時候候退出調試模式,只需點擊中止運行按鈕便可,它位於啓動調試模式按鈕的右側。

在這裏插入圖片描述

點擊該按鈕後,Android Studio會退出調試模式,運行在設備上的程序也會被強制關閉。

打印 Log 的技巧;

爲了跟蹤和記錄軟件的運行狀況,開發者們一般會輸出一些日誌(Log),這些日誌對於用戶而言是不可見的。傳統的iOS和Android平臺都提供了完善的日誌輸出功能,Flutter也不例外。要實時查看Flutter的日誌,只需在控制檯中輸入:

flutter logs

便可。Android Studio和Visual Studio Code中,默認集成了控制檯(console),使用這個集成的控制檯或者啓動一個新的控制檯皆可。這裏要注意的是,一旦執行了上面的命令,該控制檯將會進入獨佔狀態,即沒法再使用其餘的命令了,除非中斷Log查看。

當咱們想要在程序運行得某個地方輸出Log時,一般使用debugPrint()方法。結合以前的示例,修改原先的main()方法,添加一個Log輸出,內容爲「我開始啓動了」,未經修改的代碼:

void main() => runApp(MyApp());

添加Log後的代碼:

void main() {
  debugPrint("我開始啓動了");
  runApp(MyApp());
}

在控制檯中使用flutter logs命令監視Log輸出,而後從新安裝並運行程序,控制檯輸出:

I/flutter (12705): 我開始啓動了

結果如圖所示:

在這裏插入圖片描述

要結束監視Log輸出,可以使用Control + C組合鍵,而後輸入y,回車確認,也可直接關閉控制檯。最後,須要注意的是,爲了保證Log輸出正確無誤,建議各位讀者使用英文輸出,而不是直接使用中文。由於在某些狀況下,可能會致使顯示亂碼。經測試,在英文版的Windows下啓動命令提示符,並執行上例,會獲得以下輸出:

I/flutter (13320): 我开始启动了

利用 Dart 語言中的「斷言」;

Dart運行時提供兩種運行方式:Production和Checked。默認狀況下會以Production模式運行,在這種條件下,優先考慮性能,關閉類型檢查和斷言;反之,Checked模式更利於開發階段調試使用。
斷言能夠檢查程序中某些可能出現的運行時邏輯錯誤。好比下面這段代碼:

// assert
var intValue = 300;
assert(intValue == 299);

很明顯,intValue不知足和299相等的條件,此時在開發環境中運行程序,將看到控制檯報錯。而一旦切換到生產模式,則不會收到任何錯誤提示。這對於檢查代碼中某些隱含的邏輯問題十分有效。

如何查看界面 Widget 樹形層級;

Flutter框架中的每一層都提供了轉儲當前狀態或事件的方式,這些轉儲的信息將經過debugPrint()方式輸出到控制檯上。
下面就讓咱們逐層探究,瞭解其Log日誌轉儲的方法和內容。

組件層

要轉儲組件層的狀態,須要調用debugdumpapp()方法。
保證該方法取到有效Log的前提是要確保該App至少構建了一次組件,且不要在build()過程當中調用。
咱們以新建的計數器應用爲例,添加一個建立組件層日誌轉儲的按鈕,並在用戶點擊該按鈕後,執行debugdumpapp()方法。具體實現以下:

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            RaisedButton(
                onPressed: () => debugDumpApp(),
                child: Text("Create app dump")),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

注意新添加的RaisedButton。
運行上述代碼,而後點擊該按鈕,能夠看到控制檯以下輸出(節選):

I/flutter ( 4489): WidgetsFlutterBinding - CHECKED MODE
I/flutter ( 4489): [root](renderObject: RenderView#d27b1)
I/flutter ( 4489): └MyApp
I/flutter ( 4489):  └MaterialApp(state: _MaterialAppState#51668)
I/flutter ( 4489):   └ScrollConfiguration(behavior: _MaterialScrollBehavior)
I/flutter ( 4489):    └WidgetsApp-[GlobalObjectKey _MaterialAppState#51668](state: 
_WidgetsAppState#04e30)
I/flutter ( 4489):     └MediaQuery(MediaQueryData(size: Size(411.4, 797.7), devicePixelRatio: 2.6, 
textScaleFactor: 1.1, platformBrightness: Brightness.light, padding: EdgeInsets(0.0, 24.0, 0.0, 0.0), 
viewInsets: EdgeInsets.zero, alwaysUse24HourFormat: true, accessibleNavigation: 
falsedisableAnimations: falseinvertColors: falseboldText: false))
I/flutter ( 4489):      └Localizations(locale: en_US, delegates: 
[DefaultMaterialLocalizations.delegate(en_US), DefaultCupertinoLocalizations.delegate(en_US), 
DefaultWidgetsLocalizations.delegate(en_US)], state: _LocalizationsState#c0c98)
I/flutter ( 4489):       └Semantics(container: false, properties: SemanticsProperties, label: null, value: 
null, hint: null, textDirection: ltr, hintOverrides: null, renderObject: RenderSemanticsAnnotations#31c77)
I/flutter ( 4489):        └_LocalizationsScope-[GlobalKey#60b05]
I/flutter ( 4489):         └Directionality(textDirection: ltr)
I/flutter ( 4489):          └Title(title: "Flutter Demo", color: MaterialColor(primary value: 
Color(0xff2196f3)))
I/flutter ( 4489):           └CheckedModeBanner("DEBUG")
I/flutter ( 4489):            └Banner("DEBUG", textDirection: ltr, location: topEnd, Color(0xa0b71c1c), 
text inherit: true, text color: Color(0xffffffff), text size: 10.2, text weight: 900, text height: 1.0x, dependencies: 
[Directionality])
I/flutter ( 4489):             └CustomPaint(renderObject: RenderCustomPaint#c2a34)
I/flutter ( 4489):              └DefaultTextStyle(debugLabel: fallback style; consider putting your text in a 
Material, inherit: true, color: Color(0xd0ff0000), family: monospace, size: 48.0, weight: 900, decoration: 
double Color(0xffffff00) TextDecoration.underline, softWrap: wrapping at box width, overflow: clip)
I/flutter ( 4489):               └Builder(dependencies: [MediaQuery])
……

實際的輸出內容要比上述節選部分多好幾倍。

這些內容乍看上去彷佛很複雜的樣子,但仔細觀察後發現:組件層的轉儲信息實際上就是把全部的組件按照樹形結構羅列了出來。其中包含了組件的樣式、值等信息。固然,還會看到某些不曾在代碼中體現的組件。這是由於這些組件在框架自己的組件中有所使用。好比RaisedButton中的InkWell,雖然沒有經過代碼實現InkWell,但RaisedButton自己爲了實現相應的效果,在其中使用了InkWell組件。

此外,在轉儲信息中,會有某個組件被標記爲dirty,這是由於建立轉儲信息的行爲是經過該組件觸發的。本例中,被標記爲dirty的組件以下:

RaisedButton(dependencies: [_LocalizationsScope-[GlobalKey#60b05], _InheritedTheme])    
└RawMaterialButton(dirty, state: _RawMaterialButtonState#fe2da)

可見,它就是爲了執行debugDumpApp()方法而增長的按鈕。

渲染層

由上一小節得知組件層提供了各個組件的詳情信息。但某些時候,這些信息並不徹底夠使用,此時能夠調用debugDumpRenderTree()方法轉儲渲染層。
基於上小節的示例,繼續添加一個按鈕,其操做就是觸發debugDumpRenderTree()方法。以下:

RaisedButton(
    onPressed: () => debugDumpRenderTree(),
    child: Text("Create render tree dump"))

程序運行後,單擊這個按鈕,觀察控制檯輸出(節選):

I/flutter ( 7255): RenderView#7e860
I/flutter ( 7255):  │ debug mode enabled - android
I/flutter ( 7255):  │ window size: Size(1080.0, 2094.0) (in physical pixels)
I/flutter ( 7255):  │ device pixel ratio: 2.6 (physical pixels per logical pixel)
I/flutter ( 7255):  │ configuration: Size(411.4, 797.7) at 2.625x (in logical pixels)
I/flutter ( 7255):  │
I/flutter ( 7255):  └─child: RenderSemanticsAnnotations#62d7d
I/flutter ( 7255):    │ creator: Semantics ← Localizations ← MediaQuery ←
I/flutter ( 7255):    │   WidgetsApp-[GlobalObjectKey _MaterialAppState#d0498] ←
I/flutter ( 7255):    │   ScrollConfiguration ← MaterialApp ← MyApp ← [root]
I/flutter ( 7255):    │ parentData: <none>
I/flutter ( 7255):    │ constraints: BoxConstraints(w=411.4, h=797.7)
I/flutter ( 7255):    │ size: Size(411.4, 797.7)
I/flutter ( 7255):    │
I/flutter ( 7255):    └─child: RenderCustomPaint#e2d03
I/flutter ( 7255):      │ creator: CustomPaint ← Banner ← CheckedModeBanner ← Title ←
I/flutter ( 7255):      │   Directionality ← _LocalizationsScope-[GlobalKey#6be84] ←
I/flutter ( 7255):      │   Semantics ← Localizations ← MediaQuery ←
I/flutter ( 7255):      │   WidgetsApp-[GlobalObjectKey _MaterialAppState#d0498] ←
I/flutter ( 7255):      │   ScrollConfiguration ← MaterialApp ← ⋯
I/flutter ( 7255):      │ parentData: <none> (can use size)
I/flutter ( 7255):      │ constraints: BoxConstraints(w=411.4, h=797.7)
I/flutter ( 7255):      │ size: Size(411.4, 797.7)
I/flutter ( 7255):      │
I/flutter ( 7255):      └─child: RenderPointerListener#9b873
I/flutter ( 7255):        │ creator: Listener ← Navigator-[GlobalObjectKey<NavigatorState>
I/flutter ( 7255):        │   _WidgetsAppState#74612] ← IconTheme ← IconTheme ←
I/flutter ( 7255):        │   _InheritedCupertinoTheme ← CupertinoTheme ← _InheritedTheme ←
I/flutter ( 7255):        │   Theme ← AnimatedTheme ← Builder ← DefaultTextStyle ←
I/flutter ( 7255):        │   CustomPaint ← ⋯
I/flutter ( 7255):        │ parentData: <none> (can use size)
I/flutter ( 7255):        │ constraints: BoxConstraints(w=411.4, h=797.7)
I/flutter ( 7255):        │ size: Size(411.4, 797.7)
I/flutter ( 7255):        │ behavior: deferToChild
I/flutter ( 7255):        │ listeners: down, up, cancel
I/flutter ( 7255):        │
I/flutter ( 7255):        └─child: RenderAbsorbPointer#52153
I/flutter ( 7255):          │ creator: AbsorbPointer ← Listener ←
……

這段節選依然比實際輸出少不少。
不過,這些轉儲信息,一般只關注size和constrains參數就能夠了。由於它們表示了大小和約束條件。此外,針對盒約束,還有可能存在relayoutSubtreeRoot,它表示有多少父控件依賴該組件的尺寸。

層的合成

若是要調試有關合成的問題,就須要轉儲層級關係的信息。
轉儲層級關係的方法是debugDumpLayerTree()。咱們繼續添加一個按鈕,其操做就是觸發debugDumpLayerTree()方法。以下:

RaisedButton(
    onPressed: () => debugDumpLayerTree(),
    child: Text("Create layer tree dump"))

運行,並點擊該按鈕,獲得控制檯輸出:

I/flutter (10050): TransformLayer#256a4
I/flutter (10050):  │ owner: RenderView#6c917
I/flutter (10050):  │ creator: [root]
I/flutter (10050):  │ offset: Offset(0.0, 0.0)
I/flutter (10050):  │ transform:
I/flutter (10050):  │   [0] 2.625,0.0,0.0,0.0
I/flutter (10050):  │   [1] 0.0,2.625,0.0,0.0
I/flutter (10050):  │   [2] 0.0,0.0,1.0,0.0
I/flutter (10050):  │   [3] 0.0,0.0,0.0,1.0
I/flutter (10050):  │
I/flutter (10050):  ├─child 1: OffsetLayer#03fdc
I/flutter (10050):  │ │ creator: RepaintBoundary ← _FocusScopeMarker ← Semantics ←
I/flutter (10050):  │ │   FocusScope ← PageStorage ← Offstage ← _ModalScopeStatus ←
I/flutter (10050):  │ │   
_ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#ea0b8]
I/flutter (10050):  │ │   ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#d7b44] ←
I/flutter (10050):  │ │   Stack ← _Theatre ←
I/flutter (10050):  │ │   Overlay-[LabeledGlobalKey<OverlayState>#404b4] ← ⋯
I/flutter (10050):  │ │ offset: Offset(0.0, 0.0)
I/flutter (10050):  │ │
I/flutter (10050):  │ └─child 1: OffsetLayer#9ca96
I/flutter (10050):  │   │ creator: RepaintBoundary-[GlobalKey#71e3e] ← IgnorePointer ←
I/flutter (10050):  │   │   FadeTransition ← FractionalTranslation ← SlideTransition ←
I/flutter (10050):  │   │   _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundary
I/flutter (10050):  │   │   ← _FocusScopeMarker ← Semantics ← FocusScope ← PageStorage ← ⋯
I/flutter (10050):  │   │ offset: Offset(0.0, 0.0)
I/flutter (10050):  │   │
I/flutter (10050):  │   └─child 1: PhysicalModelLayer#9986e
I/flutter (10050):  │     │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (10050):  │     │   PrimaryScrollController ← _ScaffoldScope ← Scaffold ←
I/flutter (10050):  │     │   MyHomePage ← Semantics ← Builder ←
I/flutter (10050):  │     │   RepaintBoundary-[GlobalKey#71e3e] ← IgnorePointer ←
I/flutter (10050):  │     │   FadeTransition ← ⋯
I/flutter (10050):  │     │ elevation: 0.0
I/flutter (10050):  │     │ color: Color(0xfffafafa)
I/flutter (10050):  │     │
I/flutter (10050):  │     ├─child 1: PictureLayer#1f44b
I/flutter (10050):  │     │   paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 797.7)
I/flutter (10050):  │     │
I/flutter (10050):  │     ├─child 2: PhysicalModelLayer#e486c
I/flutter (10050):  │     │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (10050):  │     │ │   ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButton
I/flutter (10050):  │     │ │   ← RaisedButton ← Column ← Center ← MediaQuery ←
I/flutter (10050):  │     │ │   LayoutId-[<_ScaffoldSlot.body>] ← ⋯
I/flutter (10050):  │     │ │ elevation: 2.0
I/flutter (10050):  │     │ │ color: Color(0xffe0e0e0)
I/flutter (10050):  │     │ │
I/flutter (10050):  │     │ └─child 1: PictureLayer#225de
I/flutter (10050):  │     │     paint bounds: Rect.fromLTRB(130.2, 403.9, 281.2, 439.9)
I/flutter (10050):  │     │
I/flutter (10050):  │     ├─child 3: PhysicalModelLayer#f4d9a
I/flutter (10050):  │     │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (10050):  │     │ │   ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButton
I/flutter (10050):  │     │ │   ← RaisedButton ← Column ← Center ← MediaQuery ←
I/flutter (10050):  │     │ │   LayoutId-[<_ScaffoldSlot.body>] ← ⋯
I/flutter (10050):  │     │ │ elevation: 2.0
I/flutter (10050):  │     │ │ color: Color(0xffe0e0e0)
I/flutter (10050):  │     │ │
I/flutter (10050):  │     │ └─child 1: PictureLayer#c7baf
I/flutter (10050):  │     │     paint bounds: Rect.fromLTRB(105.2, 451.9, 306.2, 487.9)
I/flutter (10050):  │     │
I/flutter (10050):  │     ├─child 4: PhysicalModelLayer#eb57b
I/flutter (10050):  │     │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (10050):  │     │ │   ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButton
I/flutter (10050):  │     │ │   ← RaisedButton ← Column ← Center ← MediaQuery ←
I/flutter (10050):  │     │ │   LayoutId-[<_ScaffoldSlot.body>] ← ⋯
I/flutter (10050):  │     │ │ elevation: 2.0
I/flutter (10050):  │     │ │ color: Color(0xffe0e0e0)
I/flutter (10050):  │     │ │
I/flutter (10050):  │     │ └─child 1: PictureLayer#2350d
I/flutter (10050):  │     │     paint bounds: Rect.fromLTRB(111.2, 499.9, 300.2, 535.9)
I/flutter (10050):  │     │
I/flutter (10050):  │     ├─child 5: AnnotatedRegionLayer<SystemUiOverlayStyle>#a5e42
I/flutter (10050):  │     │ │ value: {systemNavigationBarColor: 4278190080,
I/flutter (10050):  │     │ │   systemNavigationBarDividerColor: null, statusBarColor: null,
I/flutter (10050):  │     │ │   statusBarBrightness: Brightness.dark, statusBarIconBrightness:
I/flutter (10050):  │     │ │   Brightness.light, systemNavigationBarIconBrightness:
I/flutter (10050):  │     │ │   Brightness.light}
I/flutter (10050):  │     │ │ size: Size(411.4, 80.0)
I/flutter (10050):  │     │ │ offset: Offset(0.0, 0.0)
I/flutter (10050):  │     │ │
I/flutter (10050):  │     │ └─child 1: PhysicalModelLayer#32968
I/flutter (10050):  │     │   │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (10050):  │     │   │   AnnotatedRegion<SystemUiOverlayStyle> ← Semantics ← AppBar ←
I/flutter (10050):  │     │   │   FlexibleSpaceBarSettings ← ConstrainedBox ← MediaQuery ←
I/flutter (10050):  │     │   │   LayoutId-[<_ScaffoldSlot.appBar>] ← CustomMultiChildLayout ←
I/flutter (10050):  │     │   │   AnimatedBuilder ← ⋯
I/flutter (10050):  │     │   │ elevation: 4.0
I/flutter (10050):  │     │   │ color: MaterialColor(primary value: Color(0xff2196f3))
I/flutter (10050):  │     │   │
I/flutter (10050):  │     │   └─child 1: PictureLayer#e562b
I/flutter (10050):  │     │       paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 80.0)
I/flutter (10050):  │     │
I/flutter (10050):  │     └─child 6: TransformLayer#4e3f3
I/flutter (10050):  │       │ offset: Offset(0.0, 0.0)
I/flutter (10050):  │       │ transform:
I/flutter (10050):  │       │   [0] 1.0,2.4492935982947064e-16,0.0,-1.7053025658242404e-13
I/flutter (10050):  │       │   [1] -2.4492935982947064e-16,1.0,0.0,1.1368683772161603e-13
I/flutter (10050):  │       │   [2] 0.0,0.0,1.0,0.0
I/flutter (10050):  │       │   [3] 0.0,0.0,0.0,1.0
I/flutter (10050):  │       │
I/flutter (10050):  │       └─child 1: PhysicalModelLayer#79c2c
I/flutter (10050):  │         │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (10050):  │         │   ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButton
I/flutter (10050):  │         │   ← Semantics ← Listener ← RawGestureDetector ← GestureDetector ←
I/flutter (10050):  │         │   Tooltip ← ⋯
I/flutter (10050):  │         │ elevation: 6.0
I/flutter (10050):  │         │ color: Color(0xff2196f3)
I/flutter (10050):  │         │
I/flutter (10050):  │         └─child 1: PictureLayer#0e8dc
I/flutter (10050):  │             paint bounds: Rect.fromLTRB(339.4, 725.7, 395.4, 781.7)
I/flutter (10050):  │
I/flutter (10050):  └─child 2: PictureLayer#1ae80
I/flutter (10050):      paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2094.0)
I/flutter (10050):

這是完整的輸出。因爲界面層級之間的結合很是簡單,所以這部分的Log會較短。
上面的Log中,RepaintBoundary組件在渲染樹中建立RenderRepaintBoundary,從而在層級的樹形結構中建立一個新層。這一步驟用來減小從新繪圖的需求。

怎樣獲取語義樹。

語義調試一般用於提供系統輔助功能的App中,當系統輔助功能開啓時,系統會根據App提供的語義理解某個組件是作什麼用的,或簡單地代表組件的內容。
調試語義實際上就是輸出「語義樹」。和前兩小節不一樣,要得到語義樹,首先要在一開始作聲明,以下所示:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      showSemanticsDebugger: true,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

特別留意上述代碼中的showSemanticsDebugger,它就是開啓語義調試的前提。
接下來就是添加語義樹輸出的方法了,這一步驟和前兩小節相似,以下所示:

RaisedButton(
    onPressed: () => debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder),
    child: Text("Create semantics tree dump"))

此時,運行App,獲得以下圖所示的界面,則表示配置成功。

在這裏插入圖片描述

此時,咱們單擊調試語義按鈕,觀察控制檯的輸出:

I/flutter ( 8341): SemanticsNode#0
I/flutter ( 8341):  │ Rect.fromLTRB(0.0, 0.0, 1080.0, 1794.0)
I/flutter ( 8341):  │
I/flutter ( 8341):  └─SemanticsNode#1
I/flutter ( 8341):    │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4) scaled by 2.6x
I/flutter ( 8341):    │ textDirection: ltr
I/flutter ( 8341):    │
I/flutter ( 8341):    └─SemanticsNode#2
I/flutter ( 8341):      │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)
I/flutter ( 8341):      │ flags: scopesRoute
I/flutter ( 8341):      │
I/flutter ( 8341):      ├─SemanticsNode#9
I/flutter ( 8341):      │ │ Rect.fromLTRB(0.0, 0.0, 411.4, 80.0)
I/flutter ( 8341):      │ │ thicknes: 4.0
I/flutter ( 8341):      │ │
I/flutter ( 8341):      │ └─SemanticsNode#10
I/flutter ( 8341):      │     Rect.fromLTRB(16.0, 40.5, 242.0, 63.5)
I/flutter ( 8341):      │     flags: isHeader, namesRoute
I/flutter ( 8341):      │     label: "Flutter Demo Home Page"
I/flutter ( 8341):      │     textDirection: ltr
I/flutter ( 8341):      │     elevation: 4.0
I/flutter ( 8341):      │
I/flutter ( 8341):      ├─SemanticsNode#3
I/flutter ( 8341):      │   Rect.fromLTRB(65.7, 257.7, 345.7, 273.7)
I/flutter ( 8341):      │   label: "You have pushed the button this many times:"
I/flutter ( 8341):      │   textDirection: ltr
I/flutter ( 8341):      │
I/flutter ( 8341):      ├─SemanticsNode#4
I/flutter ( 8341):      │   Rect.fromLTRB(195.7, 273.7, 215.7, 313.7)
I/flutter ( 8341):      │   label: "0"
I/flutter ( 8341):      │   textDirection: ltr
I/flutter ( 8341):      │
I/flutter ( 8341):      ├─SemanticsNode#5
I/flutter ( 8341):      │   Rect.fromLTRB(135.7, 313.7, 275.7, 361.7)
I/flutter ( 8341):      │   actions: tap
I/flutter ( 8341):      │   flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341):      │   label: "Create app dump"
I/flutter ( 8341):      │   textDirection: ltr
I/flutter ( 8341):      │   thicknes: 2.0
I/flutter ( 8341):      │
I/flutter ( 8341):      ├─SemanticsNode#6
I/flutter ( 8341):      │   Rect.fromLTRB(113.2, 361.7, 298.2, 409.7)
I/flutter ( 8341):      │   actions: tap
I/flutter ( 8341):      │   flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341):      │   label: "Create render tree dump"
I/flutter ( 8341):      │   textDirection: ltr
I/flutter ( 8341):      │   thicknes: 2.0
I/flutter ( 8341):      │
I/flutter ( 8341):      ├─SemanticsNode#7
I/flutter ( 8341):      │   Rect.fromLTRB(118.2, 409.7, 293.2, 457.7)
I/flutter ( 8341):      │   actions: tap
I/flutter ( 8341):      │   flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341):      │   label: "Create layer tree dump"
I/flutter ( 8341):      │   textDirection: ltr
I/flutter ( 8341):      │   thicknes: 2.0
I/flutter ( 8341):      │
I/flutter ( 8341):      ├─SemanticsNode#8
I/flutter ( 8341):      │   Rect.fromLTRB(100.7, 457.7, 310.7, 505.7)
I/flutter ( 8341):      │   actions: tap
I/flutter ( 8341):      │   flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341):      │   label: "Create semantics tree dump"
I/flutter ( 8341):      │   textDirection: ltr
I/flutter ( 8341):      │   thicknes: 2.0
I/flutter ( 8341):      │
I/flutter ( 8341):      └─SemanticsNode#11
I/flutter ( 8341):        │ merge boundary ⛔️
I/flutter ( 8341):        │ Rect.fromLTRB(0.0, 0.0, 56.0, 56.0) with transform
I/flutter ( 8341):        │   [1.0,2.4492935982947064e-16,0.0,339.42857142857144;
I/flutter ( 8341):        │   -2.4492935982947064e-16,1.0,0.0,611.4285714285714;
I/flutter ( 8341):        │   0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0]
I/flutter ( 8341):        │ label: "Increment"
I/flutter ( 8341):        │ textDirection: ltr
I/flutter ( 8341):        │
I/flutter ( 8341):        └─SemanticsNode#12
I/flutter ( 8341):            merged up ⬆️
I/flutter ( 8341):            Rect.fromLTRB(0.0, 0.0, 56.0, 56.0)
I/flutter ( 8341):            actions: tap
I/flutter ( 8341):            flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341):            thicknes: 6.0
I/flutter ( 8341):

好了,內容就講到這裏。

在實際的開發過程當中,App的界面一般會比上述示例要複雜得多。在運用這些調試工具時,建議你們不要拘泥於其中某一種方式。可以靈活運用工具,纔是打造一款優秀的App的重要前提。

但願這些內容可以對你有幫助,咱們下次再見。

推薦:Android學習PDF+架構視頻+面試文檔+源碼筆記

限時讀者福利

在這裏分享一份我本身收錄整理的Android學習PDF+架構視頻+面試文檔+源碼筆記,還有高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料幫助你們學習提高進階,也節省你們在網上搜索資料的時間來學習,也能夠分享給身邊好友一塊兒學習

若是你有須要的話,能夠點贊+轉發關注我,而後加入Android開發交流羣(820198451)免費領取
image

image

相關文章
相關標籤/搜索