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

Flutter开发之Package与Plugin

前言

flutter中有包和插件两个概念,插件 (plugin) 是 package 的一种,全称是 plugin package,我们简称为 plugin,中文叫插件。包(Package)主要指对flutter相关功能的封装,类似于Android中的插件和iOS中的三方库。而插件(Plugin)主要指通过插件调用原生的功能,如获取手机基本信息、获取原生的相机等。两者还是存在一定的差别的,Package一般只包含Dart代码,而插件除了包含有Dart外,还会包含有原生的语言,比如安卓中的JavaKotlin,和iOS中的Objective-CSwiftPackagePlugin都是为了封装一些基础组件、业务组件等,实现组件化开发,这样项目中多处用到可以快速引入实现功能。

  • 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,平台暂时只选择 AndroidiOS,开发语言选择对应的 kotlinSwift 然后创建项目后,目录结构如下:

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 的功能,以实现某些功能或者更好的用户体验,如获取手机版本信息、调用原生摄像头等,代码包含有 DartAndroid 原生代码、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),),],),),);}
}

最终页面效果:

Android 截图iOS 截图

三、发布

发布主要有两种,一种是发布到 Flutter 的官方,一种是发布到自己公司的私有库中,两者发布步骤大部分都是相同的,在一些地方存在差异,下面对这两种发布方法进行介绍。

3.1 发布前准备

首先需要检查下 Package 或 Plugin 中是否包含有 pubspec.yamlREADME.mdCHANGELOG.mdLICENSE 这四个文件,需要检查下完整性。这四项内容默认创建的时候都会有,若缺少可以在项目终端中运行 flutter create .进行补全(注意有个点)。

  • pubspec.yaml

该文件主要是引用外部插件,其中 namedescriptionversionhomepage 需要填写完整,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://xxxxx

version:1.0.3

4.3 依赖本地仓库的 package 包

flutter_plugin_name:

path: ../ #可以是相对路径也可以是绝对路径

五、其他

在创建项目的时候我们发现还有其他几个类型的 Project type 可选:Application 大家都很熟悉了,它是一个纯 flutter 项目,主体是 Flutter,当然它也可以接入 Android Module 或者 iOS Framework,其内部包含 Android 和 iOS 项目。PluginPackage 上面已经介绍过了,下面来简单看下剩余的几个类型

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 调用各种相关平台的 APIAndroid 中的 Java  或  Kotlin APIiOS 中的 Objective-C 或 Swift APIWindows操作系统中的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

相关文章:

Flutter开发之Package与Plugin

前言 在flutter中有包和插件两个概念&#xff0c;插件 (plugin) 是 package 的一种&#xff0c;全称是 plugin package&#xff0c;我们简称为 plugin&#xff0c;中文叫插件。包(Package)主要指对flutter相关功能的封装&#xff0c;类似于Android中的插件和iOS中的三方库。而插…...

[极客大挑战 2019]RCE ME 取反绕过正则匹配 绕过disable_function设置

目录 取反 1.蚁剑插件绕过 2.baypass disable_function open_dir/disable_function putenv()/LD_PRELOAD 来绕过限制 利用条件 利用思路 有意思。。。。 <?php error_reporting(0); if(isset($_GET[code])){$code$_GET[code];if(strlen($code)>40){die("Th…...

硬盘接口随机

关于硬盘接口 1 首先&#xff0c;关于[物理接口、协议、通道]2 物理接口&#xff1a;通讯中的电&#xff0c;光口&#xff0c;“物理规格&#xff0c;像是公路、铁路”。通道&#xff1a;通讯协议中的应用层以下所有层&#xff1f;“县道&#xff0c;省道&#xff0c;高速&am…...

芯片测试方案之如何测试芯片EN输入阈值?

在电源管理芯片的设计中&#xff0c;除了常规的VIN、VOUT以及GND端口之外&#xff0c;还会有SW、EN、FB等芯片独有的特殊端口引脚&#xff0c;这些引脚或负责电源开关的输入&#xff0c;或负责电路的反馈电压/电流&#xff0c;这些引脚在芯片的工作中有着极其重要的作用&#x…...

screenOrientation的值

在 Android 应用程序中&#xff0c;android:screenOrientation 属性可以设置为多个不同的值&#xff0c;以控制活动的屏幕方向。以下是一些常用的 android:screenOrientation 的值&#xff1a; "unspecified"&#xff1a;这是默认值&#xff0c;表示系统会根据设备的…...

为什么SQL预编译可以防止SQL注入攻击

前言 防范SQL注入攻击是每一位做后端开发的程序员必须会的基本功。本文介绍其中一种防范攻击的方法&#xff1a;SQL预编译。 本文大部分内容引用自这篇文章&#xff0c;部分内容有修改。 注入例子 先简单回顾下SQL注入攻击的过程&#xff0c;假设有一个SQL语句&#xff1a; …...

基于体系结构-架构真题2022(四十一)

给定关系模式R&#xff08;U,F&#xff09;&#xff0c;其中U为属性集&#xff0c;F是U上的一组函数依赖&#xff0c;那么函数依赖的公理系统中分解规则是指&#xff08;&#xff09;为F所蕴含。 解析&#xff1a; 伪传递是x到y&#xff0c;wy到z&#xff0c;则xw到z 传递是z…...

【uniapp+vue3 】页面加载时根据不同角色设置导航栏标题

uniapp 页面加载时根据不同角色设置导航栏标题 其实很好实现&#xff0c;第一次开发uniapp项目&#xff0c;所以什么都不懂&#xff0c;绕了一点点的弯路 在对应页面的onLoad中获取到跳转过来传的参数中的判断角色字段&#xff0c;我这里传的是getRole uni.setNavigationBarT…...

不讲故事的设计模式-模板方法模式

文章目录 模板方法模式简介作用模板方法模式的缺点模板方法模式的应用场景业务场景开源框架中的应用 对比回调和Hook模式关于组合优先于继承 关于设计模式乱用的现象 模板方法模式 简介 模板方法模式是一种行为型设计模式&#xff0c;该设计模式的核心在于通过抽象出一套相对…...

基于SpringBoot的酒店客房管理系统

基于SpringBoot的酒店管理系统、酒店客房管理系统 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 首页 管理员界面 用户界面 代码展示 <temp…...

消息队列-RabbitMQ(二)

接上文《消息队列-RabbitMQ&#xff08;一&#xff09;》 1、RabbitMQ概念...

程序通过命令行获取操作系统名称+版本+CPU名称等:Part2

文章目录 &#xff08;一&#xff09;沿用的方法&#xff08;二&#xff09;问题和调整&#xff08;2.1&#xff09;Windows11的版本号是10.0&#xff08;2.2&#xff09;Golang和管道符号&#xff08;Linux&#xff09;&#xff08;2.3&#xff09;最大内存容量 vs 当前安装内…...

微软最热门的10款前端开源项目!

本文来盘点微软开源的十大前端项目&#xff0c;这些项目在 Github 上获得了超过 45 万 Star&#xff01; Visual Studio Code Visual Studio Code 是一款由微软开发的开源的代码编辑器。它支持多种编程语言&#xff0c;如C、C、C#、Python、JavaScript 和 TypeScript 等&…...

C#(CSharp)入门实践项目(简易回合制游戏)

项目名称 木木夕营救公主 项目介绍 这是一个小游戏&#xff0c;你将扮演一个英雄&#xff08;木木夕&#xff09;&#xff0c;去打败恶龙&#xff0c;拯救出公主&#xff0c;该项目采用回合制战斗模式&#xff0c;由于角色的血量和攻击为随机数&#xff0c;所以需要靠运气才…...

GEO生信数据挖掘(五)提取临床信息构建分组,分组数据可视化(绘制层次聚类图,绘制PCA图)

检索到目标数据集后&#xff0c;开始数据挖掘&#xff0c;本文以阿尔兹海默症数据集GSE1297为例 上节做了很多的基因数据清洗&#xff08;离群值处理、低表达基因、归一化、log2处理&#xff09;操作&#xff0c;本节介绍构建临床分组信息。 我们已经学习了提取表达矩阵的临床…...

golang时间问题汇总(用法常见问题:插入数据库时间自动+8)

golang时间问题汇总&#xff08;用法&常见问题&#xff09; 1 用法 1.1 time.Parse() func main() {timeStr : "2023-09-26 20:56:23"allDate, _ : time.Parse("2006-01-02 15:04:05", timeStr)fmt.Println("全部解析", allDate) timeStr…...

TCP网络连接中的三次握手和四次挥手

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有需要我的支持&#xff0c;请私信或评论留言&#xff01; TCP网络连接中的三…...

游戏服务商Latis Global参展2023 ChinaJoy B2B

第20届ChinaJoy于2023年7月在上海举行了为期四天的博览会,参展观众达到了33.8万人次。ChinaJoy是全球最具知名度与影响力的年度盛会之一,涵盖了包括游戏、动漫、互联网影视、电子竞技、潮流玩具、智能娱乐在内的多个数字娱乐领域。ChinaJoy不仅仅代表了数字娱乐领域的最新风向,…...

oracle常用sql

oracle常用sql oracle常用sql查询当前会话id(sid),会话序列号(serial#),操作系统进程id(spid)查询数据库信息查询实例信息查询字符集查看回收站情况数据库系统PSU信息数据库大小查看表空间状况常规库表空间情况查询,非CDBCBD表空间情况查询当前客户端信息资源使用情况…...

手游模拟器长时间运行后,游戏掉帧且不恢复

1&#xff09;手游模拟器长时间运行后&#xff0c;游戏掉帧且不恢复 2&#xff09;FrameBuffer Fetch无论哪种模式在确定支持的手机上显示全紫 3&#xff09;协程中yield return CoFunction()和yield return StartCoroutine(CoFunction())的区别 这是第353篇UWA技术知识分享的推…...

linux下离线安装telnet

安装过程概要&#xff1a; &#xff08;一&#xff09;互联网端下载rpm包&#xff1b; &#xff08;二&#xff09;上传到服务器root目录下&#xff1b; &#xff08;三&#xff09;安装telnet服务和测试&#xff1a; 详细内容&#xff1a; &#xff08;一&#xff09;互联…...

Unity 发布WebGL平台,C#与JavaScript交互

发布H5平台&#xff0c;接入SDK&#xff0c;比如微信等&#xff0c;涉及到C#与JS的交互。 jslib&#xff08;JavaScript Library&#xff09;是Unity的一种机制&#xff0c;允许你在C#中通过JavaScript代码来执行一些操作。这是一种高级的技巧&#xff0c;主要用于一些特殊情况…...

利用 Forcing InnoDB Recovery 特性解决 MySQL 重启失败的问题

问题 由于异常断电或者系统异常重启时 MySQL 没有正常退出导致 MySQL 无法启动&#xff0c;启动时报错如下&#xff1a; [System] [Server] /usr/sbin/mysqld (mysqld 8.0.30) starting as process 2665 [System] [InnoDB] InnoDB initialization has started. [System] [Inn…...

windows修改键位F11变insert(改键盘映射)

这里是通过改变windows的注册表来实现的 1.按住winr打开运行&#xff0c;在运行中输入“regedit”&#xff0c;再点击“确定”按钮。如下图 2.找到注册表的目录 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout 3.在Keyboard Layout右击新建 -> 二进…...

安装gpu版本的paddle和paddleclas

安装gpu版本的paddle python -m pip install paddlepaddle-gpu2.3.2.post111 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html以上支持cuda11.1版本 其他需求可查阅文档在这里 安装paddleclas 1 在虚拟环境中安装所需的Python库&#xff1a; pip inst…...

61从零开始学Java之处理大数字相关的类有哪些?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 我们知道&#xff0c;在现实世界里&#xff0c;实际上数字是有无穷个的&#xff0c;就比如0和1之间&a…...

vscode 搜索界面的files to include files to exclude 是什么功能?

在VSCode&#xff08;Visual Studio Code&#xff09;中&#xff0c;搜索功能是一个强大的工具&#xff0c;可以帮助你在项目中快速查找特定的文本、代码或其他内容。搜索界面的 “files to include” 和 “files to exclude” 提供了一种方式来定制你的搜索范围。 files to in…...

数据计算-第15届蓝桥杯第一次STEMA测评Scratch真题精选

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第154讲。 第15届蓝桥杯第1次STEMA测评已于2023年8月20日落下帷幕&#xff0c;编程题一共有6题&#xff0c;分别如下&a…...

谈谈前端和后端的选择

引言 在我的印象中&#xff0c;也是视线里&#xff0c;后端都是在一个黑屏的页面&#xff0c;左边一个文件类&#xff0c;右边在不停的写sql,一只手放在键盘上&#xff0c;一边写&#xff0c;一遍不停的关联进入&#xff0c;感觉很无趣&#xff0c;他们的分享不是什么java集成&…...

Vue3最佳实践 第六章 Pinia,Vuex与axios,VueUse 1(Pinia)

Pinia状态管理 在 Vue3 中项目中组件之间传递数据时&#xff0c;可以使用 Props 和 Emit&#xff0c;还可以使用 Provide/Inject 来代替 Props 和 Emit。Props 和 Emit 只能在具有父子关系的组件之间传递数据&#xff0c;所以层级越深&#xff0c;过程就越复杂。为了解决此类问…...