前言
在flutter
中有包和插件两个概念,插件 (plugin) 是 package
的一种,全称是 plugin package
,我们简称为 plugin
,中文叫插件。包(Package
)主要指对flutter
相关功能的封装,类似于Android
中的插件和iOS
中的三方库。而插件(Plugin
)主要指通过插件调用原生的功能,如获取手机基本信息、获取原生的相机等。两者还是存在一定的差别的,Package
一般只包含Dart
代码,而插件除了包含有Dart
外,还会包含有原生的语言,比如安卓中的Java
或Kotlin
,和iOS
中的Objective-C
或Swift
。Package
和Plugin
都是为了封装一些基础组件、业务组件等,实现组件化开发,这样项目中多处用到可以快速引入实现功能。
- Packages
Dart package
最低要求是包含一个pubspec.yaml
文件,通常还包含一个lib
目录。pubspec.yaml
文件用于定义 package 名称、版本号、作者等其他信息的元数据文件;lib
目录包含共享代码,其中至少包含一个<package-name>.dart
文件。一个package
可以包含依赖关系 (在pubspec.yaml
文件里声明)、 Dart 库、应用、资源、字体、测试、图片和例子等。 pub.dev 上列出了很多 package,由 Google 工程师和 Flutter 和 Dart 社区的开发者开发和发布,你可以用在自己的应用里。- Plugins
插件 (plugin package
) 是一种特别的 package,特别指那些帮助你获得原生平台特性的 package。插件可以为 Android (使用 Kotlin 或 Java 语言)、 iOS (使用 Swift 或 Objective-C 语言)、Web、macOS、Windows、Linux 平台,或其任意组合的平台编写。比如:某个插件可以为 Flutter 应用提供使用原生平台的摄像头的功能。
下面一起来看下如何开发 Package 与 Plugin
一、 包(Package)
1.1 创建 Package
使用命令行创建:
flutter create --template=package hello
这将在 hello
目录下创建一个 package
项目,并且包含以下内容:
LICENSE 文件
大概率会是空的一个许可证文件。test/hello_test.dart 文件
Package 的 单元测试 文件。hello.iml 文件
由 IntelliJ 生成的配置文件。.gitignore 文件
告诉 Git 系统应该隐藏哪些文件或文件夹的一个隐藏文件。.metadata 文件
IDE 用来记录某个 Flutter 项目属性的的隐藏文件。pubspec.yaml 文件
pub 工具需要使用的,包含 package 依赖的 yaml 格式的文件。README.md 文件
起步文档,用于描述 package。lib/hello.dart 文件
package 的 Dart 实现代码。.idea/modules.xml、.idea/workspace.xml 文件
IntelliJ 的各自配置文件(包含在 .idea 隐藏文件夹下)。CHANGELOG.md 文件
又一个大概率为空的文档,用于记录 package 的版本变更。
推荐使用 Android Studio
进行创建,打开 Android Studio
选择 New Flutter Project
,首先需要选择该 package
使用的 flutter sdk
:
然后需要输入插件名、文件目录、插件类型(可以选择是 Package
还是 Plugin
),支持开发平台及对应的开发语言:
这里输入项目名字为 flutter_package_demo
,然后 Project type
选择 Package
,平台暂时只选择 Android
、iOS
,开发语言选择对应的 kotlin
、Swift
然后创建项目后,目录结构如下:
Package
项目的目录结构较为简单,主要是 lib
目录下的和项目同名的类flutter_package_demo.dart
,该类主要是用于在里面暴露出包中的类,以便其它项目调用。Package
中的代码实现放在lib
目录下即可,测试代码写在test
目录下的flutter_package_demo_test.dart
中即可。
因为 Package
是个单纯的 Dart
库,没有 Android
和 iOS
壳工程,所以不能直接运行该Package
进行联调测试,可以在自己的项目中通过本地依赖path
引入该 Package
,然后可以通过在项目中调用进行测试联调。
1.2 定义 Package
这里创建好项目后,模版代码在 flutter_package_demo.dart
中已经生成了一个方法:
library flutter_package_demo;/// A Calculator.
class Calculator {/// Returns [value] plus 1.int addOne(int value) => value + 1;
}
这里的 library flutter_package_demo
表明该 package
是一个可依赖包,包的名字为flutter_package_demo
1.3 验证 Package
由于这里不涉及页面 UI,该逻辑可以直接在单元测试中进行验证,代码写在test
目录下的flutter_package_demo_test.dart
中:
import 'package:flutter_test/flutter_test.dart';import 'package:flutter_package_demo/flutter_package_demo.dart';void main() {test('adds one to input values', () {final calculator = Calculator();expect(calculator.addOne(2), 3);expect(calculator.addOne(-7), -6);expect(calculator.addOne(0), 1);});
}
然后启动单元测试:
如果是涉及 UI 的 package,本地测试时,可以直接在当前项目下创建 example
目录,在里面开发功能验证代码,或者在外部使用的项目中通过如下方式在 pubspec.yaml
中引入包:
flutter_package_demo:path: ../ # package 所在的路径,绝对或者相对
1.4 包含 UI 的 Package
下面改造一下项目,让 package
提供一个对外可显示的 material dialog,
先来定义package
:
library flutter_package_demo;export 'caculator.dart';
export 'dialogs.dart';
包名不变,把逻辑代码移除,导出了两个文件,第一个为上述的加1方法的抽离,第二个为我们要显示的 dialog
:
import 'package:flutter/material.dart';import 'dialog_widget.dart';class Dialogs {static const TextStyle titleStyle =TextStyle(fontWeight: FontWeight.bold, fontSize: 16);static const Color bcgColor = Color(0xfffefefe);static const Widget holder = SizedBox(height: 0,);static const ShapeBorder dialogShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16)));static const ShapeBorder bottomSheetShape = RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(16), topRight: Radius.circular(16)));/// 屏幕中弹出对话框static Future<void> materialDialog({required BuildContext context,Function(dynamic value)? onClose,String? title,String? msg,List<Widget>? actions,Widget customView = holder,bool barrierDismissible = true,Color? barrierColor = Colors.black54,String? barrierLabel,bool useSafeArea = true,bool useRootNavigator = true,RouteSettings? routeSettings,ShapeBorder dialogShape = dialogShape,TextStyle titleStyle = titleStyle,TextStyle? msgStyle,TextAlign? titleAlign,TextAlign? msgAlign,Color color = bcgColor,double? dialogWidth,}) async {await showDialog(context: context,barrierDismissible: barrierDismissible,barrierColor: barrierColor,barrierLabel: barrierLabel,useSafeArea: useSafeArea,useRootNavigator: useRootNavigator,routeSettings: routeSettings,builder: (context) {return Dialog(backgroundColor: color,shape: dialogShape,child: DialogWidget(title: title,dialogWidth: dialogWidth,msg: msg,actions: actions,customView: customView,titleStyle: titleStyle,msgStyle: msgStyle,titleAlign: titleAlign,msgAlign: msgAlign,color: color,),);},).then((value) => onClose?.call(value));}/// 底部弹出对话框static void bottomMaterialDialog({required BuildContext context,Function(dynamic value)? onClose,String? title,String? msg,List<Widget>? actions,Widget customView = holder,bool barrierDismissible = true,ShapeBorder dialogShape = bottomSheetShape,TextStyle titleStyle = titleStyle,TextStyle? msgStyle,Color color = bcgColor,bool isScrollControlled = false,bool useRootNavigator = false,bool isDismissible = true,bool enableDrag = true,RouteSettings? routeSettings,AnimationController? transitionAnimationController,}) {showModalBottomSheet(context: context,shape: dialogShape,backgroundColor: color,isScrollControlled: isScrollControlled,useRootNavigator: useRootNavigator,isDismissible: isDismissible,enableDrag: enableDrag,routeSettings: routeSettings,transitionAnimationController: transitionAnimationController,builder: (context) => DialogWidget(title: title,msg: msg,actions: actions,customView: customView,titleStyle: titleStyle,msgStyle: msgStyle,color: color,),).then((value) => onClose?.call(value));}
}
接着增加 example
工程来验证我们的 dialog
,这里为了简单,以 Android
为例,就不添加iOS demo
工程了,项目结构如下:
在 example
工程的lib
目录下创建 main.dart
编写功能验证代码:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_package_demo/caculator.dart';
import 'package:flutter_package_demo/dialogs.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return MaterialApp(title: 'package 测试',theme: ThemeData(primarySwatch: Colors.blue,),home: SafeArea(child: Scaffold(backgroundColor: Colors.white,appBar: AppBar(title: const Text("package 测试"),),body: const HomePage()),));}
}class HomePage extends StatefulWidget {const HomePage({Key? key}) : super(key: key);@overrideState<HomePage> createState() => _HomePageState();
}class _HomePageState extends State<HomePage> {int value = 0;@overrideWidget build(BuildContext context) {return Center(child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: [Text(value.toString(), textScaleFactor: 1.5),ElevatedButton(onPressed: () {setState(() {value = Calculator().addOne(value);});},child: const Text('+1')),showPackageDialog(context),],),);}Widget showPackageDialog(BuildContext context) {return MaterialButton(color: Colors.grey[300],minWidth: 300,onPressed: () => Dialogs.materialDialog(msg: '确认关闭?',title: "关闭",color: Colors.white,context: context,dialogWidth: kIsWeb ? 0.3 : null,onClose: (value) => debugPrint("返回值: '$value'"),actions: [ElevatedButton(onPressed: () {Navigator.of(context).pop(['取消了', 'List']);},child: const Text('取消', style: TextStyle(color: Colors.black)),),OutlinedButton(onPressed: () {Navigator.of(context).pop(['关闭了', 'List']);},style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.red)),child: const Text('关闭', style: TextStyle(color: Colors.white)),),]),child: const Text("Show Material Dialog"),);}
}
页面效果如下:
二、 插件(Plugin)
Plugin
和 Package
最大的差别在于 Plugin
可以跨平台访问原生API
提供的能力,然后通过插件调用原生 Android
和 iOS
的功能,以实现某些功能或者更好的用户体验,如获取手机版本信息、调用原生摄像头等,代码包含有 Dart
、Android
原生代码、iOS
原生代码。
2.1 创建Plugin
和创建Package
类似,同样可以使用命令行进行创建:
flutter create --org com.example --template=plugin --platforms=android,ios -a kotlin helloflutter create --org com.example --template=plugin --platforms=android,ios -a java helloflutter create --org com.example --template=plugin --platforms=android,ios -i objc helloflutter create --org com.example --template=plugin --platforms=android,ios -i swift hello
4 条指令,可以根据选择的插件平台和开发语言,自由选择其中之一,这将在 hello
目录下创建一个插件项目,其中包含以下内容:
lib/hello.dart 文件
Dart 插件 API 实现。android/src/main/java/com/example/hello/HelloPlugin.kt 文件
Android 平台原生插件 API 实现(使用 Kotlin 编程语言)。ios/Classes/HelloPlugin.m 文件
iOS 平台原生插件 API 实现(使用 Objective-C 编程语言)。example/ 文件
一个依赖于该插件并说明了如何使用它的 Flutter 应用。默认情况下,插件项目中 iOS 代码使用 Swift 编写, Android 代码使用 Kotlin 编写
要在现有的插件项目中添加对特定平台的支持,请在项目目录运行 flutter create
命令,并加入--template=plugin
。例如,要对现有的插件项目添加 Web 支持,请运行以下命令:
flutter create --template=plugin --platforms=web .
建议使用IDE进行创建,比较直观且易操作。
1、首先打开 Android Studio
,然后点击 File->New->New Flutter Project
。
2、进入到选择 flutter sdk
页面,选择自己本地的 flutter
,然后点击 next
。
3、选择 Plugin type
,如下图所示。
前几个步骤和创建 Package
是一样的,唯一的区别在与第三步,这里 Project type
选择Plugin
点击 create
后创建项目,项目结构如下:
lib
目录下的文件为插件的 flutter
具体实现,example
是插件测试项目,可以对 plugin
进行功能测试,iOS
目录下为插件的iOS
端原生实现,android
目录下为插件的 Android
端原生实现,test
目录为插件对应的单元测试。
2.2 定义Plugin
这里先重点关注 lib
目录下的三个文件:
- 接口定义
flutter_plugin_demo_platform_interface.dart
:定义接口的地方,该文件只定义方法。getPlatformVersion()
为创建项目时,自动生成的方法,这里我们新增一个getScreenWidth()
方法,来获取屏幕宽度:
import 'package:plugin_platform_interface/plugin_platform_interface.dart';import 'flutter_plugin_demo_method_channel.dart';abstract class FlutterPluginDemoPlatform extends PlatformInterface {/// Constructs a FlutterPluginDemoPlatform.FlutterPluginDemoPlatform() : super(token: _token);static final Object _token = Object();static FlutterPluginDemoPlatform _instance = MethodChannelFlutterPluginDemo();/// The default instance of [FlutterPluginDemoPlatform] to use.////// Defaults to [MethodChannelFlutterPluginDemo].static FlutterPluginDemoPlatform get instance => _instance;/// Platform-specific implementations should set this with their own/// platform-specific class that extends [FlutterPluginDemoPlatform] when/// they register themselves.static set instance(FlutterPluginDemoPlatform instance) {PlatformInterface.verifyToken(instance, _token);_instance = instance;}Future<String?> getPlatformVersion() {throw UnimplementedError('platformVersion() has not been implemented.');}Future<String?> getScreenWidth() {throw UnimplementedError('getScreenWidth() has not been implemented.');}
}
- 接口实现
接口实现主要在 flutter_plugin_demo_method_channel
文件中,该文件为上述 interface
的子类,负责具体接口的实现,但方法的具体实现依赖于调用原生端的实现。
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';import 'flutter_plugin_demo_platform_interface.dart';/// An implementation of [FlutterPluginDemoPlatform] that uses method channels.
class MethodChannelFlutterPluginDemo extends FlutterPluginDemoPlatform {/// The method channel used to interact with the native platform.@visibleForTestingfinal methodChannel = const MethodChannel('flutter_plugin_demo');@overrideFuture<String?> getPlatformVersion() async {final version = await methodChannel.invokeMethod<String>('getPlatformVersion');return version;}@overrideFuture<String?> getScreenWidth() async {final screenWidth = await methodChannel.invokeMethod<String>('getScreenWidth');return screenWidth;}
}
这里 getScreenWidth
的实现需要通过 methodChannel
调用原生方法。
- 调用
和 plugin
项目同名的类 flutter_plugin_demo.dart
主要是供外部调用,该类中提供了所有对外调用的方法。这个类提供的方法包含两类,一类为使用 Dart 直接实现,一类为需要依赖于原生端实现,需要调用 flutter_plugin_demo_platform_interface
接口类中的方法
import 'flutter_plugin_demo_platform_interface.dart';class FlutterPluginDemo {Future<String?> getPlatformVersion() {return FlutterPluginDemoPlatform.instance.getPlatformVersion();}Future<String?> getScreenWidth() {return FlutterPluginDemoPlatform.instance.getScreenWidth();}// 直接 dart 实现int getScreenHeight() {return 1080;}
}
2.3 iOS 原生实现
iOS
目录主要为 iOS 原生端实现,这里可以使用 example
下的 iOS
项目进行调试,因项目默认没有 pod
文件,所以需要对 example
目录下的 iOS
进行 pod install
。
这里需要注意的是,创建项目时选择的是 Swift
语言,所以具体实现是在 swift
类中,若选择的是oc
,则实现是在 oc
类中。其实 swift
的实现也是通过oc
的类调用的 swift
。
在 FlutterPluginDemoPlugin.m
类中可以看到调用的是SwiftFlutterPluginDemoPlugin.swift
,具体的实现是在该类中:
#import "FlutterPluginDemoPlugin.h"
#if __has_include(<flutter_plugin_demo/flutter_plugin_demo-Swift.h>)
#import <flutter_plugin_demo/flutter_plugin_demo-Swift.h>
#else
// Support project import fallback if the generated compatibility header
// is not copied when this plugin is created as a library.
// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
#import "flutter_plugin_demo-Swift.h"
#endif@implementation FlutterPluginDemoPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {[SwiftFlutterPluginDemoPlugin registerWithRegistrar:registrar];
}
@end
SwiftFlutterPluginDemoPlugin
类中实现:
import Flutter
import UIKitpublic class SwiftFlutterPluginDemoPlugin: NSObject, FlutterPlugin {public static func register(with registrar: FlutterPluginRegistrar) {let channel = FlutterMethodChannel(name: "flutter_plugin_demo", binaryMessenger: registrar.messenger())let instance = SwiftFlutterPluginDemoPlugin()registrar.addMethodCallDelegate(instance, channel: channel)}public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {switch call.method {case "getScreenWidth":let screenRect = UIScreen.main.boundslet screenWidth = screenRect.size.widthlet screenHeight = screenRect.size.heightresult("iOS \(screenWidth)")case "getPlatformVersion":result("iOS " + UIDevice.current.systemVersion)default:result("no such method !")}}
}
2.4 Android 原生实现
因为创建项目时选择的语言为 kotlin
,因此 Android
的原生实现在 kotlin
目录下与项目同名的 FlutterPluginDemoPlugin.kt
文件中:
package com.example.flutter_plugin_demoimport android.content.Context
import androidx.annotation.NonNullimport 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/** FlutterPluginDemoPlugin */
class FlutterPluginDemoPlugin: FlutterPlugin, MethodCallHandler {/// The MethodChannel that will the communication between Flutter and native Android////// This local reference serves to register the plugin with the Flutter Engine and unregister it/// when the Flutter Engine is detached from the Activityprivate lateinit var channel : MethodChannelprivate var context: Context? = nulloverride fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_plugin_demo")context = flutterPluginBinding.applicationContextchannel.setMethodCallHandler(this)}override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {when (call.method) {"getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}")"getScreenWidth" -> result.success("Android ${context?.resources?.displayMetrics?.widthPixels}")else -> result.notImplemented()}}override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {channel.setMethodCallHandler(null)}
}
2.5 功能验证
完成以上步骤,我们的插件就开发完成了,接下来让我们在 example
项目中验证下我们刚刚开发的插件,在 example/lib/main.dart
中编写测试代码:
import 'dart:async';import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_plugin_demo/flutter_plugin_demo.dart';void main() {runApp(const MyApp());
}class MyApp extends StatefulWidget {const MyApp({super.key});@overrideState<MyApp> createState() => _MyAppState();
}class _MyAppState extends State<MyApp> {String _platformVersion = 'Unknown';String _screenWidth = 'Unknown';final _flutterPluginDemoPlugin = FlutterPluginDemo();@overridevoid initState() {super.initState();initPlatformState();_getScreenWidth();}// Platform messages are asynchronous, so we initialize in an async method.Future<void> initPlatformState() async {String platformVersion;// Platform messages may fail, so we use a try/catch PlatformException.// We also handle the message potentially returning null.try {platformVersion = await _flutterPluginDemoPlugin.getPlatformVersion() ??'Unknown platform version';} on PlatformException {platformVersion = 'Failed to get platform version.';}// If the widget was removed from the tree while the asynchronous platform// message was in flight, we want to discard the reply rather than calling// setState to update our non-existent appearance.if (!mounted) return;setState(() {_platformVersion = platformVersion;});}Future<void> _getScreenWidth() async {String screenWidth;try {screenWidth = await _flutterPluginDemoPlugin.getScreenWidth() ?? '0';} on PlatformException {screenWidth = 'Failed to get screen width.';}if (!mounted) return;setState(() {_screenWidth = screenWidth;});}@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('Plugin example app'),),body: Column(children: [Center(child: Text('Running on: $_platformVersion\n',textScaleFactor: 1.5),),Center(child: Text('screen width: $_screenWidth\n',textScaleFactor: 1.5),),],),),);}
}
最终页面效果:
三、发布
发布主要有两种,一种是发布到 Flutter 的官方,一种是发布到自己公司的私有库中,两者发布步骤大部分都是相同的,在一些地方存在差异,下面对这两种发布方法进行介绍。
3.1 发布前准备
首先需要检查下 Package
或 Plugin
中是否包含有 pubspec.yaml
、README.md
、CHANGELOG.md
、LICENSE
这四个文件,需要检查下完整性。这四项内容默认创建的时候都会有,若缺少可以在项目终端中运行 flutter create .
进行补全(注意有个点)。
- pubspec.yaml
该文件主要是引用外部插件,其中 name
、description
、version
、homepage
需要填写完整,publish_to
指将该库发布到指定位置,发布到私有库需要,若发布到 pub.dev
则不需要。
- README.md
该文件主要是写插件或包的内容,便于别人查看时告知该库的作用。
- CHANGELOG.md
该文件主要是记录插件或包的版本修改记录,说明每个版本的更新内容。
- LICENSE
该文件主要是添加许可证书,可根据需要填写
上述准备工作完成后,需要对项目进行校验,打开终端,进入插件或包的路径,然后运行
flutter packages pub publish --dry-run
然后查看运行结果,如果警告信息为0,说明没什么问题,就可以进行发布了。
3.2 发布
校验完成以后就可以发布了,还是使用命令行,最好先科学上网,然后再运行命令进行发布:
flutter packages pub publish
发布到官方和私有库有点区别,若发布到官方,运行命令后会多一步登录谷歌账号校验的步骤,而发布到私有库不需要。
发布到私有库,需要进行私有仓库的搭建,这个网上有很多教程,可以参考下:Flutter私有仓库搭建
注意:
设置了中国镜像的开发者,目前所存在的镜像都不能(也不应该)进行 package 的上传。如果你设置了镜像,执行上述发布代码可能会造成发布失败。网络设定好后,无需取消中文镜像,执行下述代码可直接上传:
flutter pub publish --server=https://pub.dartlang.org
四、使用
4.1 依赖 pub.dev 仓库的 package 包
cupertino_icons: ^1.0.2
4.2 依赖远程 git 仓库的 package 包
flutter_plugin_name:
git:url: https://github.com/xxxxxx/xxxxxx.git #git仓库地址
path: xxxxx #如果项目不是在git地址的根目录 则需要指定path
ref: ‘1.0.0' #指定的版本 对应git仓库中的tag标签 也可以指定分支 ref: some-branch
4.3 依赖私有 pub 仓库的 package 包
flutter_plugin_name:
hosted:
name: flutter_plugin_name
url: https://xxxxxversion:1.0.3
4.3 依赖本地仓库的 package 包
flutter_plugin_name:
path: ../ #可以是相对路径也可以是绝对路径
五、其他
在创建项目的时候我们发现还有其他几个类型的 Project type
可选:Application
大家都很熟悉了,它是一个纯 flutter
项目,主体是 Flutter
,当然它也可以接入 Android Module
或者 iOS Framework
,其内部包含 Android 和 iOS 项目。Plugin
、Package
上面已经介绍过了,下面来简单看下剩余的几个类型
5.1 Module
项目结构:
特性:
1、这种方式常用与将 Flutter
项目集成到原生项目中进行混合开发,原生项目是主体(宿主)
2、项目中只能使用 Flutter/Dart
,不能直接使用原生语言,但是可以使用包含原生语言的 package
3、可以在 pubspec.yaml
使用 package/plugin
适用场景:
1、已有 Native
项目,新开发的功能使用 Flutter
开发,提高效率
2、已存在旧的 Flutter
项目,以 module
方式集成新开发的功能
集成方式:
1、直接使用源码,集成到原生项目中
2、打包成资源包,集成到项目中。如安卓打包成 aar
集成到项目中
存在问题:
1、集成多个 module
时,需要考虑 Flutter Engine
的使用。多 Flutter Engine
会存在内存之间不能共享的问题。Dart 2.15 之后,Isolate
组之内的 isolate
可以共享内存。
2、多个 Flutter engine
会消耗大量资源
3、Native
打开 Flutter
页面时,由于 Flutter Engine
需要初始化,需要消耗一定时间,造成页面跳转存在延迟(“卡顿”)
5.2 Skeleton
这种创建项目方式,从 Flutter 2.5
版本以后开始支持,本质还是一个 Flutter Application
,这种方式就是为开发提供一种较好的项目模板,不在是默认的 Couter App
。在模板中可以看到路由、assets
资源、多语言、状态管理,功能优先」的文件夹组织方式、主题等多种最佳实践方式
5.3 FFI Plugin
FFI Plugin
是官方提供的一种方式来集成指定平台的本地C源代码功能,虽然常规的 plugin
也可以支持,但是主要用途还是支持 method channel
,即 dart
调用各种相关平台的 API
(Android
中的 Java
或 Kotlin API
,iOS
中的 Objective-C
或 Swift API
,Windows
操作系统中的C++ API
),而且官方的意思是3.0之后对C源代码功能的支持 FFI Plugin
会更强大,所以我们如果只是调用C代码,不需要平台 SDK API
的话,可以考虑使用 FFI Plugin
。
创建完项目后,我们观察一下 FFI Plugin
项目的目录结构,对比常规 plugin
,主要有以下几点不同:
本地的源代码文件和 CMakeLists.txt
文件现在统一放到项目的 src
目录下,ios
平台目录Classes
下面的源文件存在,只是引入了项目 src
下面的源代码,android
平台 build.gradle
文件中 externalNativeBuild
属性中 cmake
的路径也是指向 src
中的CMakeLists.txt
。
项目中的 pubspec.yaml
提供了如下配置选项:
代表利用 ffiPlugin
去为各个不同的平台编译源代码,并且绑定了二进制文件集成到 flutter
应用中去,你需要哪些平台都需要体现在这个配置项中。
最后通过 DynamicLibrary
来加载库中的方法,具体参考项目 lib
目录下的同名 dart
文件。
六、总结
以上介绍了创建 flutter
不同类型项目的方式、差别与使用场景,并详细介绍了如何开发一个Package
与 Plugin
,对应 Package
项目源码与上传 GitHub
,感兴趣可参考:
- flutter_package_demo:GitHub - ericwangjp/flutter_package_demo: A new flutter package demo project.
- flutter_plugin_demo:https://github.com/ericwangjp/flutter_plugin_demo