当前位置: 首页 > news >正文

flutter的包管理#资源管理#调试Flutter应用#Flutter异常捕获

2.5 包管理

2.5.1 简介

在软件开发中,很多时候有一些公共的库或 SDK 可能会被很多项目用到,因此,将这些代码单独抽到一个独立模块,然后哪个项目需要使用时再直接集成这个模块,便可大大提高开发效率。很多编程语言或开发工具都支持这种“模块共享”机制,如 Java 语言中这种独立模块会被打成一个 jar 包,Android 中的 aar 包,Web开发中的 npm 包等。为了方便表述,我们将这种可共享的独立模块统一称为“包”( Package)。

一个 App 在实际开发中往往会依赖很多包,而这些包通常都有交叉依赖关系、版本依赖等,如果由开发者手动来管理应用中的依赖包将会非常麻烦。因此,各种开发生态或编程语言官方通常都会提供一些包管理工具,比如在 Android 提供了 Gradle 来管理依赖,iOS 用 Cocoapods 或 Carthage 来管理依赖,Node 中通过 npm 等。而在 Flutter 开发中也有自己的包管理工具。本节我们主要介绍一下 Flutter 如何使用配置文件pubspec.yaml(位于项目根目录)来管理第三方依赖包。

YAML 是一种直观、可读性高并且容易被人类阅读的文件格式,和 xml 或 Json 相比它语法简单并非常容易解析,所以 YAML 常用于配置文件,Flutter 也是用 yaml 文件作为其配置文件。Flutter 项目默认的配置文件是pubspec.yaml我们看一个简单的示例:

name: flutter_in_action
description: First Flutter Application.version: 1.0.0+1dependencies:flutter:sdk: fluttercupertino_icons: ^0.1.2dev_dependencies:flutter_test:sdk: flutterflutter:uses-material-design: true

下面,我们逐一解释一下各个字段的意义:

  • name:应用或包名称。
  • description: 应用或包的描述、简介。
  • version:应用或包的版本号。
  • dependencies:应用或包依赖的其他包或插件。
  • dev_dependencies:开发环境依赖的工具包(而不是flutter应用本身依赖的包)。
  • flutter:flutter相关的配置选项。

如果我们的Flutter应用本身依赖某个包,我们需要将所依赖的包添加到dependencies 下,接下来我们通过一个例子来演示一下如何添加、下载并使用第三方包。

#2.5.2 Pub仓库

Pub(https://pub.dev/ )是 Google 官方的 Dart Packages 仓库,类似于 node 中的 npm仓库、Android中的 jcenter。我们可以在 Pub 上面查找我们需要的包和插件,也可以向 Pub 发布我们的包和插件。我们将在后面的章节中介绍如何向 Pub 发布我们的包和插件。

#2.5.3 示例

接下来,我们实现一个显示随机字符串的 widget。有一个名为 “english_words” 的开源软件包,其中包含数千个常用的英文单词以及一些实用功能。我们首先在 pub 上找到 english_words 这个包(如图2-12所示),确定其最新的版本号和是否支持 Flutter。

图2-12

我们看到“english_words”包最新的版本是4.0.0,并且支持flutter,接下来:

  1. 将“english_words” 添加到依赖项列表,如下:

    dependencies:flutter:sdk: flutter# 新添加的依赖english_words: ^4.0.0
    
  2. 下载包。在Android Studio的编辑器视图中查看pubspec.yaml时(图2-13),单击右上角的 Pub get 。

    图2-13

    这会将依赖包安装到您的项目。我们可以在控制台中看到以下内容:

    flutter packages get
    Running "flutter packages get" in flutter_in_action...
    Process finished with exit code 0
    

    我们也可以在控制台,定位到当前工程目录,然后手动运行flutter packages get 命令来下载依赖包。另外,需要注意dependenciesdev_dependencies的区别,前者的依赖包将作为App的源码的一部分参与编译,生成最终的安装包。而后者的依赖包只是作为开发阶段的一些工具包,主要是用于帮助我们提高开发、测试效率,比如 flutter 的自动化测试包等。

  3. 引入english_words包。

    import 'package:english_words/english_words.dart';
    

    在输入时,Android Studio会自动提供有关库导入的建议选项。导入后该行代码将会显示为灰色,表示导入的库尚未使用。

  4. 使用english_words包来生成随机字符串。

    class RandomWordsWidget extends StatelessWidget {@overrideWidget build(BuildContext context) {// 生成随机字符串final wordPair = WordPair.random();return Padding(padding: const EdgeInsets.all(8.0),child: Text(wordPair.toString()),);}
    }
    

    我们将RandomWordsWidget 添加到 _MyHomePageState.build 的Column的子widget中。

    Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[... //省略无关代码RandomWordsWidget(),],
    )
    
  5. 如果应用程序正在运行,请使用热重载按钮(⚡️图标) 更新正在运行的应用程序。每次单击热重载或保存项目时,都会在正在运行的应用程序中随机选择不同的单词对。 这是因为单词对是在 build 方法内部生成的。每次热更新时,build方法都会被执行,运行效果如图2-14所示。

    图2-14

#2.5.4 其他依赖方式

上文所述的依赖方式是依赖Pub仓库的。但我们还可以依赖本地包和git仓库。

  • 依赖本地包

    如果我们正在本地开发一个包,包名为pkg1,我们可以通过下面方式依赖:

    dependencies:pkg1:path: ../../code/pkg1
    

    路径可以是相对的,也可以是绝对的。

  • 依赖Git:你也可以依赖存储在Git仓库中的包。如果软件包位于仓库的根目录中,请使用以下语法

    dependencies:pkg1:git:url: git://github.com/xxx/pkg1.git
    

    上面假定包位于Git存储库的根目录中。如果不是这种情况,可以使用path参数指定相对位置,例如:

    dependencies:package1:git:url: git://github.com/flutter/packages.gitpath: packages/package1        
    

上面介绍的这些依赖方式是Flutter开发中常用的,但还有一些其他依赖方式,完整的内容读者可以自行查看:https://www.dartlang.org/tools/pub/dependencies 。

2.6 资源管理

Flutter APP 安装包中会包含代码和 assets(资源)两部分。Assets 是会打包到程序安装包中的,可在运行时访问。常见类型的 assets 包括静态数据(例如JSON文件)、配置文件、图标和图片等。

#2.6.1 指定 assets

和包管理一样,Flutter 也使用pubspec.yaml (opens new window)文件来管理应用程序所需的资源,举个例子:

flutter:assets:- assets/my_icon.png- assets/background.png

assets指定应包含在应用程序中的文件, 每个 asset 都通过相对于pubspec.yaml文件所在的文件系统路径来标识自身的路径。asset 的声明顺序是无关紧要的,asset的实际目录可以是任意文件夹(在本示例中是assets 文件夹)。

在构建期间,Flutter 将 asset 放置到称为 asset bundle 的特殊存档中,应用程序可以在运行时读取它们(但不能修改)。

#2.6.2 Asset 变体(variant)

构建过程支持“asset变体”的概念:不同版本的 asset 可能会显示在不同的上下文中。 在pubspec.yaml的assets 部分中指定 asset 路径时,构建过程中,会在相邻子目录中查找具有相同名称的任何文件。这些文件随后会与指定的 asset 一起被包含在 asset bundle 中。

例如,如果应用程序目录中有以下文件:

  • …/pubspec.yaml
  • …/graphics/my_icon.png
  • …/graphics/background.png
  • …/graphics/dark/background.png
  • ….

然后pubspec.yaml文件中只需包含:

flutter:assets:- graphics/background.png

那么这两个graphics/background.pnggraphics/dark/background.png 都将包含在您的 asset bundle中。前者被认为是_main asset_ (主资源),后者被认为是一种变体(variant)。

在选择匹配当前设备分辨率的图片时,Flutter会使用到 asset 变体(见下文)。

#2.6.3 加载 assets

您的应用可以通过AssetBundle (opens new window)对象访问其 asset 。有两种主要方法允许从 Asset bundle 中加载字符串或图片(二进制)文件。

#1. 加载文本assets

  • 通过rootBundle (opens new window)对象加载:每个Flutter应用程序都有一个rootBundle (opens new window)对象, 通过它可以轻松访问主资源包,直接使用package:flutter/services.dart中全局静态的rootBundle对象来加载asset即可。
  • 通过 DefaultAssetBundle (opens new window)加载:建议使用 DefaultAssetBundle (opens new window)来获取当前 BuildContext 的AssetBundle。 这种方法不是使用应用程序构建的默认 asset bundle,而是使父级 widget 在运行时动态替换的不同的 AssetBundle,这对于本地化或测试场景很有用。

通常,可以使用DefaultAssetBundle.of()在应用运行时来间接加载 asset(例如JSON文件),而在widget 上下文之外,或其他AssetBundle句柄不可用时,可以使用rootBundle直接加载这些 asset,例如:

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;Future<String> loadAsset() async {return await rootBundle.loadString('assets/config.json');
}

#2. 加载图片

类似于原生开发,Flutter也可以为当前设备加载适合其分辨率的图像。

#1)声明分辨率相关的图片 assets

AssetImage (opens new window)可以将asset的请求逻辑映射到最接近当前设备像素比例(dpi)的asset。为了使这种映射起作用,必须根据特定的目录结构来保存asset:

  • …/image.png 
  • …/Mx/image.png
  • …/Nx/image.png

其中 M 和 N 是数字标识符,对应于其中包含的图像的分辨率,也就是说,它们指定不同设备像素比例的图片。

主资源默认对应于1.0倍的分辨率图片。看一个例子:

  • …/my_icon.png
  • …/2.0x/my_icon.png
  • …/3.0x/my_icon.png

在设备像素比率为1.8的设备上,.../2.0x/my_icon.png 将被选择。对于2.7的设备像素比率,.../3.0x/my_icon.png将被选择。

如果未在Image widget上指定渲染图像的宽度和高度,那么Image widget将占用与主资源相同的屏幕空间大小。 也就是说,如果.../my_icon.png是72px乘72px,那么.../3.0x/my_icon.png应该是216px乘216px; 但如果未指定宽度和高度,它们都将渲染为72像素×72像素(以逻辑像素为单位)。

pubspec.yaml中asset部分中的每一项都应与实际文件相对应,但主资源项除外。当主资源缺少某个资源时,会按分辨率从低到高的顺序去选择 ,也就是说1x中没有的话会在2x中找,2x中还没有的话就在3x中找。

#2)加载图片

要加载图片,可以使用 AssetImage (opens new window)类。例如,我们可以从上面的asset声明中加载背景图片:

Widget build(BuildContext context) {return DecoratedBox(decoration: BoxDecoration(image: DecorationImage(image: AssetImage('graphics/background.png'),),),);
}

注意,AssetImage 并非是一个widget, 它实际上是一个ImageProvider,有些时候你可能期望直接得到一个显示图片的widget,那么你可以使用Image.asset()方法,如:

Widget build(BuildContext context) {return Image.asset('graphics/background.png');
}

使用默认的 asset bundle 加载资源时,内部会自动处理分辨率等,这些处理对开发者来说是无感知的。 (如果使用一些更低级别的类,如 ImageStream (opens new window)或 ImageCache (opens new window)时你会注意到有与缩放相关的参数)

#3)依赖包中的资源图片

要加载依赖包中的图像,必须给AssetImage提供package参数。

例如,假设您的应用程序依赖于一个名为“my_icons”的包,它具有如下目录结构:

  • …/pubspec.yaml
  • …/icons/heart.png
  • …/icons/1.5x/heart.png
  • …/icons/2.0x/heart.png

然后加载图像,使用:

AssetImage('icons/heart.png', package: 'my_icons')

Image.asset('icons/heart.png', package: 'my_icons')

注意:包在使用本身的资源时也应该加上package参数来获取。

打包包中的 assets

如果在pubspec.yaml文件中声明了期望的资源,它将会打包到相应的package中。特别是,包本身使用的资源必须在pubspec.yaml中指定。

包也可以选择在其lib/文件夹中包含未在其pubspec.yaml文件中声明的资源。在这种情况下,对于要打包的图片,应用程序必须在pubspec.yaml中指定包含哪些图像。 例如,一个名为“fancy_backgrounds”的包,可能包含以下文件:

  • …/lib/backgrounds/background1.png
  • …/lib/backgrounds/background2.png
  • …/lib/backgrounds/background3.png

要包含第一张图像,必须在pubspec.yaml的assets部分中声明它:

flutter:assets:- packages/fancy_backgrounds/backgrounds/background1.png

lib/是隐含的,所以它不应该包含在资产路径中。

#3. 特定平台 assets

上面的资源都是flutter应用中的,这些资源只有在Flutter框架运行之后才能使用,如果要给我们的应用设置APP图标或者添加启动图,那我们必须使用特定平台的assets。

#1)设置APP图标

更新Flutter应用程序启动图标的方式与在本机Android或iOS应用程序中更新启动图标的方式相同。

  • Android

    在 Flutter 项目的根目录中,导航到.../android/app/src/main/res目录,里面包含了各种资源文件夹(如mipmap-hdpi已包含占位符图像 “ic_launcher.png”,见图2-15)。 只需按照Android开发人员指南 (opens new window)中的说明, 将其替换为所需的资源,并遵守每种屏幕密度(dpi)的建议图标大小标准。

    图2-15

    注意: 如果您重命名.png文件,则还必须在您AndroidManifest.xml<application>标签的android:icon属性中更新名称。

  • iOS

    在Flutter项目的根目录中,导航到.../ios/Runner。该目录中Assets.xcassets/AppIcon.appiconset已经包含占位符图片(见图2-16), 只需将它们替换为适当大小的图片,保留原始文件名称。

    图2-16

#2)更新启动页

图2-17

在 Flutter 框架加载时,Flutter 会使用本地平台机制绘制启动页。此启动页将持续到Flutter渲染应用程序的第一帧时。

注意: 这意味着如果您不在应用程序的main()方法中调用runApp (opens new window)函数 (或者更具体地说,如果您不调用window.render (opens new window)去响应window.onDrawFrame (opens new window))的话, 启动屏幕将永远持续显示。

  • Android

要将启动屏幕(splash screen)添加到您的Flutter应用程序, 请导航至.../android/app/src/main。在res/drawable/launch_background.xml,通过自定义drawable来实现自定义启动界面(你也可以直接换一张图片)。

  • iOS

要将图片添加到启动屏幕(splash screen)的中心,请导航至.../ios/Runner。在Assets.xcassets/LaunchImage.imageset, 拖入图片,并命名为LaunchImage.pngLaunchImage@2x.pngLaunchImage@3x.png。 如果你使用不同的文件名,那您还必须更新同一目录中的Contents.json文件,图片的具体尺寸可以查看苹果官方的标准。

您也可以通过打开Xcode完全自定义storyboard。在Project Navigator中导航到Runner/Runner然后通过打开Assets.xcassets拖入图片,或者通过在LaunchScreen.storyboard中使用Interface Builder进行自定义,如图2-18所示。

图2-18

#2.6.4 平台共享 assets

如果我们采用的是Flutter+原生的开发模式,那么可能会存Flutter和原生需要共享资源的情况,比如Flutter项目中已经有了一张图片A,如果原生代码中也要使用A,我们可以将A拷贝一份到原生项目的特定目录,这样的话虽然功能可以实现,但是最终的应用程序包会变大,因为包含了重复的资源,为了解决这个问题,Flutter 提供了一种Flutter和原生之间共享资源的方式,由于实现上需要涉及平台相关的原生代码,故本书不做展开,读者有需要可以自行查阅官方文档 (opens new window)。

2.7 调试Flutter应用

有各种各样的工具和功能来帮助调试Flutter应用程序。

#2.7.1 日志与断点

#1. debugger() 声明

当使用Dart Observatory(或另一个Dart调试器,例如IntelliJ IDE中的调试器)时,可以使用该debugger()语句插入编程式断点。要使用这个,你必须添加import 'dart:developer';到相关文件顶部。

debugger()语句采用一个可选when参数,我们可以指定该参数仅在特定条件为真时中断,如下所示:

void someFunction(double offset) {debugger(when: offset > 30.0);// ...
}

#2. printdebugPrintflutter logs

Dart print()功能将输出到系统控制台,我们可以使用flutter logs来查看它(基本上是一个包装adb logcat)。

如果你一次输出太多,那么Android有时会丢弃一些日志行。为了避免这种情况,我们可以使用Flutter的foundation库中的debugPrint() (opens new window),它封装了 print,将一次输出的内容长度限制在一个级别(内容过多时会分批输出),避免被Android内核丢弃。

Flutter框架中的许多类都有toString实现,按照惯例,输出信息通过包括对象的运行时类型 、类名以及关键字段等信息。 树中的一些类也具有toStringDeep实现,从该点返回整个子树的多行描述。一些具有详细信息toString的类会实现一个toStringShort,它只返回对象的类型或其他非常简短的(一个或两个单词)描述。

#3. 调试模式断言

在Flutter应用调试过程中,Dart assert语句被启用,并且 Flutter 框架使用它来执行许多运行时检查来验证是否违反一些不可变的规则。当一个某个规则被违反时,就会在控制台打印错误日志,并带上一些上下文信息来帮助追踪问题的根源。

要关闭调试模式并使用发布模式,请使用flutter run --release运行我们的应用程序。 这也关闭了Observatory调试器。一个中间模式可以关闭除Observatory之外所有调试辅助工具的,称为“profile mode”,用--profile替代--release即可。

#4. 断点

开发过程中,断点是最实用的调试工具之一,我们以 Android Studio 为例,如图2-19:

2-19

我们在 93 行打了一个断点,一旦代码执行到这一行就会暂停,这时我们可以看到当前上下文所有变量的值,然后可以选择一步一步的执行代码。关于如何通过 IDE 来打断点,网上教程很多,读者可以自行搜索。

#2.7.2 调试应用程序层

Flutter框架的每一层都提供了将其当前状态或事件转储(dump)到控制台(使用debugPrint)的功能。

#1. Widget 树

要转储Widgets树的状态,请调用debugDumpApp() (opens new window)。 只要应用程序已经构建了至少一次(即在调用build()之后的任何时间),我们可以在应用程序未处于构建阶段(即,不在build()方法内调用 )的任何时间调用此方法(在调用runApp()之后)。

如, 这个应用程序:

import 'package:flutter/material.dart';void main() {runApp(MaterialApp(home: AppHome(),),);
}class AppHome extends StatelessWidget {@overrideWidget build(BuildContext context) {return Material(child: Center(child: TextButton(onPressed: () {debugDumpApp();},child: Text('Dump App'),),),);}
}

…会输出这样的内容(精确的细节会根据框架的版本、设备的大小等等而变化):

I/flutter ( 6559): WidgetsFlutterBinding - CHECKED MODE
I/flutter ( 6559): RenderObjectToWidgetAdapter<RenderBox>([GlobalObjectKey RenderView(497039273)]; renderObject: RenderView)
I/flutter ( 6559): └MaterialApp(state: _MaterialAppState(1009803148))
I/flutter ( 6559):  └ScrollConfiguration()
I/flutter ( 6559):   └AnimatedTheme(duration: 200ms; state: _AnimatedThemeState(543295893; ticker inactive; ThemeDataTween(ThemeData(Brightness.light Color(0xff2196f3) etc...) → null)))
I/flutter ( 6559):    └Theme(ThemeData(Brightness.light Color(0xff2196f3) etc...))
I/flutter ( 6559):     └WidgetsApp([GlobalObjectKey _MaterialAppState(1009803148)]; state: _WidgetsAppState(552902158))
I/flutter ( 6559):      └CheckedModeBanner()
I/flutter ( 6559):       └Banner()
I/flutter ( 6559):        └CustomPaint(renderObject: RenderCustomPaint)
I/flutter ( 6559):         └DefaultTextStyle(inherit: true; color: Color(0xd0ff0000); family: "monospace"; size: 48.0; weight: 900; decoration: double Color(0xffffff00) TextDecoration.underline)
I/flutter ( 6559):          └MediaQuery(MediaQueryData(size: Size(411.4, 683.4), devicePixelRatio: 2.625, textScaleFactor: 1.0, padding: EdgeInsets(0.0, 24.0, 0.0, 0.0)))
I/flutter ( 6559):           └LocaleQuery(null)
I/flutter ( 6559):            └Title(color: Color(0xff2196f3))
... #省略剩余内容

这是一个“扁平化”的树,显示了通过各种构建函数投影的所有widget(如果你在widget树的根中调用toStringDeepwidget,这是你获得的树)。 你会看到很多在你的应用源代码中没有出现的widget,因为它们是被框架中widget的build()函数插入的。例如,InkFeature (opens new window)是Material widget的一个实现细节 。

当按钮从被按下变为被释放时debugDumpApp()被调用,TextButton对象同时调用setState(),并将自己标记为"dirty"。我们还可以查看已注册了哪些手势监听器; 在这种情况下,一个单一的GestureDetector被列出,并且监听“tap”手势(“tap”是TapGestureDetectortoStringShort函数输出的)。

如果我们编写自己的widget,则可以通过覆盖debugFillProperties() (opens new window)来添加信息。 将DiagnosticsProperty (opens new window)对象作为方法参数,并调用父类方法。 该函数是该toString方法用来填充小部件描述信息的。

#2. 渲染树

如果我们尝试调试布局问题,那么Widget树可能不够详细。在这种情况下,我们可以通过调用debugDumpRenderTree()转储渲染树。 正如debugDumpApp(),除布局或绘制阶段外,我们可以随时调用此函数。作为一般规则,从frame 回调 (opens new window)或事件处理器中调用它是最佳解决方案。

要调用debugDumpRenderTree(),我们需要添加import'package:flutter/rendering.dart';到我们的源文件。

上面这个小例子的输出结果如下所示:

I/flutter ( 6559): RenderView
I/flutter ( 6559):  │ debug mode enabled - android
I/flutter ( 6559):  │ window size: Size(1080.0, 1794.0) (in physical pixels)
I/flutter ( 6559):  │ device pixel ratio: 2.625 (physical pixels per logical pixel)
I/flutter ( 6559):  │ configuration: Size(411.4, 683.4) at 2.625x (in logical pixels)
I/flutter ( 6559):  │
I/flutter ( 6559):  └─child: RenderCustomPaint
I/flutter ( 6559):    │ creator: CustomPaint ← Banner ← CheckedModeBanner ←
I/flutter ( 6559):    │   WidgetsApp-[GlobalObjectKey _MaterialAppState(1009803148)] ←
I/flutter ( 6559):    │   Theme ← AnimatedTheme ← ScrollConfiguration ← MaterialApp ←
I/flutter ( 6559):    │   [root]
I/flutter ( 6559):    │ parentData: <none>
I/flutter ( 6559):    │ constraints: BoxConstraints(w=411.4, h=683.4)
I/flutter ( 6559):    │ size: Size(411.4, 683.4)
... # 省略

这是根RenderObject对象的toStringDeep函数的输出。

当调试布局问题时,关键要看的是sizeconstraints字段。约束沿着树向下传递,尺寸向上传递。

如果我们编写自己的渲染对象,则可以通过覆盖debugFillProperties() (opens new window)将信息添加到转储。 将DiagnosticsProperty (opens new window)对象作为方法的参数,并调用父类方法。

#3. Layer树

读者可以理解为渲染树是可以分层的,而最终绘制需要将不同的层合成起来,而Layer则是绘制时需要合成的层,如果我们尝试调试合成问题,则可以使用debugDumpLayerTree() (opens new window)。对于上面的例子,它会输出:

I/flutter : TransformLayer
I/flutter :  │ creator: [root]
I/flutter :  │ offset: Offset(0.0, 0.0)
I/flutter :  │ transform:
I/flutter :  │   [0] 3.5,0.0,0.0,0.0
I/flutter :  │   [1] 0.0,3.5,0.0,0.0
I/flutter :  │   [2] 0.0,0.0,1.0,0.0
I/flutter :  │   [3] 0.0,0.0,0.0,1.0
I/flutter :  │
I/flutter :  ├─child 1: OffsetLayer
I/flutter :  │ │ creator: RepaintBoundary ← _FocusScope ← Semantics ← Focus-[GlobalObjectKey MaterialPageRoute(560156430)] ← _ModalScope-[GlobalKey 328026813] ← _OverlayEntry-[GlobalKey 388965355] ← Stack ← Overlay-[GlobalKey 625702218] ← Navigator-[GlobalObjectKey _MaterialAppState(859106034)] ← Title ← ⋯
I/flutter :  │ │ offset: Offset(0.0, 0.0)
I/flutter :  │ │
I/flutter :  │ └─child 1: PictureLayer
I/flutter :  │
I/flutter :  └─child 2: PictureLayer

这是根LayertoStringDeep输出的。

根部的变换是应用设备像素比的变换; 在这种情况下,每个逻辑像素代表3.5个设备像素。

RepaintBoundary widget在渲染树的层中创建了一个RenderRepaintBoundary。这用于减少需要重绘的需求量。

#4. 语义

我们还可以调用debugDumpSemanticsTree() (opens new window)获取语义树(呈现给系统可访问性API的树)的转储。 要使用此功能,必须首先启用辅助功能,例如启用系统辅助工具或SemanticsDebugger (下面讨论)。

对于上面的例子,它会输出:

I/flutter : SemanticsNode(0; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4))
I/flutter :  ├SemanticsNode(1; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4))
I/flutter :  │ └SemanticsNode(2; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4); canBeTapped)
I/flutter :  └SemanticsNode(3; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4))
I/flutter :    └SemanticsNode(4; Rect.fromLTRB(0.0, 0.0, 82.0, 36.0); canBeTapped; "Dump App")

#5. 调度

要找出相对于帧的开始/结束事件发生的位置,可以切换debugPrintBeginFrameBanner (opens new window)和debugPrintEndFrameBanner (opens new window)布尔值以将帧的开始和结束打印到控制台。

例如:

I/flutter : ▄▄▄▄▄▄▄▄ Frame 12         30s 437.086ms ▄▄▄▄▄▄▄▄
I/flutter : Debug print: Am I performing this work more than once per frame?
I/flutter : Debug print: Am I performing this work more than once per frame?
I/flutter : ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

debugPrintScheduleFrameStacks (opens new window)还可以用来打印导致当前帧被调度的调用堆栈。

#6. 可视化调试

我们也可以通过设置debugPaintSizeEnabledtrue以可视方式调试布局问题。 这是来自rendering库的布尔值。它可以在任何时候启用,并在为true时影响绘制。 设置它的最简单方法是在void main()的顶部设置。

当它被启用时,所有的盒子都会得到一个明亮的深青色边框,padding(来自widget如Padding)显示为浅蓝色,子widget周围有一个深蓝色框, 对齐方式(来自widget如Center和Align)显示为黄色箭头. 空白(如没有任何子节点的Container)以灰色显示。

debugPaintBaselinesEnabled (opens new window)做了类似的事情,但对于具有基线的对象,文字基线以绿色显示,表意(ideographic)基线以橙色显示。

debugPaintPointersEnabled (opens new window)标志打开一个特殊模式,任何正在点击的对象都会以深青色突出显示。 这可以帮助我们确定某个对象是否以某种不正确的方式进行hit测试(Flutter检测点击的位置是否有能响应用户操作的widget),例如,如果它实际上超出了其父项的范围,首先不会考虑通过hit测试。

如果我们尝试调试合成图层,例如以确定是否以及在何处添加RepaintBoundary widget,则可以使用debugPaintLayerBordersEnabled (opens new window)标志, 该标志用橙色或轮廓线标出每个层的边界,或者使用debugRepaintRainbowEnabled (opens new window)标志, 只要他们重绘时,这会使该层被一组旋转色所覆盖。

所有这些标志只能在调试模式下工作。通常,Flutter框架中以“debug...” 开头的任何内容都只能在调试模式下工作。

#7. 调试动画

调试动画最简单的方法是减慢它们的速度。为此,请将timeDilation (opens new window)变量(在scheduler库中)设置为大于1.0的数字,例如50.0。 最好在应用程序启动时只设置一次。如果我们在运行中更改它,尤其是在动画运行时将其值改小,则在观察时可能会出现倒退,这可能会导致断言命中,并且这通常会干扰我们的开发工作。

#8. 调试性能问题

要了解我们的应用程序导致重新布局或重新绘制的原因,我们可以分别设置debugPrintMarkNeedsLayoutStacks (opens new window)和 debugPrintMarkNeedsPaintStacks (opens new window)标志。 每当渲染盒被要求重新布局和重新绘制时,这些都会将堆栈跟踪记录到控制台。如果这种方法对我们有用,我们可以使用services库中的debugPrintStack()方法按需打印堆栈痕迹。

#9. 统计应用启动时间

要收集有关Flutter应用程序启动所需时间的详细信息,可以在运行flutter run时使用trace-startupprofile选项。

$ flutter run --trace-startup --profile

跟踪输出保存为start_up_info.json,在Flutter工程目录在build目录下。输出列出了从应用程序启动到这些跟踪事件(以微秒捕获)所用的时间:

  • 进入Flutter引擎时.
  • 展示应用第一帧时.
  • 初始化Flutter框架时.
  • 完成Flutter框架初始化时.

如 :

{"engineEnterTimestampMicros": 96025565262,"timeToFirstFrameMicros": 2171978,"timeToFrameworkInitMicros": 514585,"timeAfterFrameworkInitMicros": 1657393
}

#10. 跟踪Dart代码性能

要执行自定义性能跟踪和测量Dart任意代码段的wall/CPU时间(类似于在Android上使用systrace (opens new window))。 使用dart:developer的Timeline (opens new window)工具来包含你想测试的代码块,例如:

Timeline.startSync('interesting function');
// iWonderHowLongThisTakes();
Timeline.finishSync();

然后打开你应用程序的Observatory timeline页面,在“Recorded Streams”中选择‘Dart’复选框,并执行你想测量的功能。

刷新页面将在Chrome的跟踪工具 (opens new window)中显示应用按时间顺序排列的timeline记录。

请确保运行flutter run时带有--profile标志,以确保运行时性能特征与我们的最终产品差异最小。

2.7.3 DevTools

Flutter DevTools 是 Flutter 可视化调试工具,如图2-20。它将各种调试工具和能力集成在一起,并提供可视化调试界面,它的功能很强大,掌握它会对我们开发和优化 Flutter 应用有很大帮助。由于 Flutter DevTools 功能很多,短篇幅是讲不完的,本书不做专门介绍,Flutter 官网对 DevTools 有详细的介绍,读者可以去官网查看相关教程。

2-20

2.8 Flutter异常捕获

在介绍Flutter异常捕获之前必须先了解一下Dart单线程模型,只有了解了Dart的代码执行流程,我们才能知道该在什么地方去捕获异常。

#2.8.1 Dart单线程模型

在 Java 和 Objective-C(以下简称“OC”)中,如果程序发生异常且没有被捕获,那么程序将会终止,但是这在Dart或JavaScript中则不会!究其原因,这和它们的运行机制有关系。Java 和 OC 都是多线程模型的编程语言,任意一个线程触发异常且该异常未被捕获时,就会导致整个进程退出。但 Dart 和 JavaScript 不会,它们都是单线程模型,运行机制很相似(但有区别),下面我们通过Dart官方提供的一张图(2-21)来看看 Dart 大致运行原理:

图2-21

Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列”  microtask queue,另一个叫做“事件队列” event queue。从图中可以发现,微任务队列的执行优先级高于事件队列。

现在我们来介绍一下Dart线程运行过程,如上图中所示,入口函数 main() 执行完后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,事件任务执行完毕后程序便会退出,但是,在事件任务执行的过程中也可以插入新的微任务和事件任务,在这种情况下,整个线程的执行过程便是一直在循环,不会退出,而Flutter中,主线程的执行过程正是如此,永不终止。

在Dart中,所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等,而微任务通常来源于Dart内部,并且微任务非常少,之所以如此,是因为微任务队列优先级高,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,对于GUI应用来说最直观的表现就是比较卡,所以必须得保证微任务队列不会太长。值得注意的是,我们可以通过Future.microtask(…)方法向微任务队列插入一个任务。

在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其他任务执行的。

#2.8.2 Flutter异常捕获

Dart中可以通过try/catch/finally来捕获代码块异常,这个和其他编程语言类似,如果读者不清楚,可以查看Dart语言文档,不再赘述,下面我们看看Flutter中的异常捕获。

#1. Flutter框架异常捕获

Flutter 框架为我们在很多关键的方法进行了异常捕获。这里举一个例子,当我们布局发生越界或不合规范时,Flutter就会自动弹出一个错误界面,这是因为Flutter已经在执行build方法时添加了异常捕获,最终的源码如下:

@override
void performRebuild() {...try {//执行build方法  built = build();} catch (e, stack) {// 有异常时则弹出错误提示  built = ErrorWidget.builder(_debugReportException('building $this', e, stack));} ...
}      

可以看到,在发生异常时,Flutter默认的处理方式是弹一个ErrorWidget,但如果我们想自己捕获异常并上报到报警平台的话应该怎么做?我们进入_debugReportException()方法看看:

FlutterErrorDetails _debugReportException(String context,dynamic exception,StackTrace stack, {InformationCollector informationCollector
}) {//构建错误详情对象  final FlutterErrorDetails details = FlutterErrorDetails(exception: exception,stack: stack,library: 'widgets library',context: context,informationCollector: informationCollector,);//报告错误 FlutterError.reportError(details);return details;
}

我们发现,错误是通过FlutterError.reportError方法上报的,继续跟踪:


static void reportError(FlutterErrorDetails details) {...if (onError != null)onError(details); //调用了onError回调
}

我们发现onErrorFlutterError的一个静态属性,它有一个默认的处理方法 dumpErrorToConsole,到这里就清晰了,如果我们想自己上报异常,只需要提供一个自定义的错误处理回调即可,如:

void main() {FlutterError.onError = (FlutterErrorDetails details) {reportError(details);};...
}

这样我们就可以处理那些Flutter为我们捕获的异常了,接下来我们看看如何捕获其他异常。

#2. 其他异常捕获与日志收集

在Flutter中,还有一些Flutter没有为我们捕获的异常,如调用空对象方法异常、Future中的异常。在Dart中,异常分两类:同步异常和异步异常,同步异常可以通过try/catch捕获,而异步异常则比较麻烦,如下面的代码是捕获不了Future的异常的:

try{Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
}catch (e){print(e)
}

Dart中有一个runZoned(...) 方法,可以给执行对象指定一个Zone。Zone表示一个代码执行的环境范围,为了方便理解,读者可以将Zone类比为一个代码执行沙箱,不同沙箱的之间是隔离的,沙箱可以捕获、拦截或修改一些代码行为,如Zone中可以捕获日志输出、Timer创建、微任务调度的行为,同时Zone也可以捕获所有未处理的异常。下面我们看看runZoned(...)方法定义:

R runZoned<R>(R body(), {Map zoneValues, ZoneSpecification zoneSpecification,
}) 
  • zoneValues: Zone 的私有数据,可以通过实例zone[key]获取,可以理解为每个“沙箱”的私有数据。

  • zoneSpecification:Zone的一些配置,可以自定义一些代码行为,比如拦截日志输出和错误等,举个例子

    runZoned(() => runApp(MyApp()),zoneSpecification: ZoneSpecification(// 拦截print 蜀西湖print: (Zone self, ZoneDelegate parent, Zone zone, String line) {parent.print(zone, "Interceptor: $line");},// 拦截未处理的异步错误handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone,Object error, StackTrace stackTrace) {parent.print(zone, '${error.toString()} $stackTrace');},),
    );
    

    这样一来,我们 APP 中所有调用print方法输出日志的行为都会被拦截,通过这种方式,我们也可以在应用中记录日志,等到应用触发未捕获的异常时,将异常信息和日志统一上报。

    另外我们还拦截了未被捕获的异步错误,这样一来,结合上面的 FlutterError.onError 我们就可以捕获我们Flutter应用错误了并进行上报了!

#3. 最终的错误上报代码

我们最终的异常捕获和上报代码大致如下:

void collectLog(String line){... //收集日志
}
void reportErrorAndLog(FlutterErrorDetails details){... //上报错误和日志逻辑
}FlutterErrorDetails makeDetails(Object obj, StackTrace stack){...// 构建错误信息
}void main() {var onError = FlutterError.onError; //先将 onerror 保存起来FlutterError.onError = (FlutterErrorDetails details) {onError?.call(details); //调用默认的onErrorreportErrorAndLog(details); //上报};runZoned(() => runApp(MyApp()),zoneSpecification: ZoneSpecification(// 拦截printprint: (Zone self, ZoneDelegate parent, Zone zone, String line) {collectLog(line);parent.print(zone, "Interceptor: $line");},// 拦截未处理的异步错误handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone,Object error, StackTrace stackTrace) {reportErrorAndLog(details);parent.print(zone, '${error.toString()} $stackTrace');},),);
}

相关文章:

flutter的包管理#资源管理#调试Flutter应用#Flutter异常捕获

2.5 包管理 2.5.1 简介 在软件开发中&#xff0c;很多时候有一些公共的库或 SDK 可能会被很多项目用到&#xff0c;因此&#xff0c;将这些代码单独抽到一个独立模块&#xff0c;然后哪个项目需要使用时再直接集成这个模块&#xff0c;便可大大提高开发效率。很多编程语言或开…...

Unity Netcode自定义数据传输——结构体及其序列化

在 Unity Netcode 中&#xff0c;要实现自定义数据的网络传输&#xff0c;确实需要两个关键部分&#xff1a; ✅ 两个必需组件&#xff1a; 数据结构定义 public struct PlayerState : INetworkSerializable {public int id; // 字段1&#xff1a;玩家IDpublic bool …...

Vue 3 高级编程技巧

Vue 3 高级编程技巧 1. 计算属性 (Computed Properties) 含义&#xff1a;计算属性在依赖变化时会自动更新。以下是一个示例&#xff0c;展示当 firstName 或 lastName 变化时&#xff0c;fullName 也会更新。 实例&#xff1a; <script setup> import { ref, comput…...

GraphQL注入 -- GPN CTF 2025 Real Christmas

part 1 服务器会每段时间禁用已注册的账号,此处存在漏洞 def deactivate_user_graphql(email):graphql_endpoint current_app.config["GRAPHQL_ENDPOINT"]query f"""mutation {{deactivateUser (user: {{email: "{email}"}}){{ success…...

python打卡day43

疏锦行 作业&#xff1a; kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 进阶&#xff1a;并拆分成多个文件 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms# 数据预处理 tra…...

ethers.js express vue2 定时任务每天凌晨2点监听合约地址数据同步到Mysql整理

下面是一个完整的 Ethers.js Express Vue2 MySQL 实现方案&#xff0c;用于&#xff1a; &#x1f4a1;每天凌晨 2 点监听某合约地址的 Transfer 事件&#xff0c;写入 MySQL 数据库&#xff0c;并展示每日 NFT 交易量图表&#xff08;Vue2 ECharts&#xff09; ✅ 后端部分…...

内网穿透和端口映射的区别在哪?局域网提供互联网访问方案对比选择详解

内网穿透和端口映射是两个经常被提及的概念&#xff0c;它们对于实现网络中的内外网通信起着关键作用。内网穿透和端口映射都能够有效地将本地局域网地址提供给互联网上外网访问&#xff0c;但二者之间存在着显著的区别。 内网穿透与端口映射的核心区别在于实现方式和依赖条件…...

机器学习---正则化、过拟合抑制与特征筛选

专栏:机器学习 个人主页:云端筑梦狮 注&#xff1a;上一篇机器学习还差一小节&#xff0c;日后坑必会填上 一.正则化 什么是正则化 / 如何进行正则化 其实机器学习中正则化&#xff08;regularization&#xff09;的外在形式非常简单&#xff0c;就是在模型的损失函数中加…...

优化 ArcPy 脚本性能

合理设置环境变量 优化环境变量配置 ArcPy 提供了许多环境变量&#xff0c;用于控制地理处理工具的行为。合理设置环境变量可以优化脚本的性能。例如&#xff0c;设置“workspace”环境变量可以指定默认的工作空间&#xff0c;避免在脚本中重复指定工作空间路径。 Python 复制…...

Robyn高性能Web框架系列06:使用WebSocket实现产品智能助理

使用WebSocket实现产品智能助理 WebSocket原理与应用场景Robyn的WebSocket基本使用1、创建WebSocket服务2、侦听WebSocket事件3、向客户端发送消息4、向客户端广播消息5、使用查询参数6、主动关闭连接 示例&#xff1a;简易的产品智能助理1、产品数据部分2、产品信息部分3、智能…...

UDP 缓冲区

UDP 有接收缓冲区&#xff0c;没有发送缓冲区 引申问题 1、为什么没有发送缓冲区&#xff1f; 直接引用原文 “因为 UDP 是不可靠的&#xff0c;它不必保存应用进程的数据拷贝&#xff0c;因此无需一个真正的发送缓冲区” 2、没有发送缓冲区的情况下&#xff0c;sendto 的数…...

物联网与低代码:Node-RED如何赋能工业智能化与纵横智控的创新实践

在数字化浪潮席卷全球的今天&#xff0c;物联网&#xff08;IoT&#xff09;已从概念走向现实&#xff0c;成为连接物理世界与数字世界的关键桥梁。它通过将日常物品、工业设备等“物”嵌入传感器、软件及其他技术&#xff0c;使其能够通过网络相互连接并交换数据&#xff0c;从…...

【甲方安全视角】开源的安全悖论

文章目录 安全的充分必要条件&#xff1a;从「符号化信任」到「验证驱动安全」构建与分发的不可信链条迭代与审计的节奏错位代码透明与攻击面的对等暴露对普通用户的建议选择可信项目与品牌始终通过官方渠道获取软件注意权限与环境安全对“签名请求”、“连接钱包”等敏感操作保…...

GEO生成式引擎优化发展迅猛:热点数智化传播是GEO最佳路径

在人工智能技术浪潮的推动下&#xff0c;GEO生成式引擎优化已跃升为行业技术演进与产业发展相融合的核心赛道。通过系统性梳理其发展脉络&#xff0c;我们可清晰勾勒出技术突破与产业变革交织的演进轨迹&#xff0c;其发展进程包含以下重要节点。 2023年4月&#xff0c;GPT-4发…...

【unity游戏开发——网络】计算机网络中的三种数据管理模型(分散式、集中式、分布式)和三大通信模型(C/S、B/S、P2P)

注意&#xff1a;考虑到热更新的内容比较多&#xff0c;我将热更新的内容分开&#xff0c;并全部整合放在【unity游戏开发——网络】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 文章目录 一、数据管理模型1、分散式 (Decentralized - 各管各的)2、集中式 (Centra…...

MR30分布式 IO在物流堆垛机的应用

在现代物流行业蓬勃发展的浪潮中&#xff0c;物流堆垛机作为自动化仓储系统的核心设备&#xff0c;承担着货物的高效存取与搬运任务。它凭借自动化操作、高精度定位等优势&#xff0c;极大地提升了仓储空间利用率和货物周转效率。然而&#xff0c;随着物流行业的高速发展&#…...

香港维尔利健康科技集团推出AI辅助医学影像训练平台,助力医护人才数字化转型

香港维尔利健康科技集团近日正式发布其自主研发的“AI辅助医学影像训练平台&#xff08;V-MedTrain&#xff09;”&#xff0c;这一创新平台的上线&#xff0c;标志着医学影像教育迈入智能化辅助教学新时代。依托人工智能与大数据分析技术&#xff0c;香港维尔利健康科技集团在…...

2025学年湖北省职业院校技能大赛 “信息安全管理与评估”赛项 样题卷(五)

2025学年湖北省职业院校技能大赛 “信息安全管理与评估”赛项 样题卷&#xff08;五&#xff09; 第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务 1&#xff1a;应急响应&#xff08;可以培训有答案&#xff09;任务 2&#xff1a;通信数据分析取…...

基于 Python 的批量文件重命名软件设计与实现

在工作过程中,经常有很多文件,想要对文件名进行批量改名,特此写了一个程序,以实现此功能。 一、批量文件重命名软件设计原理 (一)核心原理阐述 批量文件重命名软件的核心原理在于运用操作系统提供的文件管理功能,借助编程手段达成对文件名称的批量修改。在这个软件里,…...

【深度学习新浪潮】什么是上下文工程?

什么是上下文工程? 上下文工程(Context Engineering) 是指通过设计、优化与大语言模型(LLM)交互时的输入内容(即“上下文”),引导模型生成更符合预期、更精准回答的系统性方法。这里的“上下文”通常包括 提示词(Prompt)、示例(Few-Shot Examples)、历史对话记录、…...

逆向入门(8)汇编篇-rol指令的学习

还是那个题&#xff0c;这回又碰到个循环左移&#xff0c;有挺多操作方法之前都没有系统的学&#xff0c;用到的时候再看看感觉还挺好&#xff0c;不耽误事 0x00 基本介绍 ROL(Rotate Left): 循环左移&#xff0c;它有两个操作数&#xff1a; 第一个操作数是目标操作数&#…...

Fisco Bcos学习 - 开发第一个区块链应用

文章目录 一、前言二、业务场景分析&#xff1a;简易资产管理系统三、智能合约设计与实现3.1 存储结构设计3.2 接口设计3.3 完整合约代码 四、合约编译与Java接口生成五、SDK配置与项目搭建5.1 获取Java工程项目5.2 项目目录结构5.3 引入Web3SDK5.4 证书与配置文件 六、业务开发…...

黑马python(十六)

目录&#xff1a; 1.JSON数据格式的转换 2.pyecharts模块简介 3.pyecharts入门使用 4.数据准备 5.生成折线图 1.JSON数据格式的转换 2.pyecharts模块简介 官方网站&#xff1a; 画廊网站:有更多的图标形式 测试是否安装 3.pyecharts入门使用 运行会生成一个html的文件&a…...

完成国产化替代!昆明卷烟厂用时序数据库 TDengine 重塑工业时序数据平台

小T导读&#xff1a;昆明卷烟厂作为红云红河烟草&#xff08;集团&#xff09;有限责任公司的重要组成部分&#xff0c;是集团卷烟生产的核心工厂。早期在建设制造执行系统&#xff08;MES&#xff09;时&#xff0c;其采用了 Wonderware 平台的时序数据存储功能模块&#xff0…...

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | BackgroundSlider(背景滑块)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— BackgroundSlider组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ 使用 Vue 3 的 Composition API 和 <script setup> …...

Wpf的Binding

前言 wpf的Binding就像一个桥梁&#xff0c;它的作用就是连接逻辑层与界面层&#xff0c;既能够把逻辑层的数据搬到界面层展示&#xff0c;又能将界面层的数据更改后传递到逻辑层&#xff0c;Binding的数据来源就是Binding的源&#xff0c;数据展示的地方就是Binding的目标。 …...

Redis—持久化

持久化 在mysql当中&#xff0c;有4个比较关心的特性&#xff0c;分别是原子性、一致性、隔离性和持久性。这里的持久性和持久化是一回事。我们该如何判断是否具有持久性呢&#xff1f;答案就是看重启进程或者主机之后&#xff0c;数据是否存在。当我们把数据存储在硬盘上是就…...

Spring Boot中日志管理与异常处理

以下是Spring Boot中日志管理与异常处理的系统化实践指南&#xff0c;结合最佳实践与核心配置&#xff0c;确保应用健壮性与可维护性。 &#x1f4ca; 一、日志管理核心配置 默认框架与级别控制 Logback 是Spring Boot默认日志框架&#xff0c;通过application.yml快速配置&…...

基于MATLAB的BP神经网络的心电图分类方法应用

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 心电图&#xff08;ECG&#xff09;是临床诊断心血管疾病的重要工具&#xff0c;能够反映心脏电活动的周期性变化。…...

【笔记】Docker 配置阿里云镜像加速(公共地址即开即用,无需手动创建实例)

2025年06月25日记 【好用但慎用】Windows 系统中将所有 WSL 发行版从 C 盘迁移到 非系统 盘的完整笔记&#xff08;附 异常处理&#xff09;-CSDN博客 【笔记】解决 WSL 迁移后 Docker 出现 “starting services: initializing Docker API Proxy: setting up docker ap” 问题…...

Java 中LinkedList 总结

406.根据身高重建队列 力扣题目链接(opens new window) 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &#xff0c;前面 正好 有 ki 个身高…...

微信小程序 / UNIAPP --- 阻止小程序返回(顶部导航栏返回、左 / 右滑手势、安卓物理返回键和调用 navigateBack 接口)

目录 理解page-container的原理 设置禁止点击遮盖层关闭&#xff1f; 阻止左滑返回 理解page-container的原理 page-container组件的所有属性&#xff0c;最重要的是show值。在页面上引入这个组件后&#xff0c;若show值为true&#xff0c;页面上所有各种方式触发的返回操作…...

Linux基本指令篇 —— mv指令

在Windows中我们经常使用CtrlX和CtrlV将一个地方的文件或目录移动到另一个地方&#xff0c;我们若是要在Linux当中完成此操作&#xff0c;则需要使用mv指令。mv 是 Linux 系统中用于移动或重命名文件和目录的基本命令之一&#xff0c;是 "move" 的缩写。下面将详细介…...

基于STM32的智能节能风扇的设计

基于STM32的智能节能风扇的设计 内容:1.摘要 本设计旨在解决传统风扇能耗高、功能单一的问题&#xff0c;提出一种基于STM32的智能节能风扇。通过结合温度传感器、人体红外传感器等多种传感器&#xff0c;利用STM32微控制器实现对风扇的智能控制。经过实际测试&#xff0c;该智…...

HCIA-IP路由基础

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 ​ 本篇笔记是根据B站上的视频教程整理而成&#xff0c;感谢UP主的精彩讲解&#xff01;如果需要了解更多细节&#xff0c;可以参考以下视频&#xf…...

Linux 内存管理之page cache

文章目录 一、page cache1.1 File-backed pages和Anonymous pages1.2 page cache/slab cache1.3 读/写路径1.4 脏页回写1.5 drop_caches1.6 时间局部性与空间局部性1.7 Page Cache 的两种类型1.8 关键数据结构 二、Page Cache 的产生2.1 Buffered I/O&#xff08;标准 I/O&…...

uniApp实战四:网络请求封装

文章目录 1.最终效果预览2.请求封装3.创建config配置文件4.创建api请求5.页面调用 说明&#xff1a;当前笔记基于Vue3开发&#xff0c;HbuilderX版本4.66 1.最终效果预览 2.请求封装 在util/request.js下创建js文件&#xff0c;代码如下 import config from /configconst tim…...

sentinel 自定义 dashboard 用户名密码

默认情况下&#xff0c;sentinel dashboard 用户名密码为 sentinel / sentinel &#xff0c;这里我使用重写 镜像的方式&#xff1a; // 定义 Dockerfile $ cat Dockerfile # 基于现有 Sentinel Dashboard 镜像 FROM bladex/sentinel-dashboard:1.8.4# 重新定义 ENTRYPOINT&…...

Fisco Bcos学习 - 搭建星形拓扑组网

文章目录 一、前言二、环境准备与依赖安装2.1 系统要求2.2 依赖安装 三、星形拓扑设计与节点规划四、使用build_chain.sh构建星形拓扑4.1 创建操作目录并获取脚本4.2 生成星形拓扑配置文件4.3 执行构建命令4.4 查看生成的节点文件 五、启动节点与共识验证5.1 启动所有节点5.2 查…...

深度学习入门--(二)感知机

一.感知机是什么 简单的输入和输出&#xff0c;感觉&#xff08;输入&#xff09;&#xff0c;知道&#xff08;输出&#xff0c;作出反应&#xff09; 二.简单逻辑电路 2.1与门 import numpy as np #AND def AND(X1,X2):w1,w2,thera0.5,0.5,0.7tmpX1*w1X2*w2if tmp>the…...

LeetCode 3298.统计重新排列后包含另一个字符串的子字符串数目2

给你两个字符串 word1 和 word2 。 如果一个字符串 x 重新排列后&#xff0c;word2 是重排字符串的 前缀 &#xff0c;那么我们称字符串 x 是 合法的 。 请你返回 word1 中 合法 子字符串 的数目。 注意 &#xff0c;这个问题中的内存限制比其他题目要 小 &#xff0c;所以你…...

【nRF52832】【环境搭建 1】【ubuntu下搭建nRF52832开发环境】

本文讲述如何在 ubuntu 22.04 下开发 nRF52832. host 环境说明: $ uname -a Linux leo 6.8.0-60-generic #63~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 22 19:00:15 UTC 2 x86_64 x86_64 x86_64 GNU/Linux1. 安装软件 sudo apt install gcc-arm-none-eabisudo apt-get i…...

Django

1. Django 和 Tornado 的关系 Django 是一个高级 Python Web 框架&#xff0c;它鼓励快速开发和干净、实用的设计。Django 遵循 MVC&#xff08;模型-视图-控制器&#xff09;设计模式的一个变种&#xff0c;称为 MTV&#xff08;模型-模板-视图&#xff09;。Django 框架提供…...

51c嵌入式~CAN~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/14016935 一、CAN总线常见信号干扰问题 定位干扰原因 当总线有干扰时&#xff0c;有经验的工程师能够迅速定位&#xff0c;但是对于新手来说却很麻烦。 造成总线干扰的原因有很多&#xff0c;比如通过电磁辐射耦合到通…...

【iOS】iOS崩溃总结

【iOS】iOS崩溃总结 一、前言 之前写了一篇博文《【Flutter】程序报错导致的灰屏总结》&#xff0c;浏览量、收藏率和点赞量还挺高&#xff0c;还被收录了&#xff0c;就想着总结一下iOS崩溃&#xff0c;这个也是在iOS面试中经常被问到的。 在 iOS 开发过程中&#xff0c;导致…...

npm 报错:“无法加载文件 ...npm.ps1,因为在此系统上禁止运行脚本” 解决方案(附执行策略说明)

在使用 npm 命令时&#xff0c;部分 Windows 用户可能会遇到如下错误&#xff1a; npm : 无法加载文件 D:\nvm4w\nodejs\npm.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https://go.microsoft.com/fwlink/?LinkID135170 中的 about_Executi…...

AES加密:为你的PDF文档加上一道钢铁防线

在数字化时代&#xff0c;确保敏感数据的安全性至关重要。加密技术在保护信息免受未经授权访问方面起着关键作用。而在众多加密标准中&#xff0c;AES&#xff08;高级加密标准&#xff09;因其强大的安全性和广泛的应用而脱颖而出。那么&#xff0c;AES加密如何应用到PDF文档中…...

2025学年湖北省职业院校技能大赛 “信息安全管理与评估”赛项 样题卷(一)

2025学年湖北省职业院校技能大赛 “信息安全管理与评估”赛项 样题卷&#xff08;一&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书DCRS:DCFW:DCWS:WAF: 第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务 1&#xff1a;应急响…...

1688商品发布API:自动化上架与信息同步

一、1688商品发布API的核心功能与技术架构 1.1 API功能全景 1688商品发布API是1688开放平台的核心组件之一&#xff0c;支持商品信息的自动化发布、编辑、上下架及库存同步。其核心功能包括&#xff1a; 商品信息管理&#xff1a;支持商品标题、描述、价格、库存、SKU&#…...

鸿蒙ArkUI---基础组件Tabs(Tabbar)

基础页面组件 Tabs 作用&#xff1a; 快速创建Tabbar 个人理解&#xff1a; 快速的创建Tabar。 效果图&#xff1a; 代码&#xff1a;interface TabItem {icon: Resource; //未选中activeIcon: Resource; //选中name: string; //文字 }Entry Component struct Index {// st…...