Skip to content

Flutter - 包(Package)简介

包(Packages)是 Dart 和 Flutter 中代码共享和组织的基础。它们允许开发者捆绑可重用的库(libraries)、模块(modules)或插件(plugins),可以轻松地集成到多个项目中。

一个 Dart 包本质上是一个目录,包含 Dart 代码和一个关键的 pubspec.yaml 文件,该文件定义了其元数据(名称、版本、依赖项 dependencies 等)。它类似于一个 Dart 应用,但通常没有 main() 入口点,除非它被设计为可执行文件。

标准的包结构约定:

  • lib/: 包含包的公共代码。lib/ 内部的代码可以被其他包/应用导入。
  • lib/src/: 包含私有的实现细节。lib/src/ 内部的代码通常不应由使用者直接导入;相反,应从 lib/ 内部的某个文件导出。
  • lib/<package_name>.dart: 通常用作包的主要入口文件,导出主要的公共 API。
  • pubspec.yaml: 定义包的元数据文件。
  • README.md: 解释包功能和使用方法的文档。
  • CHANGELOG.md: 记录包版本之间变化的文件。
  • LICENSE: 指定软件许可。
  • example/: (可选但推荐)包含一个小型示例应用,演示如何使用该包。

要使用名为 my_package 的包中的代码:

// 导入主库文件
import 'package:my_package/my_package.dart';
// 或者导入 lib/ 中的特定文件
import 'package:my_package/utils/helpers.dart';

根据其依赖项(dependencies)和用途,包可以大致分为以下几类:

仅包含纯 Dart 代码,不依赖于 Flutter。它们可以用在任何 Dart 项目中(Flutter 应用、命令行工具 command-line tools、服务器端应用 server-side applications)。示例:http、intl、collection。

依赖于 Flutter 框架(在 pubspec.yaml 中标记 sdk: flutter),包含专门用于 Flutter 应用的 Dart 代码,通常提供自定义 widget 或 Flutter 特定的工具类。它们不包含平台特定(Android/iOS)的原生代码。

一种特殊的 Flutter 包类型,既包含 Dart 代码,也包含平台特定的原生代码(Android 使用 Kotlin/Java,iOS 使用 Swift/Objective-C)。插件用于访问仅通过 Dart 无法获得的功能(例如,相机 camera、GPS、设备传感器 device sensors、原生 SDK)。它们内部使用平台通道(Platform Channels)。示例:url_launcher、camera、shared_preferences、google_maps_flutter。

将第三方包集成到你的 Flutter 项目中非常简单:

  1. 查找包: 在官方仓库 pub.dev 上发现包:https://pub.dev/。
  2. 添加依赖项: 打开你的项目中的 pubspec.yaml 文件,并在 dependencies: 部分下添加包,指定包名称和所需的版本约束(例如,^1.2.3 允许版本 >= 1.2.3 且 < 2.0.0)。
dependencies:
flutter:
sdk: flutter
# 在此处添加包
http: ^1.1.0
provider: ^6.1.1
intl: ^0.18.1
  1. 安装包: 在你的终端中从项目根目录运行 flutter pub get,或使用 IDE(Android Studio/VS Code 通常在保存 pubspec.yaml 后自动提示)提供的“Pub get”操作。这将下载并链接该包。
  2. 导入和使用: 在你的代码中导入包中必要的 Dart 文件,并开始使用其类和函数。
import 'package:http/http.dart' as http; // 使用 'as' 创建命名空间
import 'package:provider/provider.dart';
void main() {
// 使用包中的函数/类
http.get(...);
ChangeNotifierProvider(...);
}

让我们将平台通道示例中的 my_browser 实现创建为一个可重用的 Flutter 插件。

  1. 创建插件项目: 使用 IDE 或命令行:
    • IDE (Android Studio/VS Code): File(文件)> New(新建)> New Flutter Project...(新建 Flutter 项目…)> 选择 Flutter Plugin(Flutter 插件)。填写名称(my_browser)、组织(organization)和所需平台。
    • 命令行: 运行 flutter create --template=plugin --platforms=android,ios -a kotlin -i swift my_browser(标志指定平台和首选原生语言)。
  2. 实现 Dart API(lib/my_browser.dart): 定义插件的公共 Dart 接口。
import 'dart:async';
import 'package:flutter/services.dart';
class MyBrowser {
// 使用插件名称作为通道名称
static const MethodChannel _channel = MethodChannel('my_browser');
// 可选:模板中通常包含的平台版本示例方法
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
// 定义打开浏览器的方法
Future<bool> openBrowser(String urlString) async {
try {
final bool? result = await _channel.invokeMethod('openBrowser', {
'url': urlString
});
// 返回原生代码的成功状态,错误/null 时默认为 false
return result ?? false;
} on PlatformException catch (e) {
print('Failed to open browser via plugin: ${e.message}');
return false;
} catch (e) {
print('An unexpected error occurred in openBrowser: $e');
return false;
}
}
}
  1. 实现 Android 原生代码(android/src/main/kotlin/.../MyBrowserPlugin.kt): 实现平台通道处理程序(platform channel handler)。
package com.example.my_browser // 使用你的插件包名
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.util.Log
/** MyBrowserPlugin */
class MyBrowserPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
/// 持有 Flutter 与原生 Android 之间通信的 MethodChannel
private lateinit var channel : MethodChannel
private var activity: Activity? = null // 持有 activity 引用
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
// 使用 binding 中的 binaryMessenger
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "my_browser")
channel.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
// MethodCallHandler 实现
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else if (call.method == "openBrowser") {
val url = call.argument<String>("url")
if (url != null && activity != null) {
val success = openBrowserInActivity(url)
result.success(success)
} else if (activity == null) {
result.error("NO_ACTIVITY", "Cannot open browser without an activity.", null)
} else {
result.error("INVALID_ARGUMENT", "URL argument is missing or invalid.", null)
}
} else {
result.notImplemented()
}
}
private fun openBrowserInActivity(url: String): Boolean {
return try {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
activity?.startActivity(intent)
true // 表示成功
} catch (e: Exception) {
Log.e("MyBrowserPlugin", "Error opening browser", e)
false // 表示失败
}
}
// ActivityAware 实现 (获取 context/activity)
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
activity = null
}
}
  1. 实现 iOS 原生代码(ios/Classes/SwiftMyBrowserPlugin.swift):
import Flutter
import UIKit
public class SwiftMyBrowserPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
// 使用 registrar.messenger()
let channel = FlutterMethodChannel(name: "my_browser", binaryMessenger: registrar.messenger())
let instance = SwiftMyBrowserPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "getPlatformVersion" {
result("iOS " + UIDevice.current.systemVersion)
} else if call.method == "openBrowser" {
if let args = call.arguments as? [String: Any],
let urlString = args["url"] as? String {
openBrowser(urlString: urlString, result: result)
} else {
result(FlutterError(code: "INVALID_ARGUMENT",
message: "URL argument is missing or not a string",
details: nil))
}
} else {
result(FlutterMethodNotImplemented)
}
}
private func openBrowser(urlString: String, result: @escaping FlutterResult) {
guard let url = URL(string: urlString) else {
result(FlutterError(code: "INVALID_URL",
message: "Could not parse URL: \(urlString)",
details: nil))
return
}
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:]) { (success) in
result(success)
}
} else {
result(FlutterError(code: "CANNOT_OPEN_URL",
message: "Cannot open URL: \(urlString)",
details: nil))
}
}
}
  1. 测试插件: 生成的插件项目包含一个 example/ 目录。修改 example/lib/main.dart 来导入并使用你的插件。
// 在 example/lib/main.dart 中
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:my_browser/my_browser.dart'; // 导入插件
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
final _myBrowserPlugin = MyBrowser(); // 创建实例
bool _browserOpened = false;
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion = await MyBrowser.platformVersion ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// 检查 widget 是否仍在树中
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
Future<void> _callOpenBrowser() async {
bool success = await _myBrowserPlugin.openBrowser('https://flutter.dev');
if (mounted) { // Check if widget is still in the tree
setState(() { _browserOpened = success; });
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(success ? 'Browser open attempt successful.' : 'Browser open attempt failed.')),
);
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin Example App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Running on: $_platformVersion\n'),
ElevatedButton(
onPressed: _callOpenBrowser,
child: const Text('Open Browser (flutter.dev)'),
),
if (_browserOpened) const Text('Browser opened! (Check device)'),
],
)
),
),
);
}
}
  1. 在 Android 和 iOS 模拟器/设备上运行示例应用(cd example,flutter run)以验证功能。
  2. 发布(可选): 测试通过后,你可以将你的插件发布到 pub.dev,供他人使用。

开发插件可以通过连接 Dart 和强大的原生平台 API 来扩展 Flutter 的能力。