Flutter 构建模式
目前,Flutter一共提供了三种运行模式,分别是Debug、Release和Profile模式。其中,Debug模式主要用在软件编写过程中,Release模式主要用于应用发布过程中,而Profile模式则主要用于应用性能分析时,每个模式都有自己特殊的使用场景。下面简介介绍下这几种模式:
Debug模式
Debug模式又名调试模式,Debug模式可以同时在物理设备、仿真器或者模拟器上运行应用。默认情况下,使用flutter run命令运行应用程序时就是使用的Debug模式。在Debug模式下,所有的断言、服务扩展是开启的,并且在模式对快速开发和运行周期进行了编译优化,当使用调试工具进行代码调试时可以直接连接到应用的进程里。
Release模式
Release模式又名发布模式,此模式只能在物理设备上运行,不能在模拟器上运行。使用flutter run --release命令运行应用程序时就是使用的Release模式。在Release模式下,断点、调试信息和服务扩展是不可用的,并且Release模式针对快速启动、快速执行和安装包大小进行了优化。
Profile模式
Profile模式只能在物理设备上运行,不能在模拟器上运行。此模式主要用于应用性能分析,一些应用调试能力是被保留的,目的是分析应用存在的性能问题。Profile模式和Release模式大体相同,不同点体现在,Profile模式的某些服务扩展是启用的,某些进程调试手段也是开启的。
调试模式
在 Debug 模式下,app 可以被安装在物理设备、仿真器或者模拟器上进行调试。在Debug模式下,可以进行如下操作:
- 断点 是开启的。
- 服务扩展是开启的。
- 针对快速开发和运行周期进行了编译优化(但不是针对执行速度、二进制文件大小或者部署)。
- 调试开启,类似 开发者工具 等调试工具可以连接到进程里。
如果是在 Web 平台下的调试模式,可以进行如下操作:
- 本次构建 没有 最小化资源并且整个构建 没有 优化性能。
- 为了简化调试,这个 Web 应用使用了 dartdevc 编译器。
默认情况下,运行 flutter run
会使用 Debug 模式,同时 IDE 也支持这些模式。例如,Android Studio 提供了 Run > Debug… 菜单选项,而且在项目面板中还有一个三角形的绿色运行按钮图标 。
Release 模式
当你想要最大的优化以及最小的占用空间时,就使用 Release 模式来部署 app。 release 模式是不支持模拟器或者仿真器的,使用 Release 模式意味着。
- 断点是不可用的。
- 调试信息是不可见的。
- 调试是禁用的。
- 编译针对快速启动、快速执行和小的 package 的大小进行了优化。
- 服务扩展是禁用的。
对于Web开发来说,使用 Release 模式意味着。
- 这次构建资源已经被压缩,并且性能得以优化。
- 这个 Web 应用通过 dart2js 编译器构建,以确保更优秀的性能。
Profile 模式
在 Profile 模式下,一些调试能力是被保留的,足够分析你的 app 性能。Profile 模式在仿真器和模拟器上是不可用的,因为他们的行为不能代表真实的性能。和 release 相比, profile 模式有以下不同:
- 一些服务扩展是启用的。例如,支持 performance overlay。
- Tracing 是启用的,一些调试工具,比如 开发者工具 可以连接到进程里。
在 Web 平台使用Profile 模式意味着:
- 资源文件没有被压缩,但是整体性能已经优化。
- 这个 Web 应用通过 dart2js 编译器构建。
调试工具
在Flutter应用开发中,有很多工具可以帮助调试 Flutter 应用程序,常见的如下所示。
- 开发者工具,是一套运行在浏览器的性能及分析工具。
- Android Studio/IntelliJ 和 VS Code(借助 Flutter 和 Dart 插件)支持内置的源代码调试器,可以设置断点,单步调试,检查数值。
- Flutter inspector,是开发者工具提供的 widget 检查器,也可直接在 Android Studio 和 IntelliJ 中使用(借助 Flutter 插件)。检查器可以可视化展现 widget 树,查看单个 widget 及其属性值,开启性能图层,等等。
开发者工具
要调试及分析应用,开发者工具可能是你的首选。开发者工具运行在浏览器,支持以下特性:
- 源代码调试器
- Widget 检查器,展示可视化的 widget 树; “widget select” 模式,在应用中选择一个 widget,会在 widget 树直接定位到它的位置。
- 内存分析
- 时间线视图,支持跟踪,导入及导出跟踪信息
- 日志视图
如果你在Debug 模式 或Profile 模式 运行,那么可以在浏览器打开开发者工具连接到你的应用。开发者工具不能用在 Release 模式 编译的应用,因为调试和分析信息都被删除了。如果你要用开发者工具分析应用,需确保使用 Profile 模式运行应用。
断点调试
和其他语言一样,Flutter的断点调试支持在 IDE 或编辑器(比如 Android Studio/IntelliJ 和 VS Code)、或者通过编码两种方式。
其中,开发者工具调试器如下图所示。
开启调试后,可以在控制台看到如下一些信息。
- 底部的 Debugger 窗口会显示出堆栈和变量信息。
- 底部的 Console 窗口会显示详细的日志输出。
- 调试基于默认的启动配置,如果需要自定义,点击选择目标下拉按钮,选择 Edit configuration 进行配置。
在进行断点调试时,使用得最多的就是单步调试,三个单步调试按钮在暂停后会变为可用状态。
- 使用 Step in 来进入被调用的方法,在遇到方法内的第一行可执行代码时结束。
- 使用 Step over 直接执行某个方法调用而不进入内部;该按钮在当前方法内按行执行。
- 使用 Step out 来跳出当前方法,这种方式会直接执行完所有当前方法内的语句。
除此之外,我们还可以使用代码的方式进行断点调试,我们可以在源代码中使用 debugger()函数来开启断点,当代码运行到此处时就会刮起,如下所示。
import 'dart:developer';
void someFunction(double offset) {
debugger(when: offset > 30.0);
// ...
}
Dart 分析器
如果你使用的是 Android Studio或者VSCode,那么工具会自带的 Dart 分析器默认会检查代码,并发现可能的错误。如果你使用命令行,则可以使用 flutter analyze
命令来检查代码。Dart 分析器非常依赖你在代码中添加的类型注解,以帮助跟踪问题。
另外,我们可以使用flutter analyze --flutter-repo
命令将分析结果打印到控制台上,每次运行这个命名之前,请先运行flutter update-packages
升级最新的包,这样就可以获取最新的依赖包。如果你不这样做,你可能会从dart:ui得到一些错误消息,比如偏移量等。因为执行flutter analysis
命令时并不会主动去拉取依赖。
对于一次性的Dart分析,直接使用flutter analyze --flutter-repo
即可,对于连续分析,则可以使用flutter analyze --flutter-repo --watch
命令。如果你想知道多少个成员变量丢失了dartdocs,可以添加一个dartdocs参数。
Flutter inspector 工具
Flutter inspector 是分析Flutter组件状态树的利器,Flutter使用小部件来控制页面组件到布局的精准控制,Flutter inspector 可以帮助我们进行如下一些分析。
- 进行布局分析,理解布局层次
- 诊断布局问题
- Select widget mode:启用此按钮后,选择组件树的代码会自动跳转到对应的源代码里面。
- Refresh tree : 重新加载最新的组件信息。
- Slow Animations:放慢动画速度,以便进行视觉上的查验。
- Debug Paint: 边框、方向的可视化。
- Paint Baselines: 每个渲染框在它的每个文本基线上画一条线。
- Repaint Rainbow:查看重绘的严重程度,严重的会被爆红。
除了上面的功能外,我们还可以点击【Open DevTools】打开Flutter的调试页面,可以借助它进行很多性能分析,后面会具体介绍。
测量应用启动时间
要收集有关 Flutter 应用程序启动所需时间的详细信息,可以在运行 flutter run 命令时使用 trace-startup 和 profile 选项,如下所示。
flutter run --trace-startup --profile
跟踪输出被保存到 Flutter 工程目录在 build 目录下,一个名为 start_up_info.json 的 JSON 文件中,输出列出了从应用程序启动到这些跟踪事件(以微秒捕获)所用的时间,如下所示。
{
"engineEnterTimestampMicros": 2346054348633,
"timeToFrameworkInitMicros": 812748,
"timeToFirstFrameRasterizedMicros": 1573154,
"timeToFirstFrameMicros": 1221472,
"timeAfterFrameworkInitMicros": 408724
}
对应的具体含义如下:
- 进入 Flutter 引擎时
- 展示应用第一帧时
- 初始化Flutter框架时
- 完成Flutter框架初始化时
使用Android Studio进行调试
Flutter官方推荐使用Android Studio或VSCode进行应用开发, 和其他语言的调试一样,Dart代码的调试流程也差不多。如果还没有Flutter项目,可以新建一个示例项目。通过单击首先,点击调试图标(Debug-run icon)同时打开调试面板并在控制台中运行应用,首次运行应用是最慢的,应用启动后,界面应该是下面这样的。
你可以 step in/out/over Dart 语句、热重载和恢复执行应用、以及像使用其他调试器一样来使用 Dart 调试器。
Flutter inspector
Flutter inspector 是一个用来可视化以及查看 Flutter widget 树的工具,提供如下功能:
- 了解现有布局
- 诊断布局问题
可以使用 Android Studio 窗口右侧的垂直按钮来打开Flutter inspector,如下图所示。
Flutter outline
Flutter Outline 是一个可视的显示页面构建方法的功能,注意在构建方法上可能与 widget 树不同,可以使用 Android Studio 窗口右侧的垂直按钮切换 outline 的显示。
然后在输入框中输入attach关键字,显示如下图。
使用 Android Gradle 调试
为了调试原生代码,你需要一个包含 Android 原生代码的应用。在本节中,你将学会如何连接两个调试器到你的应用:
1)Dart 调试器。
2)Android Gradle 调试器。
创建一个基本的 Flutter 应用,然后替换 lib/main.dart 的代码为以下示例代码。
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'URL Launcher',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'URL Launcher'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
Future _launched;
Future _launchInBrowser(String url) async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC: false, forceWebView: false);
} else {
throw 'Could not launch $url';
}
}
Future _launchInWebViewOrVC(String url) async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC: true, forceWebView: true);
} else {
throw 'Could not launch $url';
}
}
Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text('');
}
}
@override
Widget build(BuildContext context) {
String toLaunch = 'https://flutter.dev';
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(16.0),
child: Text(toLaunch),
),
RaisedButton(
onPressed: () => setState(() {
_launched = _launchInBrowser(toLaunch);
}),
child: Text('Launch in browser'),
),
Padding(padding: EdgeInsets.all(16.0)),
RaisedButton(
onPressed: () => setState(() {
_launched = _launchInWebViewOrVC(toLaunch);
}),
child: Text('Launch in app'),
),
Padding(padding: EdgeInsets.all(16.0)),
FutureBuilder(future: _launched, builder: _launchStatus),
],
),
),
);
}
}
然后,添加 url_launcher 依赖到 pubspec 文件,并执行 flutter pub get命令拉取依赖包。
name: flutter_app
description: A new Flutter application.
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
url_launcher: ^3.0.3
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter