跳到主内容

开发包和插件

如何为 Flutter 编写包和插件。

包介绍

#

包能够创建可轻松共享的模块化代码。一个最小的包包含以下内容

pubspec.yaml

一个元数据文件,声明包名称、版本、作者等。

lib

lib 目录包含包中的公共代码,最少包含一个 <package-name>.dart 文件。

包类型

#

包可以包含多种类型的内容

Dart 包

用 Dart 编写的通用包,例如 path 包。其中一些可能包含 Flutter 特定的功能,因此对 Flutter 框架有依赖关系,从而将其使用限制为 Flutter,例如 fluro 包。

插件包

一种特殊的 Dart 包,包含用 Dart 代码编写的 API,以及一个或多个特定于平台的实现。

插件包可以为 Android(使用 Kotlin 或 Java)、iOS(使用 Swift 或 Objective-C)、Web、macOS、Windows 或 Linux,或它们的任意组合编写。

一个具体的例子是 url_launcher 插件包。要了解如何使用 url_launcher 包,以及它是如何扩展以实现对 Web 的支持,请参阅 Harry Terkelsen 在 Medium 上的文章 如何编写 Flutter Web 插件,第一部分

FFI 包

一种特殊的 Dart 包,它使用 dart:ffi 调用原生代码。这些包在 Dart 单独运行,不需要特定于操作系统的构建文件。它们使用 flutter create --template=package_ffi 命令创建(请参阅 创建 FFI 包)。自 Flutter 3.38 以来,这是构建和捆绑原生代码的推荐方法。

开发 Dart 包

#

以下说明介绍了如何编写 Flutter 包。

步骤 1:创建包

#

要创建一个启动的 Flutter 包,请使用 --template=package 标志与 flutter create 一起使用

flutter create --template=package hello

这将在 hello 文件夹中创建一个包项目,内容如下

LICENSE

一个(大部分)空的许可证文本文件。

test/hello_test.dart

包的 单元测试

hello.iml

一个由 IntelliJ IDE 使用的配置文件。

.gitignore

一个隐藏文件,告诉 Git 在项目中忽略哪些文件或文件夹。

.metadata

一个隐藏文件,由 IDE 用于跟踪 Flutter 项目的属性。

pubspec.yaml

一个 YAML 文件,包含元数据,指定包的依赖项。由 pub 工具使用。

README.md

一个启动的 Markdown 文件,简要描述包的目的。

lib/hello.dart

一个启动应用程序,包含包的 Dart 代码。

.idea/modules.xml, .idea/workspace.xml

一个隐藏文件夹,包含 IntelliJ IDE 的配置文件。

CHANGELOG.md

一个(大部分)空的 Markdown 文件,用于跟踪包的版本更改。

步骤 2:实现包

#

对于纯 Dart 包,只需将功能添加到主 lib/<package name>.dart 文件中,或添加到 lib 目录中的多个文件中即可。

要测试包,请在 test 目录中添加 单元测试

有关如何组织包内容的更多详细信息,请参阅 Dart 库包 文档。

开发插件包

#

如果您想开发一个调用特定于平台的 API 的包,则需要开发一个插件包。

API 通过 平台通道 连接到特定于平台的实现。

联合插件

#

联合插件 是一种将插件的 API 分割为平台接口、该接口的独立平台实现以及面向应用程序的接口的方法,该接口使用正在运行平台的已注册实现。

包分离的联合插件 是将平台接口、平台实现和面向应用程序的接口都分离到各自的 Dart 包中的联合插件。

因此,一个包分离的联合插件可以使用一个包用于 iOS,另一个用于 Android,另一个用于 Web,还有一个用于汽车(作为物联网设备的示例)。在其他好处中,这种方法允许领域专家扩展现有插件以适用于他们最了解的平台。

联合插件需要以下内容

面向应用程序的接口

插件用户在使用插件时与之交互的接口。此接口指定 Flutter 应用程序使用的 API。在包分离的联合插件中,这是插件用户依赖于使用插件的包。

平台实现

一个或多个包含特定于平台的实现代码的实现。面向应用程序的接口调用这些实现——它们除非包含可供最终用户访问的特定于平台的的功能,否则不会直接使用或依赖于包分离的实现。

平台接口

将面向应用程序的接口与平台实现粘合在一起的接口。它声明了一个任何平台实现都必须实现才能支持面向应用程序的接口的接口。拥有一个单独的包来定义此接口可确保所有平台包以统一的方式实现相同的功能。

认可的联合插件

#

理想情况下,在将平台实现添加到打包分离的联合插件时,您将与包作者协调,以包含您的实现。这样,原始作者认可您的实现。

例如,假设您为(虚构的)foobar 插件编写了 foobar_windows 实现。在认可的插件中,原始 foobar 作者将您的 Windows 实现作为依赖项添加到面向应用程序的包的 pubspec 中。然后,当开发人员将 foobar 插件包含在其 Flutter 应用程序中时,Windows 实现以及其他认可的实现将自动提供给应用程序。

未认可的联合插件

#

如果您由于任何原因无法获得原始插件作者的认可,则您的插件获得认可。开发人员仍然可以使用您的实现,但必须手动将其添加到应用程序的 pubspec.yaml 文件

yaml
dependencies:
  foobar: ^1.0.0
  foobar_windows: ^1.0.0 # Non-endorsed plugin implementation

这种方法也适用于覆盖已经认可的 foobar 插件实现。

有关联合插件的更多信息,为什么它们很有用以及如何实现它们,请参阅 Harry Terkelsen 在 Medium 上的文章 如何编写 Flutter Web 插件,第二部分

指定插件支持的平台

#

插件可以通过在 pubspec.yaml 文件中的 platforms 映射中添加键来指定它们支持的平台。例如,以下 pubspec 文件显示了 hello 插件的 flutter: 映射,它仅支持 iOS 和 Android

yaml
flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin

在添加更多平台的插件实现时,应相应地更新 platforms 映射。例如,这是在 pubspec 文件中更新 hello 插件的映射,以添加对 macOS 和 Web 的支持

yaml
flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin
      macos:
        pluginClass: HelloPlugin
      web:
        pluginClass: HelloPlugin
        fileName: hello_web.dart

联合平台包

#

平台包使用相同的格式,但包含一个 implements 条目,指示它实现哪个面向应用程序的包。例如,包含 hello 的 Windows 实现的 hello_windows 插件将具有以下 flutter: 映射

yaml
flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        pluginClass: HelloPlugin

认可的实现

#

面向应用程序的包可以通过添加对其的依赖项,并在 platforms: 映射中将其包含为 default_package 来认可平台包。如果上面的 hello 插件认可 hello_windows,则如下所示

yaml
flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin
      windows:
        default_package: hello_windows

dependencies:
  hello_windows: ^1.0.0

请注意,如这里所示,面向应用程序的包可以在包内实现一些平台,并在认可的联合实现中实现其他平台。

共享 iOS 和 macOS 实现

#

许多框架都支持 iOS 和 macOS,并且具有相同或几乎相同的 API,因此可以对这两个平台使用相同的代码库来实现某些插件。通常,每个平台的实现都在自己的文件夹中,但 sharedDarwinSource 选项允许 iOS 和 macOS 使用相同的文件夹。

yaml
flutter:
  plugin:
    platforms:
      ios:
        pluginClass: HelloPlugin
        sharedDarwinSource: true
      macos:
        pluginClass: HelloPlugin
        sharedDarwinSource: true

environment:
  sdk: ^3.0.0
  # Flutter versions prior to 3.7 did not support the
  # sharedDarwinSource option.
  flutter: ">=3.7.0"

启用 sharedDarwinSource 时,不再使用 iOS 的 ios 目录和 macOS 的 macos 目录,而是两个平台都使用共享的 darwin 目录来存储所有代码和资源。启用此选项时,需要将任何现有文件从 iosmacos 移动到共享目录。还需要更新 podspec 文件以设置两个平台的依赖项和部署目标,例如

ruby
  s.ios.dependency 'Flutter'
  s.osx.dependency 'FlutterMacOS'
  s.ios.deployment_target = '13.0'
  s.osx.deployment_target = '10.15'

步骤 1:创建包

#

要创建一个插件包,请使用 --template=plugin 标志与 flutter create 一起使用。

使用 --platforms= 选项,后跟逗号分隔的列表,以指定插件支持的平台。可用平台是:androidiosweblinuxmacoswindows。如果未指定任何平台,则生成的项目不支持任何平台。

使用 --org 选项指定您的组织,使用反向域名表示法。此值用于生成的插件代码中的各种包和捆绑标识符。

默认情况下,插件项目使用 Swift 进行 iOS 代码,使用 Kotlin 进行 Android 代码。如果您更喜欢 Objective-C 或 Java,可以使用 -i 指定 iOS 语言,使用 -a 指定 Android 语言。请选择 其中一个

flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -a kotlin hello
flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -a java hello
flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -i objc hello
flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -i swift hello

这将在 hello 文件夹中创建一个插件项目,内容如下

lib/hello.dart

插件的 Dart API。

android/src/main/java/com/example/hello/HelloPlugin.kt

Kotlin 中插件的 Android 平台特定实现。

ios/Classes/HelloPlugin.m

Objective-C 中插件的 iOS 平台特定实现。

example/

一个依赖于插件的 Flutter 应用程序,并说明了如何使用它。

步骤 2:实现包

#

由于插件包包含用多种编程语言编写的多个平台的代码,因此需要采取一些具体步骤来确保流畅的体验。

步骤 2a:定义包 API(.dart)

#

插件包的 API 在 Dart 代码中定义。在您最喜欢的 Flutter 编辑器 中打开 hello/ 文件夹。找到文件 lib/hello.dart

步骤 2b:添加 Android 平台代码(.kt/.java)

#

我们建议您使用 Android Studio 编辑 Android 代码。

在 Android Studio 中编辑 Android 平台代码之前,请确保代码至少已构建一次(换句话说,从您的 IDE/编辑器运行示例应用程序,或在终端中执行 cd hello/example; flutter build apk --config-only)。

然后执行以下步骤

  1. 启动 Android Studio。
  2. 欢迎使用 Android Studio 对话框中选择 打开现有 Android Studio 项目,或从菜单中选择 文件 > 打开,然后选择 hello/example/android/build.gradlehello/example/android/build.gradle.kts 文件。
  3. Gradle 同步 对话框中,选择 确定
  4. Android Gradle Plugin 更新 对话框中,选择 不再为此项目提醒我

您的插件的 Android 平台代码位于 hello/java/com.example.hello/HelloPlugin

您可以按运行 (▶) 按钮从 Android Studio 运行示例应用。

步骤 2c:添加 iOS 平台代码 (.swift/.h+.m)

#

我们建议您使用 Xcode 编辑 iOS 代码。

在 Xcode 中编辑 iOS 平台代码之前,请确保代码已至少构建一次(换句话说,从您的 IDE/编辑器运行示例应用,或在终端中执行 cd hello/example; flutter build ios --no-codesign --config-only)。

然后执行以下步骤

  1. 启动 Xcode。
  2. 选择 文件 > 打开,并选择 hello/example/ios/Runner.xcworkspace 文件。

您的插件的 iOS 平台代码位于项目导航器中的 Pods/Development Pods/hello/../../example/ios/.symlinks/plugins/hello/ios/Classes 中。(如果您使用的是 sharedDarwinSource,则路径将以 hello/darwin/Classes 结尾。)

您可以按运行 (▶) 按钮运行示例应用。

添加 CocoaPod 依赖项
#

使用以下说明添加版本为 0.0.1HelloPod

  1. ios/hello.podspec 的末尾指定依赖项

    ruby
    s.dependency 'HelloPod', '0.0.1'
    

    对于私有 Pod,请参阅 Private CocoaPods 以确保仓库访问权限

    ruby
    s.source = {
        # For pods hosted on GitHub
        :git => "https://github.com/path/to/HelloPod.git",
        # Alternatively, for pods hosted locally
        # :path => "file:///path/to/private/repo",
        :tag => s.version.to_s
      }`
    
  1. 安装插件

    • 将插件添加到项目 pubspec.yaml 依赖项中。
    • 运行 flutter pub get
    • 在项目的 ios/ 目录中,运行 pod install

Pod 应该出现在安装摘要中。

如果您的插件需要隐私清单,例如,如果它使用任何 必需理由 API,请更新 PrivacyInfo.xcprivacy 文件以描述您的插件的隐私影响,并将以下内容添加到 podspec 文件的底部

ruby
s.resource_bundles = {'your_plugin_privacy' => ['your_plugin/Sources/your_plugin/Resources/PrivacyInfo.xcprivacy']}

有关更多信息,请查看 Apple 开发者网站上的 Privacy manifest files

步骤 2d:添加 Linux 平台代码 (.h+.cc)

#

我们建议您使用具有 C++ 集成的 IDE 编辑 Linux 代码。以下说明适用于安装了“C/C++”和“CMake”扩展的 Visual Studio Code,但可以针对其他 IDE 进行调整。

在 IDE 中编辑 Linux 平台代码之前,请确保代码已至少构建一次(换句话说,从您的 Flutter IDE/编辑器运行示例应用,或在终端中执行 cd hello/example; flutter build linux)。

然后执行以下步骤

  1. 启动 Visual Studio Code。
  2. 打开 hello/example/linux/ 目录。
  3. 在提示中选择 您是否要配置项目“linux”?。这将允许 C++ 自动完成功能正常工作。

您的插件的 Linux 平台代码位于 flutter/ephemeral/.plugin_symlinks/hello/linux/ 中。

您可以使用 flutter run 运行示例应用。注意:在 Linux 上创建可运行的 Flutter 应用程序需要 flutter 工具中的步骤,因此即使您的编辑器提供 CMake 集成,以这种方式构建和运行也不会正确工作。

步骤 2e:添加 macOS 平台代码 (.swift)

#

我们建议您使用 Xcode 编辑 macOS 代码。

在 Xcode 中编辑 macOS 平台代码之前,请确保代码已至少构建一次(换句话说,从您的 IDE/编辑器运行示例应用,或在终端中执行 cd hello/example; flutter build macos --config-only)。

然后执行以下步骤

  1. 启动 Xcode。
  2. 选择 文件 > 打开,并选择 hello/example/macos/Runner.xcworkspace 文件。

您的插件的 macOS 平台代码位于项目导航器中的 Pods/Development Pods/hello/../../example/macos/Flutter/ephemeral/.symlinks/plugins/hello/macos/Classes 中。(如果您使用的是 sharedDarwinSource,则路径将以 hello/darwin/Classes 结尾。)

您可以按运行 (▶) 按钮运行示例应用。

步骤 2f:添加 Windows 平台代码 (.h+.cpp)

#

我们建议您使用 Visual Studio 编辑 Windows 代码。

在 Visual Studio 中编辑 Windows 平台代码之前,请确保代码已至少构建一次(换句话说,从您的 IDE/编辑器运行示例应用,或在终端中执行 cd hello/example; flutter build windows)。

然后执行以下步骤

  1. 启动 Visual Studio。
  2. 选择 打开项目或解决方案,并选择 hello/example/build/windows/hello_example.sln 文件。

您的插件的 Windows 平台代码位于解决方案资源管理器中的 hello_plugin/Source Fileshello_plugin/Header Files 中。

您可以通过右键单击解决方案资源管理器中的 hello_example 并选择 设置为启动项目,然后按运行 (▶) 按钮来运行示例应用。重要提示:在更改插件代码后,必须选择 构建 > 构建解决方案 才能再次运行,否则将运行过时的构建插件副本,而不是包含您所做更改的最新版本。

步骤 2g:连接 API 和平台代码

#

最后,您需要将 Dart 代码中编写的 API 与特定于平台的实现连接起来。这通过 平台通道 或通过平台接口包中定义的接口来完成。

为现有插件项目添加平台支持

#

要将对现有插件项目的特定平台的支持添加到项目中,请在项目目录中再次使用带有 --template=plugin 标志的 flutter create 命令。例如,要在现有插件中添加 Web 支持,请运行

flutter create --template=plugin --platforms=web .

如果此命令显示有关更新 pubspec.yaml 文件的消息,请按照提供的说明进行操作。

Dart 平台实现

#

在许多情况下,非 Web 平台实现仅使用特定于平台的实现语言,如上所示。但是,平台实现也可以使用特定于平台的 Dart。

仅 Dart 平台实现

#

在某些情况下,某些平台完全可以用 Dart 实现(例如,使用 FFI)。对于非 Web 平台的仅 Dart 平台实现,请将 pubspec.yaml 中的 pluginClass 替换为 dartPluginClass。以下是修改为仅 Dart 实现的 hello_windows 示例

yaml
flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        dartPluginClass: HelloPluginWindows

在此版本中,您将没有 C++ Windows 代码,而是会使用包含静态 registerWith() 方法的 HelloPluginWindows 类来子类化 hello 插件的 Dart 平台接口类。此方法在启动期间调用,可用于注册 Dart 实现

dart
class HelloPluginWindows extends HelloPluginPlatform {
  /// Registers this class as the default instance of [HelloPluginPlatform].
  static void registerWith() {
    HelloPluginPlatform.instance = HelloPluginWindows();
  }

混合平台实现

#

平台实现也可以同时使用 Dart 和特定于平台的语言。例如,插件可以使用不同的平台通道为每个平台定制通道。

混合实现使用上述两种注册系统。以下是修改为混合实现的 hello_windows 示例

yaml
flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        dartPluginClass: HelloPluginWindows
        pluginClass: HelloPlugin

Dart HelloPluginWindows 类将使用上述仅 Dart 实现的 registerWith(),而 C++ HelloPlugin 类将与 C++ 仅实现相同。

测试你的插件

#

我们鼓励您使用自动化测试来测试您的插件,以确保在您更改代码时功能不会退化。

要了解有关测试您的插件的更多信息,请查看 Testing plugins。如果您正在编写应用程序的测试,并且插件导致崩溃,请查看 Flutter in plugin tests

开发 FFI 包

#

如果您想开发一个使用 Dart 的 FFI 调用本机 API 的包,则需要开发一个 FFI 包。

步骤 1:创建包

#

要创建一个启动 FFI 包,请使用带有 flutter create--template=package_ffi 标志

flutter create --template=package_ffi hello

这将在 hello 文件夹中创建一个 FFI 包项目,其中包含以下专业内容

lib:定义包 API 并使用 dart:ffi 调用本机代码的 Dart 代码。

src:本机源代码。

hook/build.dart:编译本机代码的构建钩子脚本。

步骤 2:绑定到原生代码

#

要使用本机代码,需要 Dart 中的绑定。

为了避免手动编写这些绑定,它们是由 package:ffigen 从头文件 (src/hello.h) 生成的。请参阅 ffigen 文档 以获取有关如何安装此包的信息。

要重新生成绑定,请运行以下命令

dart run tool/ffigen.dart

步骤 3:调用原生代码

#

非常短的本机函数可以直接从任何隔离体调用。例如,请参阅 lib/hello.dart 中的 sum

较长的函数应在 辅助隔离体 上调用,以避免在 Flutter 应用程序中丢帧。例如,请参阅 lib/hello.dart 中的 sumAsync

添加文档

#

建议在所有包中添加以下文档

  1. 一个介绍该包的 README.md 文件
  2. 一个记录每个版本中更改的 CHANGELOG.md 文件
  3. 一个 LICENSE 文件,其中包含授权该包的条款
  4. 所有公共 API 的 API 文档(有关详细信息,请参见下文)

API 文档

#

当您发布一个包时,API 文档会自动生成并在 pub.dev/documentation 上发布。例如,请参阅 device_info_plus 的文档。

如果您希望在开发机器上本地生成 API 文档,请使用以下命令

  1. 更改到您的包的位置

    cd ~/dev/mypackage
    
  2. 告诉文档工具 Flutter SDK 的位置(更改以下命令以反映您放置的位置)

       export FLUTTER_ROOT=~/dev/flutter  # on macOS or Linux
    
       set FLUTTER_ROOT=~/dev/flutter     # on Windows
    
  3. 运行包含在 Flutter SDK 中的 dart doc 工具,如下所示
       $FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart doc   # on macOS or Linux
    
       %FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart doc  # on Windows
    

有关如何编写 API 文档的技巧,请参阅 Effective Dart Documentation

将许可证添加到 LICENSE 文件

#

每个 LICENSE 文件中的单独许可证应由单独行上的 80 个连字符分隔。

如果 LICENSE 文件包含多个组件许可证,则每个组件许可证必须以应用组件许可证的包名称开头,每个包名称单独成行,并且包名称列表与实际许可证文本之间留有空行。(包不必与 pub 包的名称匹配。例如,一个包本身可能包含来自多个第三方来源的代码,并且可能需要包含每个来源的许可证。)

以下示例显示了一个组织良好的许可证文件

package_1

<some license text>

--------------------------------------------------------------------------------
package_2

<some license text>

这是另一个组织良好的许可证文件的示例

package_1

<some license text>

--------------------------------------------------------------------------------
package_1
package_2

<some license text>

这是一个组织不良好的许可证文件的示例

<some license text>

--------------------------------------------------------------------------------
<some license text>

这是另一个组织不良好的许可证文件的示例

package_1

<some license text>
--------------------------------------------------------------------------------
<some license text>

发布你的包

#

一旦你实现了某个包,就可以将其发布到 pub.dev,以便其他开发者可以轻松使用它。

在发布之前,请务必检查 pubspec.yamlREADME.mdCHANGELOG.md 文件,以确保其内容完整且正确。此外,为了提高你的包的质量和可用性(并使其更有可能获得 Flutter Favorite 的地位),请考虑包含以下项目

  • 多样的代码使用示例
  • 屏幕截图、动画 GIF 或视频
  • 指向相应代码仓库的链接

接下来,以 dry-run 模式运行 publish 命令,以查看所有内容是否通过分析

flutter pub publish --dry-run

下一步是发布到 pub.dev,但请确保你已准备好,因为 发布是永久性的

flutter pub publish

有关发布的更多详细信息,请参阅 dart.dev 上的 发布文档

处理包之间的依赖关系

#

如果你正在开发一个名为 hello 的包,该包依赖于另一个包暴露的 Dart API,则需要将该包添加到 pubspec.yaml 文件的 dependencies 部分。下面的代码使 url_launcher 插件的 Dart API 可供 hello 使用

yaml
dependencies:
  url_launcher: ^6.3.2

现在你可以在 hello 的 Dart 代码中 import 'package:url_launcher/url_launcher.dart'launch(someUrl)

这与你在 Flutter 应用程序或任何其他 Dart 项目中包含包的方式没有区别。

但是,如果 hello 恰好是一个插件包,其平台特定代码需要访问 url_launcher 暴露的平台特定 API,则还需要将合适的依赖声明添加到你的平台特定构建文件中,如下所示。

Android

#

以下示例在 hello/android/build.gradle 中设置了 url_launcher 的依赖项

groovy
android {
    // lines skipped
    dependencies {
        compileOnly rootProject.findProject(":url_launcher")
    }
}

现在你可以在 hello/android/src 中的源代码中 import io.flutter.plugins.urllauncher.UrlLauncherPlugin 并访问 UrlLauncherPlugin 类。

有关 build.gradle 文件的更多信息,请参阅 Gradle 文档 中有关构建脚本的内容。

iOS

#

以下示例在 hello/ios/hello.podspec 中设置了 url_launcher 的依赖项

ruby
Pod::Spec.new do |s|
  # lines skipped
  s.dependency 'url_launcher'

现在你可以在 hello/ios/Classes 中的源代码中 #import "UrlLauncherPlugin.h" 并访问 UrlLauncherPlugin 类。

有关 .podspec 文件的更多详细信息,请参阅 CocoaPods 文档

Web

#

所有 Web 依赖项都由 pubspec.yaml 文件处理,就像任何其他 Dart 包一样。