认识 Dart 语言检查器
flutter analyze
命令,检查结果会稍后显示在命令行对话框中。 比如,在默认新建的计数器应用中,去掉一个语句结尾的分号:
void _incrementCounter() {setState(() {_counter++});}
看到 _counter++ 后面少了一个分号了吗?此时,运行 Dart 检查器,命令行输出:
error - Expected to find ';' - lib\main.dart:32:15 - expected_token1 issue found. (ran in 8.9s)
如何在 IDE 中进行单步调试
在某些时候,我们需要进行单步调试。单步调试可以让程序逐条语句地进行,并可以看到当前运行的位置。另外,在单步调试过程中,还能实时关注相应范围内所有变量值的详细变化过程。
Android Studio 中提供了单步调试功能。这和开发原生 Android 平台 App 时的单步调试方法一样,其具体步骤可以分为三步进行,第一步是标记断点,第二步是运行程序到断点处,第三步则是使用 Debug 工具进行调试。
下面以默认的计数器应用为例,观察代码中 _counter 值的变化,体会单步调试的全过程。
第一步是标记断点,既然要观察 _counter 值的变化,则在每次 _counter 值发生变化后添加断点,观察数值变化是最理想的,因此在行号稍右侧点击鼠标,把断点加载下图所示的位置。
添加断点后,相应的行号右侧将会出现圆形的断点标记,并且整行将会高亮显示。
到此,断点就添加好了,当然,还可以同时添加多个断点,以便实现多个位置的调试。
我们可以在任何时候退出调试模式,只需点击停止运行按钮即可,它位于启动调试模式按钮的右侧。
点击该按钮后,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): 我开始启动了
I/flutter (13320): 我开始å¯åŠ¨äº†
利用 Dart 语言中的 "断言"
Dart 运行时提供两种运行方式: Production 和 Checked。默认情况下会以 Production 模式运行,在这种条件下,优先考虑性能,关闭类型检查和断言;反之,Checked 模式更利于开发阶段调试使用。
断言可以检查程序中某些可能出现的运行时逻辑错误。比如下面这段代码:
// assertvar intValue = 300;assert(intValue == 299);
很明显,intValue 不满足和 299 相等的条件,此时在开发环境中运行程序,将看到控制台报错。而一旦切换到生产模式,则不会收到任何错误提示。这对于检查代码中某些隐含的逻辑问题十分有效。
如何查看界面 Widget 树形层级
组件层
import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {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;_MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> {int _counter = 0;void _incrementCounter() {setState(() {_counter++;});}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),),);}}
I/flutter ( 4489): WidgetsFlutterBinding - CHECKED MODEI/flutter ( 4489): [root](renderObject: RenderViewI/flutter ( 4489): └MyAppI/flutter ( 4489): └MaterialApp(state: _MaterialAppStateI/flutter ( 4489): └ScrollConfiguration(behavior: _MaterialScrollBehavior)I/flutter ( 4489): └WidgetsApp-[GlobalObjectKey _MaterialAppState_WidgetsAppStateI/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:[DefaultWidgetsLocalizations.delegate(en_US)], state: _LocalizationsStateI/flutter ( 4489): └Semantics(container: false, properties: SemanticsProperties, label: null, value:null, hint: null, textDirection: ltr, hintOverrides: null, renderObject: RenderSemanticsAnnotationsI/flutter ( 4489): └_LocalizationsScope-[GlobalKeyI/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:[])I/flutter ( 4489): └CustomPaint(renderObject: RenderCustomPaintI/flutter ( 4489): └DefaultTextStyle(debugLabel: fallback style; consider putting your text in aMaterial, 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(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#7e860I/flutter ( 7255): │ debug mode enabled - androidI/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#62d7dI/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#e2d03I/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#9b873I/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: deferToChildI/flutter ( 7255): │ listeners: down, up, cancelI/flutter ( 7255): │I/flutter ( 7255): └─child: RenderAbsorbPointer#52153I/flutter ( 7255): │ creator: AbsorbPointer ← Listener ←……
这段节选依然比实际输出少很多。
不过,这些转储信息,通常只关注 size 和 constrains 参数就可以了。因为它们表示了大小和约束条件。此外,针对盒约束,还有可能存在 relayoutSubtreeRoot,它表示有多少父控件依赖该组件的尺寸。
如果要调试有关合成的问题,就需要转储层级关系的信息。转储层级关系的方法是 debugDumpLayerTree()。我们继续添加一个按钮,其操作就是触发 debugDumpLayerTree() 方法。如下:
RaisedButton(onPressed: () => debugDumpLayerTree(),child: Text("Create layer tree dump"))
I/flutter (10050): TransformLayerI/flutter (10050): │ owner: RenderViewI/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.0I/flutter (10050): │ [1] 0.0,2.625,0.0,0.0I/flutter (10050): │ [2] 0.0,0.0,1.0,0.0I/flutter (10050): │ [3] 0.0,0.0,0.0,1.0I/flutter (10050): │I/flutter (10050): ├─child 1: OffsetLayerI/flutter (10050): │ │ creator: RepaintBoundary ← _FocusScopeMarker ← Semantics ←I/flutter (10050): │ │ FocusScope ← PageStorage ← Offstage ← _ModalScopeStatus ←I/flutter (10050): │ │_ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>I/flutter (10050): │ │ ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>I/flutter (10050): │ │ Stack ← _Theatre ←I/flutter (10050): │ │ Overlay-[LabeledGlobalKey<OverlayState>I/flutter (10050): │ │ offset: Offset(0.0, 0.0)I/flutter (10050): │ │I/flutter (10050): │ └─child 1: OffsetLayerI/flutter (10050): │ │ creator: RepaintBoundary-[GlobalKeyI/flutter (10050): │ │ FadeTransition ← FractionalTranslation ← SlideTransition ←I/flutter (10050): │ │ _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundaryI/flutter (10050): │ │ ← _FocusScopeMarker ← Semantics ← FocusScope ← PageStorage ← ⋯I/flutter (10050): │ │ offset: Offset(0.0, 0.0)I/flutter (10050): │ │I/flutter (10050): │ └─child 1: PhysicalModelLayerI/flutter (10050): │ │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←I/flutter (10050): │ │ PrimaryScrollController ← _ScaffoldScope ← Scaffold ←I/flutter (10050): │ │ MyHomePage ← Semantics ← Builder ←I/flutter (10050): │ │ RepaintBoundary-[GlobalKeyI/flutter (10050): │ │ FadeTransition ← ⋯I/flutter (10050): │ │ elevation: 0.0I/flutter (10050): │ │ color: Color(0xfffafafa)I/flutter (10050): │ │I/flutter (10050): │ ├─child 1: PictureLayerI/flutter (10050): │ │ paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 797.7)I/flutter (10050): │ │I/flutter (10050): │ ├─child 2: PhysicalModelLayerI/flutter (10050): │ │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←I/flutter (10050): │ │ │ ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButtonI/flutter (10050): │ │ │ ← RaisedButton ← Column ← Center ← MediaQuery ←I/flutter (10050): │ │ │ LayoutId-[<_ScaffoldSlot.body>] ← ⋯I/flutter (10050): │ │ │ elevation: 2.0I/flutter (10050): │ │ │ color: Color(0xffe0e0e0)I/flutter (10050): │ │ │I/flutter (10050): │ │ └─child 1: PictureLayerI/flutter (10050): │ │ paint bounds: Rect.fromLTRB(130.2, 403.9, 281.2, 439.9)I/flutter (10050): │ │I/flutter (10050): │ ├─child 3: PhysicalModelLayerI/flutter (10050): │ │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←I/flutter (10050): │ │ │ ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButtonI/flutter (10050): │ │ │ ← RaisedButton ← Column ← Center ← MediaQuery ←I/flutter (10050): │ │ │ LayoutId-[<_ScaffoldSlot.body>] ← ⋯I/flutter (10050): │ │ │ elevation: 2.0I/flutter (10050): │ │ │ color: Color(0xffe0e0e0)I/flutter (10050): │ │ │I/flutter (10050): │ │ └─child 1: PictureLayerI/flutter (10050): │ │ paint bounds: Rect.fromLTRB(105.2, 451.9, 306.2, 487.9)I/flutter (10050): │ │I/flutter (10050): │ ├─child 4: PhysicalModelLayerI/flutter (10050): │ │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←I/flutter (10050): │ │ │ ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButtonI/flutter (10050): │ │ │ ← RaisedButton ← Column ← Center ← MediaQuery ←I/flutter (10050): │ │ │ LayoutId-[<_ScaffoldSlot.body>] ← ⋯I/flutter (10050): │ │ │ elevation: 2.0I/flutter (10050): │ │ │ color: Color(0xffe0e0e0)I/flutter (10050): │ │ │I/flutter (10050): │ │ └─child 1: PictureLayerI/flutter (10050): │ │ paint bounds: Rect.fromLTRB(111.2, 499.9, 300.2, 535.9)I/flutter (10050): │ │I/flutter (10050): │ ├─child 5: AnnotatedRegionLayer<SystemUiOverlayStyle>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: PhysicalModelLayerI/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.0I/flutter (10050): │ │ │ color: MaterialColor(primary value: Color(0xff2196f3))I/flutter (10050): │ │ │I/flutter (10050): │ │ └─child 1: PictureLayerI/flutter (10050): │ │ paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 80.0)I/flutter (10050): │ │I/flutter (10050): │ └─child 6: TransformLayerI/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-13I/flutter (10050): │ │ [1] -2.4492935982947064e-16,1.0,0.0,1.1368683772161603e-13I/flutter (10050): │ │ [2] 0.0,0.0,1.0,0.0I/flutter (10050): │ │ [3] 0.0,0.0,0.0,1.0I/flutter (10050): │ │I/flutter (10050): │ └─child 1: PhysicalModelLayerI/flutter (10050): │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←I/flutter (10050): │ │ ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButtonI/flutter (10050): │ │ ← Semantics ← Listener ← RawGestureDetector ← GestureDetector ←I/flutter (10050): │ │ Tooltip ← ⋯I/flutter (10050): │ │ elevation: 6.0I/flutter (10050): │ │ color: Color(0xff2196f3)I/flutter (10050): │ │I/flutter (10050): │ └─child 1: PictureLayerI/flutter (10050): │ paint bounds: Rect.fromLTRB(339.4, 725.7, 395.4, 781.7)I/flutter (10050): │I/flutter (10050): └─child 2: PictureLayerI/flutter (10050): paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2094.0)I/flutter (10050):
怎样获取语义树
class MyApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(showSemanticsDebugger: true,title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: MyHomePage(title: 'Flutter Demo Home Page'),);}}
RaisedButton(onPressed: () => debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder),child: Text("Create semantics tree dump"))
I/flutter ( 8341): SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(0.0, 0.0, 1080.0, 1794.0)I/flutter ( 8341): │I/flutter ( 8341): └─SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4) scaled by 2.6xI/flutter ( 8341): │ textDirection: ltrI/flutter ( 8341): │I/flutter ( 8341): └─SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)I/flutter ( 8341): │ flags: scopesRouteI/flutter ( 8341): │I/flutter ( 8341): ├─SemanticsNodeI/flutter ( 8341): │ │ Rect.fromLTRB(0.0, 0.0, 411.4, 80.0)I/flutter ( 8341): │ │ thicknes: 4.0I/flutter ( 8341): │ │I/flutter ( 8341): │ └─SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(16.0, 40.5, 242.0, 63.5)I/flutter ( 8341): │ flags: isHeader, namesRouteI/flutter ( 8341): │ label: "Flutter Demo Home Page"I/flutter ( 8341): │ textDirection: ltrI/flutter ( 8341): │ elevation: 4.0I/flutter ( 8341): │I/flutter ( 8341): ├─SemanticsNodeI/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: ltrI/flutter ( 8341): │I/flutter ( 8341): ├─SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(195.7, 273.7, 215.7, 313.7)I/flutter ( 8341): │ label: "0"I/flutter ( 8341): │ textDirection: ltrI/flutter ( 8341): │I/flutter ( 8341): ├─SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(135.7, 313.7, 275.7, 361.7)I/flutter ( 8341): │ actions: tapI/flutter ( 8341): │ flags: isButton, hasEnabledState, isEnabledI/flutter ( 8341): │ label: "Create app dump"I/flutter ( 8341): │ textDirection: ltrI/flutter ( 8341): │ thicknes: 2.0I/flutter ( 8341): │I/flutter ( 8341): ├─SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(113.2, 361.7, 298.2, 409.7)I/flutter ( 8341): │ actions: tapI/flutter ( 8341): │ flags: isButton, hasEnabledState, isEnabledI/flutter ( 8341): │ label: "Create render tree dump"I/flutter ( 8341): │ textDirection: ltrI/flutter ( 8341): │ thicknes: 2.0I/flutter ( 8341): │I/flutter ( 8341): ├─SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(118.2, 409.7, 293.2, 457.7)I/flutter ( 8341): │ actions: tapI/flutter ( 8341): │ flags: isButton, hasEnabledState, isEnabledI/flutter ( 8341): │ label: "Create layer tree dump"I/flutter ( 8341): │ textDirection: ltrI/flutter ( 8341): │ thicknes: 2.0I/flutter ( 8341): │I/flutter ( 8341): ├─SemanticsNodeI/flutter ( 8341): │ Rect.fromLTRB(100.7, 457.7, 310.7, 505.7)I/flutter ( 8341): │ actions: tapI/flutter ( 8341): │ flags: isButton, hasEnabledState, isEnabledI/flutter ( 8341): │ label: "Create semantics tree dump"I/flutter ( 8341): │ textDirection: ltrI/flutter ( 8341): │ thicknes: 2.0I/flutter ( 8341): │I/flutter ( 8341): └─SemanticsNodeI/flutter ( 8341): │ merge boundary ⛔️I/flutter ( 8341): │ Rect.fromLTRB(0.0, 0.0, 56.0, 56.0) with transformI/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: ltrI/flutter ( 8341): │I/flutter ( 8341): └─SemanticsNodeI/flutter ( 8341): merged up ⬆️I/flutter ( 8341): Rect.fromLTRB(0.0, 0.0, 56.0, 56.0)I/flutter ( 8341): actions: tapI/flutter ( 8341): flags: isButton, hasEnabledState, isEnabledI/flutter ( 8341): thicknes: 6.0I/flutter ( 8341):
"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。
点击屏末 | 阅读原文 | 了解更多 "开发者说·DTalk" 活动详情与参与方式
长按右侧二维码
报名参与