Flutter开发调试插件dokit的使用

Flutter开发调试插件dokit的使用

DoKit Flutter版

内测版本,目前提供了日志、method channel信息、路由信息、网络抓包、帧率、设备与内存信息查看、控件信息查看、颜色拾取、启动耗时、查看源码、查看widget的build链以及对齐标尺的功能。


支持flutter版本

1.17.5<=version<=1.22.4,其余版本未做过兼容性测试


Pub地址

DoKit For Flutter


接入

pubspect.yaml文件的dependencies节点添加pub依赖:

dependencies:
  dokit: ^0.6.1

main函数入口初始化。DoKit使用runZone的方式进行日志捕获和方法通道捕获,如果你的app需要使用同样的方式可能会有冲突。

void main() => {
      DoKit.runApp(
        app: DoKitApp(MyApp()),
        // 是否在release包内使用,默认release包会禁用
        useInRelease: true,
        releaseAction: () => {
          // release模式下执行该函数,一些用到runZone之类实现的可以放到这里,该值为空则会直接调用系统的runApp(MyApp())
        },
      );
    };

注:谷歌提供的DevTool会折叠非主工程内实例化的widget(根据source file是否属于当前工程),DoKit需要实例化一个wrapper widget用以展示各种overlay,如果在package内去声明这个wrapper,会导致左边树全部被折叠。故这里要求在main文件内使用DoKitApp(MyApp())的方式来初始化入口

另外提供了一个异步创建入口Widget的方式,需要异步构建widget的情况。(有些库会在异步构建Widget的时候调用WidgetFlutterBinding.ensureInitialized(),影响DoKit的method channel监控和日志监控,需要延迟到runZone内执行)

void main() => {
      DoKit.runApp(
        appCreator: () async => DoKitApp(await createApp()),
      );

      Widget createApp() async {
        // 一些初始化操作
        await ...;
        return MyApp();
      }
    };

参数说明

参数 返回类型 说明 是否必须
app DoKitApp 返回被DoKitApp类包装的根布局 app和appCreator至少需要设置一个,同时设置时app参数生效
appCreator DoKitAppCreator 异步返回根布局 同上
useInRelease bool 是否在release模式下显示DoKit x
logCallback LogCallback 调用print方法打印日志时被回调 x
exceptionCallback ExceptionCallback 异常回调 x
methodChannelBlackList List 过滤方法通道的黑名单 x
releaseAction Function release模式下执行该函数,该值为空则会直接调用系统的runApp x

功能简介

全部组件

全部组件

当前版本DoKit支持的所有功能全览。常驻工具为显示在底部tab栏的组件,可通过拖动将组件放置或移出常驻工具。

第三方业务入口

第三方业务入口

添加第三方业务入口,目前只支持跳转页面,对要跳转的页面只要求是Widget即可,添加第三方业务入口的代码推荐写在main函数中,下面是添加第三方入口的示例:

// 注册新的第三方业务入口,不可重复注册,否则报错
BizKitManager.instance.addKitWith(
    name: 'test1',
    group: 'biz',
    kitBuilder: () => Container(color: Colors.orange),
);
BizKitManager.instance.addKitWith(name: 'noAction', group: 'biz');
BizKitManager.instance.addKitWith(
    key: 'biz1_goBizPage1',
    name: 'goBizPage1',
    group: 'biz1',
    kitBuilder: () => TestBizPage1(),
);

// 添加业务分组的tip信息(需先注册对应的group,否则报错)
BizKitManager.instance.addKitGroupTip('biz1', 'dokit test biz1');

// 通过注册的key来手动通过代码打开一个业务入口对应的页面
Future.delayed(Duration(seconds: 1), () {
  BizKitManager.instance.open('biz1_goBizPage1');
  // 安全打开一个kitPage,和open的区别在于不会报错
  // BizKitManager.instance.safeOpen();
});

// 隐藏kitPage,不会删除上一次的打开记录
BizKitManager.instance.hide();
// 关闭kitPage,会删除上一次的打开记录
BizKitManager.instance.close();

// 如果传入的kitBuilder中的widget层级中没有包含Navigator(MaterialApp、WidgetApp等组件默认包含Navigator),则推荐使用,否则无法关闭
Navigator.of(context).pop();

日志查看

日志查看

查看使用print方式打印出来的日志,捕获的异常会以红色显示。超过7行的日志会自动折叠,点击可展开。长按复制日志到剪贴板。

网络请求

网络请求

可以捕获通过flutter httpclient发出的网络请求,主流的httpdio库底层也是通过httpclient实现的,也能捕获。

Method Channel信息

Method Channel信息

可以展示从dart端到native和从native端到dart端的方法调用、参数、返回结果。

路由信息

路由信息

展示当前页面的路由信息,当存在多层Navigator组件嵌套时,会展示多层的路由信息。

注:当前查找栈顶widget是通过遍历整棵widget tree的方式,如果添加了overlay,栈顶widget会始终指向overlay,导致该功能读取数据异常。

帧率

帧率

展示最近240帧的耗时情况,每次进入该页面刷新。debug模式下帧率会普遍偏高,profile和release模式下会比较正常。

内存

内存

当前已使用的内存和最大内存;底部搜索栏可以显示指定的类的对象数量和占用内存。

注:该功能通过VMService获取数据,release模式下无法使用

基本信息

基本信息

展示当前dart虚拟机进程、CPU、版本信息;当前app包名和dart工程构建版本信息。

注:该功能通过VMService获取数据,release模式下无法使用。flutter版本号需要flutter attach后才可获取

控件检查

控件检查

查看当前页面上的控件信息,包含位置、大小、源码信息、对齐标尺和查看build链等。

注:源码信息只有在debug模式下才可获取到。同路由功能,在存在Overlay的情况下功能会异常

颜色拾取

颜色拾取

查看当前页面任何位置对应的像素点的RGBA颜色值,方便UI的调试和获取像素点的颜色。

Widget层级

Widget层级

Widget层级

查看当前选中widget的树层级,以及它renderObject的详细build链等信息。

页面源码查看

页面源码查看

查看当前所在页面的源代码,支持语法高亮显示。

注:源码信息只有在debug模式下才可获取到。同路由功能,在存在Overlay的情况下功能会异常

页面启动耗时

页面启动耗时

页面启动耗时

获取页面的启动耗时,框架已做无侵入的注入NavigatorObserver。但是在较复杂的App构建时可能失效,需要手动添加DokitNavigatorObserver

注:页面启动耗时信息只有在profile或release模式下才有意义


示例代码

以下是完整的示例代码,展示了如何使用DoKit插件:

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:dokit/dokit.dart';
import 'package:dokit/kit/apm/vm/vm_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

import 'biz_page1.dart';
import 'page2.dart';

void main() {
  List<String> blackList = [
    'plugins.flutter.io/sensors/gyroscope',
    'plugins.flutter.io/sensors/user_accel',
    'plugins.flutter.io/sensors/accelerometer'
  ];

  DoKit.runApp(
      app: DoKitApp(MyApp()),
      useInRelease: true,
      logCallback: (log) {
        String i = log;
      },
      methodChannelBlackList: blackList,
      exceptionCallback: (dynamic obj, StackTrace trace) {
        print('dokit exception callback: $obj');
      });

  BizKitManager.instance.addKitWith(
      name: 'test1',
      group: 'biz',
      kitBuilder: () => Container(color: Colors.orange));
  BizKitManager.instance.addKitWith(name: 'noAction', group: 'biz');
  BizKitManager.instance.addKitWith(
    key: 'biz1_goBizPage1',
    name: 'goBizPage1',
    group: 'biz1',
    kitBuilder: () => TestBizPage1(),
  );

  BizKitManager.instance.addKitGroupTip('biz1', 'dokit test biz1');

  Future.delayed(Duration(seconds: 1), () {
    BizKitManager.instance.open('biz1_goBizPage1');
  });
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'DoKit Test',
      theme: ThemeData(primarySwatch: Colors.blue),
      visualDensity: VisualDensity.adaptivePlatformDensity,
      home: DoKitTestPage(),
    );
  }
}

class DoKitTestPage extends StatefulWidget {
  [@override](/user/override)
  _DoKitTestPageState createState() => _DoKitTestPageState();
}

class _DoKitTestPageState extends State<DoKitTestPage> {
  Timer timer;

  void testDownload() async {
    String url =
        'https://pt-starfile.didistatic.com/static/starfile/node20210220/895f1e95e30aba5dd56d6f2ccf768b57/GjzGU0Pvv11613804530384.zip';
    String savePath = await getPhoneLocalPath();
    String zipName = 'test.zip';
    Dio dio = new Dio();
    Response response = await dio.download(url, "$savePath/$zipName",
        onReceiveProgress: (received, total) {
      if (total != -1) {
        if (received == total) {
          print("下载完成 ✅ ");
        }
      }
    });
  }

  Future<String> getPhoneLocalPath() async {
    final directory = Theme.of(context).platform == TargetPlatform.android
        ? await getExternalStorageDirectory()
        : await getApplicationDocumentsDirectory();
    return directory.path;
  }

  void testMethodChannel() {
    timer?.cancel();
    timer = new Timer.periodic(new Duration(seconds: 2), (timer) async {
      const MethodChannel _kChannel =
          MethodChannel('plugins.flutter.io/package_info');
      final Map<String, dynamic> map =
          await _kChannel.invokeMapMethod<String, dynamic>('getAll');
    });
  }

  void stopAll() {
    print('stopAll');
    timer?.cancel();
    timer = null;
  }

  void mockHttpPost() async {
    timer?.cancel();
    timer = new Timer.periodic(new Duration(seconds: 2), (timer) async {
      HttpClient client = new HttpClient();
      String url = 'https://pinzhi.didichuxing.com/kop_stable/gateway?api=hhh';
      HttpClientRequest request = await client.postUrl(Uri.parse(url));
      Map<String, String> map1 = new Map();
      map1["v"] = "1.0";
      map1["month"] = "7";
      map1["day"] = "25";
      map1["key"] = "bd6e35a2691ae5bb8425c8631e475c2a";
      request.add(utf8.encode(json.encode(map1)));
      request.add(utf8.encode(json.encode(map1)));
      HttpClientResponse response = await request.close();
      String responseBody = await response.transform(utf8.decoder).join();
    });
  }

  void mockHttpGet() async {
    timer?.cancel();
    timer = new Timer.periodic(new Duration(seconds: 2), (timer) async {
      HttpClient client = new HttpClient();
      String url = 'https://www.baidu.com';
      HttpClientRequest request = await client.postUrl(Uri.parse(url));
      HttpClientResponse response = await request.close();
      String responseBody = await response.transform(utf8.decoder).join();
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      color: Colors.white,
      child: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(4)),
                  color: Color(0xffcccccc)),
              margin: EdgeInsets.only(bottom: 30),
              child: FlatButton(
                child: Text('Mock Http Post',
                    style: TextStyle(color: Color(0xff000000), fontSize: 18)),
                onPressed: mockHttpPost,
              ),
            ),
            Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(4)),
                  color: Color(0xffcccccc)),
              margin: EdgeInsets.only(bottom: 30),
              child: FlatButton(
                child: Text('Mock Http Get',
                    style: TextStyle(color: Color(0xff000000), fontSize: 18)),
                onPressed: mockHttpGet,
              ),
            ),
            Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(4)),
                  color: Color(0xffcccccc)),
              margin: EdgeInsets.only(bottom: 30),
              child: FlatButton(
                child: Text('Test Download',
                    style: TextStyle(color: Color(0xff000000), fontSize: 18)),
                onPressed: testDownload,
              ),
            ),
            Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(4)),
                  color: Color(0xffcccccc)),
              margin: EdgeInsets.only(bottom: 30),
              child: FlatButton(
                child: Text('Test Method Channel',
                    style: TextStyle(color: Color(0xff000000), fontSize: 18)),
                onPressed: testMethodChannel,
              ),
            ),
            Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(4)),
                  color: Color(0xffcccccc)),
              margin: EdgeInsets.only(bottom: 30),
              child: FlatButton(
                child: Text('Open Route Page',
                    style: TextStyle(color: Color(0xff000000), fontSize: 18)),
                onPressed: () {
                  Navigator.of(context, rootNavigator: false).push<void>(
                      new MaterialPageRoute(
                          builder: (context) {
                            return new TestPage2();
                          },
                          settings: new RouteSettings(name: 'page1')));
                },
              ),
            ),
            Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(4)),
                  color: Color(0xffcccccc)),
              margin: EdgeInsets.only(bottom: 30),
              child: FlatButton(
                child: Text('Test Get Page Script',
                    style: TextStyle(color: Color(0xff000000), fontSize: 18)),
                onPressed: () {
                  VmHelper.instance.testPrintScript();
                },
              ),
            ),
            Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(4)),
                  color: Color(0xffcccccc)),
              margin: EdgeInsets.only(bottom: 30),
              child: FlatButton(
                child: Text('Stop Timer',
                    style: TextStyle(color: Color(0xff000000), fontSize: 18)),
                onPressed: stopAll,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

更多关于Flutter开发调试插件dokit的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter开发调试插件dokit的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


DoKit 是滴滴开源的一款面向移动端开发者的多功能调试工具,支持 Android 和 iOS 平台。它提供了丰富的调试功能,如网络请求监控、视图层级查看、日志查看、性能监控等。对于 Flutter 开发者来说,DoKit 也提供了 Flutter 插件,方便在 Flutter 应用中进行调试。

以下是使用 DoKit 进行 Flutter 开发调试的基本步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 DoKit 的依赖:

dependencies:
  dokit: ^latest_version

然后运行 flutter pub get 来获取依赖。

2. 初始化 DoKit

在你的 Flutter 应用的 main.dart 文件中,初始化 DoKit:

import 'package:dokit/dokit.dart';

void main() {
  // 初始化 DoKit
  DoKit.init();

  runApp(MyApp());
}

3. 启动 DoKit

在应用启动后,你可以通过以下方式启动 DoKit 的调试面板:

import 'package:dokit/dokit.dart';

void main() {
  DoKit.init();

  runApp(MyApp());

  // 启动 DoKit 调试面板
  DoKit.show();
}

4. 使用 DoKit 功能

启动应用后,你会在屏幕上看到一个悬浮的 DoKit 图标。点击该图标,可以打开 DoKit 的调试面板,里面包含了各种调试工具,如:

  • 网络监控:查看应用中的网络请求,包括请求 URL、请求方法、响应时间、响应状态码等。
  • 视图层级:查看当前页面的视图层级结构,方便调试 UI 布局。
  • 日志查看:查看应用中的日志输出,支持过滤和搜索。
  • 性能监控:监控应用的 CPU、内存、FPS 等性能指标。
  • 自定义工具:你可以通过 DoKit 提供的 API 添加自定义的调试工具。

5. 自定义功能

DoKit 提供了丰富的 API,允许你自定义调试功能。例如,你可以添加自定义的日志输出:

import 'package:dokit/dokit.dart';

void logCustomMessage(String message) {
  DoKit.log(message, tag: 'CustomTag');
}

6. 调试与发布模式

在开发阶段,你可以启用 DoKit 的所有功能。但在发布应用时,建议移除 DoKit 的初始化代码,或者通过条件编译来禁用 DoKit:

void main() {
  // 仅在调试模式下启用 DoKit
  if (kDebugMode) {
    DoKit.init();
  }

  runApp(MyApp());
}
回到顶部