使用原生的偵錯程式

如果你只使用 Dart 語言開發 Flutter 應用,並且不使用特定於平台的的函式庫或者功能,你可以使用 IDE 的偵錯程式除錯你的程式碼。只有這篇指南的第一部分「除錯 Dart 程式碼」對你有用。

如果你正在開發特定於平台的的外掛或者使用由 Swift、ObjectiveC、Java 或 Kotlin 語言編寫的特定於平台的函式庫,你可以使用 Xcode(用於 iOS)或者 Android Gradle(用於 Android)除錯這部分程式碼。本指南介紹如何將用於 Dart 和用於原生程式碼的 兩個 偵錯程式連線到你的 Dart 應用。

除錯 Dart 程式碼

你可以使用 IDE 進行一般的 Dart 除錯。以下內容針對 Android Studio 進行說明,但你也可以使用你喜歡的安裝並配置好 Flutter 和 Dart 外掛的編輯器來進行除錯。

Dart 偵錯程式

  • 使用 Android Studio 開啟你的專案。如果你還沒有專案,根據 開發體驗初探 中的說明建立一個。

  • 透過單擊除錯圖示 (Debug-run icon) 同時開啟除錯面板並在控制檯中執行應用。

    首次執行應用是最慢的,你會發現視窗底部的除錯面板看起來會像這樣:

    Debug pane

    你可以設定除錯面板的顯示位置,甚至可以用除錯面板右側的齒輪將其拆分到獨立的視窗。對於 Android Studio 中的任何檢查器都是如此。

  • counter++ 這一行上新增斷點。

  • 在應用裡,點選 + 按鈕(FloatingActionButton,或者簡稱 FAB)來增加數字,應用會暫停。

  • 以下截圖顯示:

    • 編輯面板中的斷點。

    • 當在斷點處暫停時,在除錯面板中顯示應用的狀態。

    • this 變數展開並顯示其值。

    App status when hitting the set breakpoint

你可以 step in/out/over Dart 陳述式、熱重載和恢復執行應用、以及像使用其他偵錯程式一樣來使用 Dart 偵錯程式。 5: Debug 按鈕切換除錯面板的顯示。

Flutter inspector

Flutter 外掛提供了另外兩個可能給你提供幫助的功能。 Flutter inspector 是一個用來視覺化以及檢視 Flutter widget 樹的工具,並幫助你:

  • 瞭解現有佈局

  • 診斷佈局問題

你可以使用 Android Studio 視窗右側的垂直按鈕切換檢查器的顯示。

Flutter inspector

Flutter outline

Flutter Outline 以可視形式顯示建構方法。注意在建構方法上可能與 widget 樹不同。你可以使用 Android Studio 視窗右側的垂直按鈕切換 outline 的顯示。

screenshot showing the Flutter inspector

這篇指南剩下的部分介紹瞭如何搭建原生程式碼的除錯環境。你應該可以想象到,對於 iOS 和 Android 這個過程是不同的。

使用 Android Gradle 除錯(Android)

為了除錯原生程式碼,你需要一個包含 Android 原生程式碼的應用。在本節中,你將學會如何連線兩個偵錯程式到你的應用: 1)Dart 偵錯程式,和 2)Android Gradle 偵錯程式。

  • 建立一個基本的 Flutter 應用。

  • 替換 lib/main.dart 為來自 url_launcher 套件的以下範例程式碼:

// 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<MyHomePage> {
  Future<void> _launched;

  Future<void> _launchInBrowser(String url) async {
    if (await canLaunch(url)) {
      await launch(url, forceSafariVC: false, forceWebView: false);
    } else {
      throw 'Could not launch $url';
    }
  }

  Future<void> _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<void> 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: <Widget>[
            Padding(
              padding: EdgeInsets.all(16.0),
              child: Text(toLaunch),
            ),
            ElevatedButton(
              onPressed: () => setState(() {
                    _launched = _launchInBrowser(toLaunch);
                  }),
              child: Text('Launch in browser'),
            ),
            Padding(padding: EdgeInsets.all(16.0)),
            ElevatedButton(
              onPressed: () => setState(() {
                    _launched = _launchInWebViewOrVC(toLaunch);
                  }),
              child: Text('Launch in app'),
            ),
            Padding(padding: EdgeInsets.all(16.0)),
            FutureBuilder<void>(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
  • 點選除錯按鈕 (Debug-run icon) 來同時開啟除錯面板並啟動應用。等待應用在裝置上啟動並在除錯面板中顯示 Connected。(第一次可能需要一分鐘,但是之後的啟動會變快。)應用包含兩個按鈕: 1)Launch in browser 在你的手機預設瀏覽器中開啟 flutter.dev 網站 2)Launch in app 在你的應用中開啟 flutter.dev 網站。

    screenshot containing two buttons for opening flutter.dev

  • 點選 Attach debugger to Android process 按鈕 looks like a rectangle superimposed with a tiny green bug )

  • 從處理序對話方塊中,你應該可以看到每一個裝置的入口。選擇 show all processes 來顯示每個裝置可用的處理序。

  • 選擇你想附加到的處理序。在這個例子中是 Motorola Moto G 的 com.google.clickcount (或 com.company.app_name)處理序。

    screenshot containing two buttons for opening flutter.dev

  • 在除錯面板中,你現在應該可以看到一個 Android Debugger 標籤頁。

  • In the project pane, expand

    app_name > android > app > src > main > java > io.flutter plugins

    在專案面板,展開 app_name > android > app > src > main > java > io.flutter plugins。雙擊 GeneratedProjectRegistrant 在編輯面板中開啟 Java 程式碼。

Dart 和原生偵錯程式都在與同一個處理序互動。使用其中一個或者同時使用兩個來設定斷點、檢查堆疊、恢復執行…… 換句話說,除錯!

screenshot of Android Studio in the Dart debug pane.

Dart 除錯面板和 `lib/main.dart` 中的兩個斷點。
Dart 除錯面板和 `lib/main.dart` 中的兩個斷點。

screenshot of Android Studio in the Android debug pane.

Android 除錯面板和 `GeneratedPluginRegistrant.java` 中的一個斷點。透過單擊除錯面板頂部的相應偵錯程式,在偵錯程式之間進行切換。

使用 Xcode 除錯(iOS)

為了除錯原生 iOS 程式碼,你需要一個包含原生 iOS 程式碼的應用。在本節中,你將學會如何連線兩個偵錯程式到你的應用: 1)Dart 偵錯程式 2)Xcode 偵錯程式。

[PENDING]

資源

下面的資源包含更多關於 Flutter、iOS 和 Android 除錯的資訊。

Flutter

Android

你可以在 developer.android.com 找到下列的除錯資源。

iOS

你可以在 developer.apple.com 找到下列的除錯資源。