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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/148335.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CUDA+cuDNN+TensorRT 配置避坑指南

深度学习模型加速部署的环境配置&#xff0c;需要在本地安装NVIDIA的一些工具链和软件包&#xff0c;这是一个些许繁琐的过程&#xff0c;而且一步错&#xff0c;步步错。笔者将会根据自己的经验来提供建议&#xff0c;减少踩坑几率。当然可以完全按照官方教程操作&#xff0c;…

插入排序:简单而有效的排序方法

在计算机科学中&#xff0c;排序算法是一个重要且常见的主题&#xff0c;它们用于对数据进行有序排列。插入排序&#xff08;Insertion Sort&#xff09;是其中一个简单但有效的排序算法。本文将详细解释插入排序的原理和步骤&#xff0c;并提供Java语言的实现示例。 插入排序的…

B2主题优化:WordPress文章每次访问随机增加访问量

老站长都知道&#xff0c;一个新站刚开始创建&#xff0c;内容也不多的时候&#xff0c;用户进来看到文章浏览量要么是0&#xff0c;要么是 个位数&#xff0c;非常影响体验&#xff0c;就会有一种“这个网站没人气&#xff0c;看来不行”的感觉。 即使你的内容做的很好&#x…

全志ARM926 Melis2.0系统的开发指引②

全志ARM926 Melis2.0系统的开发指引② 编写目的4. 编译工具链使用4.1.工具链通用配置4.2.模块的工具链配置4.3.简单的 makefile 5. 固件烧录工具的安装5.1.PhoenixSuit 的安装步骤5.2.检验 USB 驱动安装5.3.使用烧录软件 PhoenixSuit -全志相关工具和资源-.1 全志固件镜像修改工…

HTTP协议,请求响应

、概述 二、HTTP请求协议 三、HTTP响应协议 四、请求数据 1.简单实体参数 RequestMapping("/simpleParam")public String simpleParam(RequestParam(name "name" ,required false ) String username, Integer age){System.out.println (username "…

Boost程序库完全开发指南:1.2-C++基础知识点梳理

主要整理了N多年前&#xff08;2010年&#xff09;学习C的时候开始总结的知识点&#xff0c;好长时间不写C代码了&#xff0c;现在LLM量化和推理需要重新学习C编程&#xff0c;看来出来混迟早要还的。 1.const_cast <new_type> (expression)[1] 解析&#xff1a;const_c…

基于Vue+ELement实现增删改查案例与表单验证(附源码)

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《ELement》。&#x1f3af;&#x1f3af; &#x1…

mysql双主+双从集群连接模式

架构图&#xff1a; 详细内容参考&#xff1a; 结果展示&#xff1a; 178.119.30.14(主) 178.119.30.15(主) 178.119.30.16(从) 178.119.30.17(从)

全志ARM926 Melis2.0系统的开发指引①

全志ARM926 Melis2.0系统的开发指引① 1. 编写目的2. Melis2.0 系统概述3. Melis2.0 快速开发3.1. Melis2.0 SDK 目录结构3.2. Melis2.0 编译环境3.3. Melis2.0 固件打包3.4. Melis2.0 固件烧录3.5.串口打印信息3.6. Melis2.0 添加和调用一个模块3.6.1. 为什么划分模块&#xf…

【学习笔记】CF1817F Entangled Substrings(基本子串结构)

前置知识&#xff1a;基本子串结构&#xff0c;SAM的结构和应用 学长博客 字符串理论比较抽象&#xff0c;建议直观的去理解它 子串 t t t的扩展串定义为 ext(t) : t ′ \text{ext(t)}:t ext(t):t′&#xff0c;满足 t t t是 t ′ t t′的子串&#xff0c;且 occ(t) occ(t…

水浒传数据集汇总

很喜欢《水浒传》&#xff0c;希望能将它融入我的考研复习中&#xff0c;打算用水浒传数据来贯穿数据结构的各种知识&#xff0c;先汇总下找到的数据集 天池上看到的一个水浒传文本数据集&#xff1a;https://tianchi.aliyun.com/dataset/36027 Hareric/masterworkNLP: 基于社…

基于 SpringBoot 2.7.x 使用最新的 Elasticsearch Java API Client 之 ElasticsearchClient

1. 从 RestHighLevelClient 到 ElasticsearchClient 从 Java Rest Client 7.15.0 版本开始&#xff0c;Elasticsearch 官方决定将 RestHighLevelClient 标记为废弃的&#xff0c;并推荐使用新的 Java API Client&#xff0c;即 ElasticsearchClient. 为什么要将 RestHighLevelC…

【C语言】内存函数的详细教学和模拟实现

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是gugugu。希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f194;本文由 gugugu 原创 CSDN首发&#x1f412; 如需转载还请通知⚠…

【再识C进阶4】详细介绍自定义类型——结构体、枚举和联合

学习目标&#xff1a; 在上一篇博客中&#xff0c;我们已经详细地学习了字符分类函数、字符转换函数和内存函数。那这一篇博客和上一篇博客的关系不是那么相连。 这一篇博客主要介绍一下自定义类型&#xff0c;因为在解决实际问题时&#xff0c;由于世界上的因素有很多&#xf…

karmada v1.7.0安装指导

前言 安装心得 经过多种方式操作&#xff0c;发现二进制方法安装太复杂&#xff0c;证书生成及其手工操作太多了&#xff0c;没有安装成功&#xff1b;helm方式的安装&#xff0c;v1.7.0的chart包执行安装会报错&#xff0c;手工修复了报错并修改了镜像地址&#xff0c;还是各…

一文拿捏Spring事务之、ACID、隔离级别、失效场景

1.&#x1f31f;Spring事务 1.编程式事务 事务管理代码嵌入嵌入到业务代码中&#xff0c;来控制事务的提交和回滚&#xff0c;例如TransactionManager 2.声明式事务 使用aop对方法前后进行拦截&#xff0c;然后在目标方法开始之前创建或者加入一个事务&#xff0c;执行完目…

一键AI高清换脸——基于InsightFace、CodeFormer实现高清换脸与验证换脸后效果能否通过人脸比对、人脸识别算法

前言 AI换脸是指利用基于深度学习和计算机视觉来替换或合成图像或视频中的人脸。可以将一个人的脸替换为另一个人的脸,或者将一个人的表情合成到另一个人的照片或视频中。算法常常被用在娱乐目上,例如在社交媒体上创建有趣的照片或视频,也有用于电影制作、特效制作、人脸编…

MySQL5.7版本与8.0版本在CentOS系统安装

目录 前置要求 1. MySQL5.7版本在CentOS系统安装 1.1 安装 1.1.1 配置yum仓库 1.1.2 使用yum安装MySQL 1.1.3 安装完成后&#xff0c;启动MySQL并配置开机自启动 1.1.4 检查MySQL的运行状态 1.2 配置 1.2.1 获取MySQL的初始密码 1.2.2 登陆MySQL数据库系统 …

《Secure Analytics-Federated Learning and Secure Aggregation》论文阅读

背景 机器学习模型对数据的分析具有很大的优势&#xff0c;很多敏感数据分布在用户各自的终端。若大规模收集用户的敏感数据具有泄露的风险。 对于安全分析的一般背景就是认为有n方有敏感数据&#xff0c;并且不愿意分享他们的数据&#xff0c;但可以分享聚合计算后的结果。 联…

Python无废话-办公自动化Excel图表制作

openpyxl 支持用Excel工作表中单元格的数据&#xff0c;创建条形图、折线图、散点图和饼图等。 图表制作步骤 在openpyxl模块中创建图表&#xff0c;步骤如下: ①选择一个单元格区域&#xff0c;创建Reference 对象&#xff0c;作为图形数据a)(Value)。 ②创建一个Chart对象…