flutter 专题 六十四 在原生项目中集成Flutter
概述
使用Flutter从零开始开发App是一件轻松惬意的事情,但对于一些成熟的产品来说,完全摒弃原有App的历史沉淀,全面转向Flutter是不现实的。因此使用Flutter去统一Android、iOS技术栈,把它作为已有原生App的扩展能力,通过有序推进来提升移动终端的开发效率。
目前,想要在已有的原生App里嵌入一些Flutter页面主要有两种方案。一种是将原生工程作为Flutter工程的子工程,由Flutter进行统一管理,这种模式称为统一管理模式。另一种是将Flutter工程作为原生工程的子模块,维持原有的原生工程管理方式不变,这种模式被称为三端分离模式,如下图所示。
三端代码分离模式的原理是把Flutter模块作为原生工程的子模块,从而快速地接入Flutter模块,降低原生工程的改造成本。在Flutter 1.1x时代,在原生已有app中接入Flutter的步骤比较繁琐,具体可以可以参考:Flutter与原生混合开发
不过,从Flutter 1.20.x版本开始,Flutter对原生app接入Flutter进行了优化和升级,下面是具体介绍。
原生Android集成Flutter
支持的特性
-
在 Gradle 脚本中添加一个自动构建并引入 Flutter 模块的 Flutter SDK 钩子。
-
将 Flutter 模块构建为通用的 Android Archive (AAR) 以便集成到您自己的构建系统中,并提高 Jetifier 与 AndroidX 的互操作性;
-
FlutterEngine API 用于启动并持续地为挂载 FlutterActivity 或 FlutterFragment 提供独立的 Flutter 环境;
-
Android Studio 的 Android 与 Flutter 同时编辑,以及 Flutter module 创建与导入向导;
-
支持Java 和 Kotlin 为宿主的应用程序;
集成Flutter
首先,我们来看一下最终的效果,如下图所示。
集成Flutter主要有两种方式,一种是使用Android Studio工具的方式,另一种是使用手动的方式。
使用Android Studio方式
直接使用 Android Studio 是在现有应用中自动集成 Flutter 模块比较便捷的方法。在 Android Studio 打开现有的 Android 原生项目,然后依次点击菜单按钮 File > New > New Module…创建出一个可以集成的新 Flutter 模块,或者选择导入已有的 Flutter 模块,如下图所示。
选择Module的类型为Flutter Module,然后在向导窗口中填写模块名称、路径等信息,如下图所示。
此时,Android Studio 插件就会自动为这个 Android 项目配置添加 Flutter 模块作为依赖项,这时集成应用就已准备好进行下一步的构建。
手动集成
如果想要在不使用 Flutter 的 Android Studio 插件的情况下手动将 Flutter 模块与现有的 Android 应用集成,可以使用下面的步骤。
假设我们的原生应用在 some/path/MyApp 路径下,那么在Flutter 项目的同级目录下新建一个Flutter模块,命令如下。
cd some/path/
flutter create -t module --org com.example my_flutter
完成上面的命令后,会在 some/path/my_flutter/ 目录下创建一个 Flutter 模块项目。该模块项目会包含一些 Dart 代码和一些一个隐藏的子文件夹 .android/,.android 文件夹包含一个 Android 项目,该项目不仅可以帮助你通过 flutter run 运行这个 Flutter 模块的独立应用,而且还可以作为封装程序来帮助引导 Flutter 模块作为可嵌入的 Android 库。
同时,由于Flutter Android 引擎需要使用到 Java 8 中的新特性。因此,需要在宿主 Android 应用的 build.gradle 文件的 android { } 块中声明了以下源兼容性代码。
android {//...compileOptions {sourceCompatibility 1.8targetCompatibility 1.8}
}
接下来,需要将Flutter module添加到原生Android工程的依赖中。将 Flutter 模块添加到原生Android应用程序中主要有两种方法实现。使用AAR包方式和直接使用module源码的方式。使用AAR包方式需要先将Flutter 模块打包成AAR包。假设,你的 Flutter 模块在 some/path/my_flutter 目录下,那么打包AAR包的命令如下。
cd some/path/my_flutter
flutter build aar
然后,根据屏幕上的提示完成集成操作,如下图所示,当然也可以在Android原生工程中进行手动添加依赖代码。
事实上,该命令主要用于创建(默认情况下创建 debug/profile/release 所有模式)本地存储库,主要包含以下文件,如下所示。
build/host/outputs/repo
└── com└── example└── my_flutter├── flutter_release│ ├── 1.0│ │ ├── flutter_release-1.0.aar│ │ ├── flutter_release-1.0.aar.md5│ │ ├── flutter_release-1.0.aar.sha1│ │ ├── flutter_release-1.0.pom│ │ ├── flutter_release-1.0.pom.md5│ │ └── flutter_release-1.0.pom.sha1│ ├── maven-metadata.xml│ ├── maven-metadata.xml.md5│ └── maven-metadata.xml.sha1├── flutter_profile│ ├── ...└── flutter_debug└── ...
可以发现,使用上面的命令编译的AAR包主要分为debug、profile和release三个版本,使用哪个版本的AAR需要根据原生的环境进行选择。找到AAR包,然后再Android宿主应用程序中修改 app/build.gradle 文件,使其包含本地存储库和上述依赖项,如下所示。
android {// ...
}repositories {maven {url 'some/path/my_flutter/build/host/outputs/repo'// This is relative to the location of the build.gradle file// if using a relative path.}maven {url 'https://storage.googleapis.com/download.flutter.io'}
}dependencies {// ...debugImplementation 'com.example.flutter_module:flutter_debug:1.0'profileImplementation 'com.example.flutter_module:flutter_profile:1.0'releaseImplementation 'com.example.flutter_module:flutter_release:1.0'
}
当然,除了命令方式外,还可以使用Android Studio来构建AAR包。依次点击 Android Studio 菜单中的 Build > Flutter > Build AAR 即可构建Flutter 模块的 AAR包,如下图所示。
除了AAR包方式外,另一种方式就是使用源码的方式进行依赖,即将flutter_module模块作为一个模块添加到Android原生工程中。首先,将Flutter 模块作为子项目添加到宿主应用的 settings.gradle 中,如下所示。
// Include the host app project.
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File( settingsDir.parentFile, 'my_flutter/.android/include_flutter.groovy'
))
binding 和 evaluation 脚本可以使 Flutter 模块将其自身(如 :flutter)和该模块使用的所有 Flutter 插件(如 :package_info,:video_player 等)都包含在 settings.gradle 上下文中,然后在原生Android工程的app目录下的build.gradle文件下添加如下依赖代码。
dependencies {implementation project(':flutter')
}
到此,在原生Android工程中集成Flutter环境就完成了,接下来编写代码即可。
添加Flutter页面
正常跳转
1, 添加FlutterActivity
Flutter提供了一个FlutterActivity来作为Flutter的容器页面,FlutterActivity和Android原生的Activity没有任何区别,可以认为它是Flutter的父容器组件,但在原生Android程序中,它就是一个普通的Activity,这个Activity必须在AndroidManifest.xml中进行注册,如下所示。
<activityandroid:name="io.flutter.embedding.android.FlutterActivity"android:theme="@style/LaunchTheme"android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"android:hardwareAccelerated="true"android:windowSoftInputMode="adjustResize" />
对于theme属性,我们可以使用Android的其他样式进行替换,此主题样式会决定了应用的系统样式。
2,打开FlutterActivity
在AndroidManifest.xml中注册FlutterActivity后,然后我们可以在任何地方启动这个FlutterActivity,如下所示。
myButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {startActivity(FlutterActivity.createDefaultIntent(MainActivity.this));}
});
运行上面的代码,发现并不会跳转到Flutter页面,因为我们并没有提供跳转的地址。下面的示例将演示如何使用自定义路由跳转到Flutter模块页面中,如下所示。
myButton.addOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {startActivity(FlutterActivity.withNewEngine().initialRoute("/my_route").build(currentActivity));}
});
其中,my_route为Flutter模块的初始路由,关于Flutter的路由知识,可以看下面的文章:Flutter开发之路由与导航
我们使用withNewEngine()工厂方法配置,创建一个的FlutterEngine实例。当运行上面的代码时,应用就会由原生页面跳转到Flutter模块页面。
3,使用带有缓存的FlutterEngine
每个FlutterActivity在默认情况下都会创建自己的FlutterEngine,并且每个FlutterEngine在启动时都需要有一定的预热时间。这意味着在原生页面跳转到Flutter模块页面之前会一定的时间延迟。为了尽量减少这个延迟,你可以在启动Flutter页面之前先预热的FlutterEngine。即在应用程序中运行过程中找一个合理的时间实例化一个FlutterEngine,如在Application中进行初始化,如下所示。
public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();flutterEngine = new FlutterEngine(this);flutterEngine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault());FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);}
}
其中,FlutterEngineCache的ID可以是任意的字符串,使用时请确保传递给任何使用缓存的FlutterEngine的FlutterFragment或FlutterActivity使用的是相同的ID。完成上面的自定义Application后,我们还需要在原生Android工程的AndroidManifest.xml中使用自定义的Application,如下所示。
<applicationandroid:name="MyApplication"android:theme="@style/AppTheme">
</application>
下面我们来看一下如何在FlutterActivity页面中使用缓存的FlutterEngine,现在使用FlutterActivity跳转到Flutter模块时需要使用上面的ID,如下所示。
myButton.addOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {startActivity(FlutterActivity.withCachedEngine("my_engine_id").build(currentActivity));}
});
可以发现,在使用withCachedEngine()工厂方法后,打开Flutter模块的延迟时间大大降低了。
4,使用缓存引擎的初始路由
当使用带有FlutterEngine配置的FlutterActivity或者FlutterFragment时,会有初始路由的概念,我们可以在代码中添加跳转到Flutter模块的初始路由。然而,当我们使用带有缓存的FlutterEngine时,FlutterActivity和FlutterFragment并没有提供初始路由的概念。如果开发人员希望使用带有缓存的FlutterEngine时也能自定义初始路由,那么可以在执行Dart入口点之前配置他们的缓存FlutterEngine以使用自定义初始路由,如下所示。
public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();flutterEngine = new FlutterEngine(this);flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");flutterEngine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault());FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);}
}
带有背景样式的跳转
如果要修改跳转的样式,那么可以在原生Android端自定义一个主题样式呈现一个半透明的背景。首先打开res/values/styles.xml文件,然后添加自定义的主题,如下所示。
<style name="MyTheme" parent="@style/AppTheme"><item name="android:windowIsTranslucent">true</item></style>
然后,将FlutterActivity的主题改为我们自定义的主题,如下所示。
<activityandroid:name="io.flutter.embedding.android.FlutterActivity"android:theme="@style/MyTheme"android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"android:hardwareAccelerated="true"android:windowSoftInputMode="adjustResize"/>
然后,就可以使用透明背景启动FlutterActivity,如下所示。
// Using a new FlutterEngine.
startActivity(FlutterActivity.withNewEngine().backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent).build(context)
);// Using a cached FlutterEngine.
startActivity(FlutterActivity.withCachedEngine("my_engine_id").backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent).build(context)
);
添加FlutterFragment
在Android开发中,除了Activity之外,还可以使用Fragment来加载页面,Fragment比Activity的粒度更小,有碎片化的意思。如果有碎片化加载的场景,那么可以使用FlutterFragment 。FlutterFragment允许开发者控制以下操作:
- 初始化Flutter的路由;
- Dart的初始页面的飞入样式;
- 设置不透明和半透明背景;
- FlutterFragment是否可以控制Activity;
- FlutterEngine或者带有缓存的FlutterEngine是否能使用;
1,将FlutterFragment 添加到Activity
使用FlutterFragment要做的第一件事就是将其添加到宿主Activity中。为了给宿主Activity添加一个FlutterFragment,需要在Activity的onCreate()中实例化并附加一个FlutterFragment的实例,这和原生Android的Fragment使用方法是一样的,代码如下:
public class MyActivity extends FragmentActivity {private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";private FlutterFragment flutterFragment;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.my_activity_layout);FragmentManager fragmentManager = getSupportFragmentManager();flutterFragment = (FlutterFragment) fragmentManager.findFragmentByTag(TAG_FLUTTER_FRAGMENT);if (flutterFragment == null) {flutterFragment = FlutterFragment.createDefault();fragmentManager.beginTransaction().add( R.id.fragment_container, flutterFragment, TAG_FLUTTER_FRAGMENT ).commit();}}
}
其中,代码中用到的原生Fragment的布局代码如下所示。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><FrameLayoutandroid:id="@+id/fragment_container"android:layout_width="match_parent"android:layout_height="match_parent" /></androidx.constraintlayout.widget.ConstraintLayout>
然后,将原生Android的启动页面改为我们的MyActivity即可。除此之外,我们还可以借助FlutterFragment来获取原生代码的生命周期,并作出相关的逻辑操作,如下所示。
public class MyActivity extends FragmentActivity {@Overridepublic void onPostResume() {super.onPostResume();flutterFragment.onPostResume();}@Overrideprotected void onNewIntent(@NonNull Intent intent) {flutterFragment.onNewIntent(intent);}@Overridepublic void onBackPressed() {flutterFragment.onBackPressed();}@Overridepublic void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) {flutterFragment.onRequestPermissionsResult(requestCode,permissions,grantResults);}@Overridepublic void onUserLeaveHint() {flutterFragment.onUserLeaveHint();}@Overridepublic void onTrimMemory(int level) {super.onTrimMemory(level);flutterFragment.onTrimMemory(level);}
}
不过,上面的示例启动时使用了一个新的FlutterEngine,因此启动后会需要一定的初始化时间,导致应用启动后会有一个空白的UI,直到FlutterEngine初始化成功后Flutter模块的首页渲染完成。对于这种现象,我们同样可以在提前初始化FlutterEngine,即在应用程序的Application中初始化FlutterFragment,如下所示。
public class MyApplication extends Application {FlutterEngine flutterEngine=null;@Overridepublic void onCreate() {super.onCreate();flutterEngine = new FlutterEngine(this);flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);}
}
在上面的代码中,通过设置导航通道的初始路由,然后关联的FlutterEngine在初始执行runApp() ,在初始执行runApp()后再改变导航通道的初始路由属性是没有效果的。然后,我们修改MyFlutterFragmentActivity类的代码,并使用FlutterFragment.withNewEngine()使用缓存的FlutterEngine,如下所示。
public class MyFlutterFragmentActivity extends FragmentActivity {private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";private FlutterFragment flutterFragment = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.flutter_fragment_activity);FragmentManager fragmentManager = getSupportFragmentManager();if (flutterFragment == null) {flutterFragment=FlutterFragment.withNewEngine().initialRoute("/").build();fragmentManager.beginTransaction().add(R.id.fragment_container, flutterFragment,TAG_FLUTTER_FRAGMENT).commit();}}
}
控制FlutterFragment的渲染模式
FlutterFragment默认使用SurfaceView来渲染它的Flutter内容,除此之外,还可以使用TextureView来渲染界面,不过SurfaceView的性能比TextureView好得多。但是,SurfaceView不能交错在Android视图层次结构中使用。此外,在Android N之前的Android版本中,SurfaceViews不能动画化,因为它们的布局和渲染不能与其他视图层次结构同步,此时,你需要使用TextureView而不是SurfaceView,使用 TextureView来渲染FlutterFragment的代码如下。
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine().renderMode(FlutterView.RenderMode.texture).build();// With a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id").renderMode(FlutterView.RenderMode.texture).build();
如果要给跳转添加一个转场的透明效果,要启用FlutterFragment的透明属性,可以使用下面的配置,如下所示。
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine().transparencyMode(TransparencyMode.transparent).build();// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id").transparencyMode(TransparencyMode.transparent).build();
FlutterFragment 与Activity
有时候,一些应用使用Fragment来作为Flutter页面的承载对象时,状态栏、导航栏和屏幕方向仍然使用的是Activity,Fragment只是作为Activity的一部分。在这些应用程序中,用一个Fragment是合理的,如下图所示。
在其他应用程序中,Fragment仅仅作为UI的一部分,此时一个FlutterFragment可能被用来实现一个抽屉的内部,一个视频播放器,或一个单一的卡片。在这些情况下,FlutterFragment不需要全屏线上,因为在同一个屏幕中还有其他UI片段,如下图所示。
FlutterFragment提供了一个概念,用来实现FlutterFragment是否能够控制它的宿主Activity。为了防止一个FlutterFragment将它的Activity暴露给Flutter插件,也为了防止Flutter控制Activity的系统UI,FlutterFragment提供了一个shouldAttachEngineToActivity()方法,如下所示。
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine().shouldAttachEngineToActivity(false).build();// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id").shouldAttachEngineToActivity(false).build();
原生iOS集成Flutter
创建Flutter模块
为了将 Flutter 集成到原生iOS应用里,第一步要创建一个 Flutter module,创建 Flutter module的命令如下所示。
cd some/path/
flutter create --template module my_flutter
执行完上面的命令后,会在some/path/my_flutter/ 目录下创建一个Flutter module库。在这个目录中,你可以像在其它 Flutter 项目中一样,执行 flutter 命令,比如 flutter run --debug 或者 flutter build ios。打开 my_flutter 模块,可以发现,目录结构和普通 的Flutter 应用的目录别无二至,如下所示。
my_flutter/
├── .ios/
│ ├── Runner.xcworkspace
│ └── Flutter/podhelper.rb
├── lib/
│ └── main.dart
├── test/
└── pubspec.yaml
默认情况下,my_flutter的Android工程和iOS工程是隐藏的,我们可以通过显示隐藏的项目来看到Android工程和iOS工程。
集成到已有iOS应用
在原生iOS开发中,有两种方式可以将 Flutter 集成到你的既有应用中。
1, 使用 CocoaPods 依赖管理和已安装的 Flutter SDK 。(推荐)
2,把 Flutter engine 、Dart 代码和所有 Flutter plugin 编译成 framework,然后用 Xcode 手动集成到你的应用中,并更新编译设置。
1, 使用 CocoaPods 和 Flutter SDK 集成
使用此方法集成Flutter,需要在本地安装了 Flutter SDK。然后,只需要在 Xcode 中编译应用,就可以自动运行脚本来集成Dart 代码和 plugin。这个方法允许你使用 Flutter module 中的最新代码快速迭代开发,而无需在 Xcode 以外执行额外的命令。
现在假如又一个原生iOS工程,并且 Flutter module 和这个iOS工程是处在相邻目录的,如下所示。
some/path/
├── my_flutter/
│ └── .ios/
│ └── Flutter/
│ └── podhelper.rb
└── MyApp/└── Podfile
1,如果你的应用(MyApp)还没有 Podfile,可以根据 CocoaPods 使用指南 来在项目中添加 Podfile。然后,在 Podfile
中添加下面代码:
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
2,每个需要集成 Flutter 的 [Podfile target][],执行 install_all_flutter_pods(flutter_application_path)
,如下所示。
target 'MyApp' doinstall_all_flutter_pods(flutter_application_path)
end
3,最后,在MyApp原生工程下运行 pod install命令拉取原生工程需要的插件。
pod install
如果没有任何错误,界面如下图。
在上面的Podfile文件中, podhelper.rb 脚本会把你的 plugins, Flutter.framework,和 App.framework 集成到你的原生iOS项目中。同时,你应用的 Debug 和 Release 编译配置,将会集成相对应的 Debug 或 Release 的 编译产物。可以增加一个 Profile 编译配置用于在 profile 模式下测试应用。然后,在 Xcode 中打开 MyApp.xcworkspace ,可以使用 【⌘B 】快捷键编译项目,并运行项目即可。
使用frameworks集成
除了上面的方法,你也可以创建一个 frameworks,手动修改既有 Xcode 项目,将他们集成进去。但是每当你在 Flutter module 中改变了代码,都必须运行 flutter build ios-framework来编译framework。下面的示例假设你想在 some/path/MyApp/Flutter/ 目录下创建 frameworks。
flutter build ios-framework --output=some/path/MyApp/Flutter/
此时的文件目录如下所示。
some/path/MyApp/
└── Flutter/├── Debug/│ ├── Flutter.framework│ ├── App.framework│ ├── FlutterPluginRegistrant.framework (only if you have plugins with iOS platform code)│ └── example_plugin.framework (each plugin is a separate framework)├── Profile/│ ├── Flutter.framework│ ├── App.framework│ ├── FlutterPluginRegistrant.framework│ └── example_plugin.framework└── Release/├── Flutter.framework├── App.framework├── FlutterPluginRegistrant.framework└── example_plugin.framework
然后,使用 Xcode 打开原生iOS工程,并将生成的 frameworks 集成到既有iOS应用中。例如,你可以在 some/path/MyApp/Flutter/Release/ 目录拖拽 frameworks 到你的应用 target 编译设置的 General > Frameworks, Libraries, and Embedded Content 下,然后在 Embed 下拉列表中选择 “Embed & Sign”。
1, 链接到框架
当然,你也可以将框架从 Finder 的 some/path/MyApp/Flutter/Release/ 拖到你的目标项目中,然后点击 build settings > Build Phases > Link Binary With Libraries。然后,在 target 的编译设置中的 Framework Search Paths (FRAMEWORK_SEARCH_PATHS) 增加 $(PROJECT_DIR)/Flutter/Release/,如下图所示。
2,内嵌框架
生成的动态framework框架必须嵌入你的应用才能在运行时被加载。需要说明的是插件会帮助你生成 静态或动态框架。静态框架应该直接链接而不是嵌入,如果你在应用中嵌入了静态框架,你的应用将不能发布到 App Store 并且会得到一个 Found an unexpected Mach-O header code 的 archive 错误。
你可以从应用框架组中拖拽框架(除了 FlutterPluginRegistrant 以及其他的静态框架)到你的目标 ‘ build settings > Build Phases > Embed Frameworks,然后从下拉菜单中选择 “Embed & Sign”,如下图所示。
3,使用 CocoaPods 在 Xcode 和 Flutter 框架中内嵌应用
除了使用Flutter.framework方式外,你还可以加入一个参数 --cocoapods ,然后将 Flutter 框架作为一个 CocoaPods 的 podspec 文件分发。这将会生成一个 Flutter.podspec 文件而不再生成 Flutter.framework 引擎文件,命令如下。
flutter build ios-framework --cocoapods --output=some/path/MyApp/Flutter/
执行命令后,Flutter模块的目录如下图所示。
some/path/MyApp/
└── Flutter/├── Debug/│ ├── Flutter.podspec│ ├── App.framework│ ├── FlutterPluginRegistrant.framework│ └── example_plugin.framework (each plugin with iOS platform code is a separate framework)├── Profile/│ ├── Flutter.podspec│ ├── App.framework│ ├── FlutterPluginRegistrant.framework│ └── example_plugin.framework└── Release/├── Flutter.podspec├── App.framework├── FlutterPluginRegistrant.framework└── example_plugin.framework
然后,在iOS应用程序使用CocoaPods添加Flutter以来文件即可,如下所示。
pod 'Flutter', :podspec => 'some/path/MyApp/Flutter/[build mode]/Flutter.podspec'
添加一个Flutter页面
FlutterEngine 和 FlutterViewController
为了在原生 iOS 应用中展示 Flutter 页面,需要使用到FlutterEngine 和 FlutterViewController。其中,FlutterEngine 充当 Dart VM 和 Flutter 运行时环境; FlutterViewController 依附于 FlutterEngine,给 Flutter 传递 UIKit 的输入事件,并展示被 FlutterEngine 渲染的每一帧画面。
1,创建一个 FlutterEngine
创建 FlutterEngine 的时机由您自己决定。作为示例,我们将在应用启动的 app delegate 中创建一个 FlutterEngine,并作为属性暴露给外界。首先,在在 AppDelegate.h文件中添加如下代码。
@import UIKit;
@import Flutter;@interface AppDelegate : FlutterAppDelegate // More on the FlutterAppDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
然后,在 AppDelegate.m文件中添加如下代码。
// Used to connect plugins (only if you have plugins with iOS platform code).
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>#import "AppDelegate.h"@implementation AppDelegate- (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];// Runs the default Dart entrypoint with a default Flutter route.[self.flutterEngine run];// Used to connect plugins (only if you have plugins with iOS platform code).[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];return [super application:application didFinishLaunchingWithOptions:launchOptions];
}@end
需要说明的是,GeneratedPluginRegistrant只有在需要支持的插件才能使用。然后运行项目,结果报了一个framework not found FlutterPluginRegistrant
错误。
ld: warning: directory not found for option '-F/Users/bilibili/Library/Developer/Xcode/DerivedData/iOSFlutterHybird-advitqdrflrsxldrjkqcsvdzxbop/Build/Products/Debug-iphonesimulator/FlutterPluginRegistrant'
ld: framework not found FlutterPluginRegistrant
clang: error: linker command failed with exit code 1 (use -v to see invocation)
对于这个错误,需要打开项目编译配置,修改Bitcode。默认情况下,Flutter是不支持Bitcode的,Bitcode是一种iOS编译程序的中间代码,在原生iOS工程中集成Flutter需要禁用Bitcode,如下图所示。
2,使用 FlutterEngine 展示 FlutterViewController
在下面的例子中,展示了一个普通的 ViewController,当点击页面中的UIButton时就会跳转到 FlutterViewController 的 ,这个 FlutterViewController 使用在 AppDelegate 中创建的 Flutter 引擎 (FlutterEngine)。
@import Flutter;
#import "AppDelegate.h"
#import "ViewController.h"@implementation ViewController
- (void)viewDidLoad {[super viewDidLoad];// Make a button to call the showFlutter function when pressed.UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];[button addTarget:selfaction:@selector(showFlutter)forControlEvents:UIControlEventTouchUpInside];[button setTitle:@"Show Flutter!" forState:UIControlStateNormal];button.backgroundColor = UIColor.blueColor;button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);[self.view addSubview:button];
}- (void)showFlutter {FlutterEngine *flutterEngine =((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;FlutterViewController *flutterViewController =[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
运行上面的代码,如果出现“symbol(s) not found for architecture x86_64”的错误,可以使用下面的步骤进行解决。使用Xcode打开项目,然后依次选择TARGETS->Build Phases,然后找到Compile Sources 并点击“+”, 在搜索框输入APPDelegate 找到他的.m文件。
3,使用隐式 FlutterEngine 创建 FlutterViewController
我们可以让 FlutterViewController 隐式的创建 FlutterEngine,而不用提前初始化一个FlutterEngine。不过不建议这样做,因为按需创建FlutterEngine 的话,在 FlutterViewController 被 present 出来之后,第一帧图像渲染完之前,将会有明显的延迟。不过,当 Flutter 页面很少被展示时,可以使用此方式。
为了不使用已经存在的 FlutterEngine 来展现 FlutterViewController,省略 FlutterEngine 的创建步骤,并且在创建 FlutterViewController 时,去掉 FlutterEngine 的引用。
// Existing code omitted.
// 省略已经存在的代码
- (void)showFlutter {FlutterViewController *flutterViewController =[[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
使用 FlutterAppDelegate
FlutterAppDelegate 具备如下功能:
- 传递应用的回调,例如 openURL 到 Flutter 的插件 —— local_auth。
- 传递状态栏点击(这只能在 AppDelegate 中检测)到 Flutter 的点击置顶行为。
我们推荐应用的UIApplicationDelegate 继承 FlutterAppDelegate,但不是必须的,如果你的 App Delegate 不能直接继承 FlutterAppDelegate,那么让你的 App Delegate 实现 FlutterAppLifeCycleProvider 协议,来确保 Flutter plugins 接收到必要的回调。否则,依赖这些事件的 plugins 将会有无法预估的行为。
@import Flutter;
@import UIKit;
@import FlutterPluginRegistrant;@interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
然后,在具体实现中,将App Delegate委托给 FlutterPluginAppLifeCycleDelegate,如下所示。
@interface AppDelegate ()
@property (nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
@end@implementation AppDelegate- (instancetype)init {if (self = [super init]) {_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];}return self;
}- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id>*))launchOptions {self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];[self.flutterEngine runWithEntrypoint:nil];[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;if ([viewController isKindOfClass:[FlutterViewController class]]) {return (FlutterViewController*)viewController;}return nil;
}- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {[super touchesBegan:touches withEvent:event];// Pass status bar taps to key window Flutter rootViewController.if (self.rootFlutterViewController != nil) {[self.rootFlutterViewController handleStatusBarTouches:event];}
}- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {[_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {[_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {[_lifeCycleDelegate application:applicationdidReceiveRemoteNotification:userInfofetchCompletionHandler:completionHandler];
}- (BOOL)application:(UIApplication*)applicationopenURL:(NSURL*)urloptions:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {return [_lifeCycleDelegate application:application openURL:url options:options];
}- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {return [_lifeCycleDelegate application:application handleOpenURL:url];
}- (BOOL)application:(UIApplication*)applicationopenURL:(NSURL*)urlsourceApplication:(NSString*)sourceApplicationannotation:(id)annotation {return [_lifeCycleDelegate application:applicationopenURL:urlsourceApplication:sourceApplicationannotation:annotation];
}- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItemcompletionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {[_lifeCycleDelegate application:applicationperformActionForShortcutItem:shortcutItemcompletionHandler:completionHandler];
}- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifiercompletionHandler:(nonnull void (^)(void))completionHandler {[_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifiercompletionHandler:completionHandler];
}- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {[_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {[_lifeCycleDelegate addDelegate:delegate];
}
@end
启动选项
上面例子使用默认配置来启动 Flutter,为了定制化你的 Flutter 运行时,我们可以指定 Dart 入口、库和路由。
1,指定Dart 入口
在 FlutterEngine 上调用 run()函数,默认将会调用你的 lib/main.dart 文件里的 main() 函数。不过,我们可以使用入口方法 runWithEntrypoint()来指定一个Dart 入口,并且,使用 main() 以外的 Dart 入口函数,必须使用下面的注解,防止被 tree-shaken 优化掉,而没有进行编译。如下所示。
@pragma('vm:entry-point')void myOtherEntrypoint() { ... };
2,指定Dart 库
同时,Flutter允许开发者在指定 Dart 函数时指定特定文件。例如使用 lib/other_file.dart 文件的 myOtherEntrypoint() 函数取代 lib/main.dart 的 main() 函数,如下所示。
[flutterEngine runWithEntrypoint:@"myOtherEntrypoint" libraryURI:@"other_file.dart"];
3,指定Dart 路由
当然,当构建Flutter Engine 时,还可以为你的 Flutter 应用设置一个初始路由,如下所示。
FlutterEngine *flutterEngine =[[FlutterEngine alloc] initWithName:@"my flutter engine"];
[[flutterEngine navigationChannel] invokeMethod:@"setInitialRoute"arguments:@"/onboarding"];
[flutterEngine run];
相关文章:
flutter 专题 六十四 在原生项目中集成Flutter
概述 使用Flutter从零开始开发App是一件轻松惬意的事情,但对于一些成熟的产品来说,完全摒弃原有App的历史沉淀,全面转向Flutter是不现实的。因此使用Flutter去统一Android、iOS技术栈,把它作为已有原生App的扩展能力,…...
AI生成Flutter UI代码实践(一)
之前的杂谈中有提到目前的一些主流AI编程工具,比如Cursor,Copilot,Trea等。因为我是Android 开发,日常使用Android Studio,所以日常使用最多的还是Copilot,毕竟Github月月送我会员,白嫖还是挺香…...
spring boot中@Validated
在 Spring Boot 中,Validated 是用于触发参数校验的注解,通常与 JSR-303/JSR-380(Bean Validation)提供的校验注解一起使用。以下是常见的校验注解及其用法: 1. 基本校验注解 这些注解可以直接用于字段…...
VBA代码解决方案第二十四讲:EXCEL中,如何删除重复数据行
《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程,目前已经是第三版修订了。这套教程定位于入门后的提高,在学习这套教程过程中,侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…...
SpringBoot+EasyExcel+Mybatis+H2实现导入
文章目录 SpringBootEasyExcelMybatisH2实现导入1.准备工作1.1 依赖管理1.2 配置信息properties1.3 H2数据库1.4 Spring Boot 基础概念1.5 Mybatis核心概念 1.6 EasyExcel核心概念 2.生成Excel数据工具类-随机字符串编写生成Excel的java文件 3.导入功能并且存入数据库3.1 返回结…...
算法四 习题 1.3
数组实现栈 #include <iostream> #include <vector> #include <stdexcept> using namespace std;class MyStack { private:vector<int> data; // 用于存储栈元素的数组public:// 构造函数MyStack() {}// 入栈操作void push(int val) {data.push_back…...
el-tabs与table样式冲突导致高度失效问题解决(vue2+elementui)
背景 正常的el-table能根据父容器自动计算剩余高度,并会在列表中判断自适应去放出滚动条。而el-tabs本身就是自适应el-tab-pane内容的高度来进行自适应调节,这样就会导致el-table计算不了当前剩余的高度,所以当el-tabs里面包含el-table时&am…...
Access开发:轻松一键将 Access 全库表格导出为 Excel
hi,大家好呀! 在日常工作中,Access 常常是我们忠实的数据管家,默默守护着项目信息、客户列表或是库存记录。它结构清晰,录入便捷,对于许多中小型应用场景来说,无疑是个得力助手。然而ÿ…...
合并多个Excel文件到一个文件,并保留格式
合并多个Excel文件到一个文件,并保留格式 需求介绍第一步:创建目标文件第二步:创建任务列表第三步:合并文件第四步:处理合并后的文件之调用程序打开并保存一次之前生成的Excel文件第五步:处理合并后的文件之…...
使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第十讲)
这一期我们讲解demo中登录、ok按键的回调函数以及界面的美化,以下是上期界面的图片如图所示: 首先点击界面在右侧的工具栏中调配颜色渐变色,具体设置如下图所示: 然后是关于界面内框也就是容器的美化,具体如下图所示…...
论文笔记(八十二)Transformers without Normalization
Transformers without Normalization 文章概括Abstract1 引言2 背景:归一化层3 归一化层做什么?4 动态 Tanh (Dynamic Tanh (DyT))5 实验6 分析6.1 DyT \text{DyT} DyT 的效率6.2 tanh \text{tanh} tanh 和 α α α 的消融实验…...
Mysql之数据库基础
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习Mysql的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...
shell(5)
位置参数变量 1.介绍 当我们执行一个shell脚本时,如果希望获取到命令行的参数信息,就可以使用到位置参数变量. 比如:./myshell.sh100 200,这就是一个执行shell的命令行,可以在myshell脚本中获取到参数信息. 2.基本语法 $n(功能描述:n为数字,$0代表命令…...
VARIAN安捷伦真空泵维修清洁保养操作SOP换油操作流程内部转子图文并茂内部培训手侧
VARIAN安捷伦真空泵维修清洁保养操作SOP换油操作流程内部转子图文并茂内部培训手侧...
动画震动效果
项目场景: 提示:这里简述项目相关背景: 在有的相关目中特别是在C端一般都要求做的炫酷一些,这就需要一些简易的动画效果,这里就弄了一个简易的震动的效果如下视频所示 让图标一大一小的震动视频 分析: 提…...
DB-GPT V0.7.1 版本更新:支持多模态模型、支持 Qwen3 系列,GLM4 系列模型 、支持Oracle数据库等
V0.7.1版本主要新增、增强了以下核心特性 🍀DB-GPT支持多模态模型。 🍀DB-GPT支持 Qwen3 系列,GLM4 系列模型。 🍀 MCP支持 SSE 权限认证和 SSL/TLS 安全通信。 🍀 支持Oracle数据库。 🍀 支持 Infini…...
C++23 std::invoke_r:调用可调用 (Callable) 对象 (P2136R3)
文章目录 引言背景知识回顾可调用对象C17的std::invoke std::invoke_r的诞生提案背景std::invoke_r的定义参数和返回值异常说明 std::invoke_r的使用场景指定返回类型丢弃返回值 std::invoke_r与std::invoke的对比功能差异使用场景差异 结论 引言 在C的发展历程中,…...
pymysql
参数(会导致SQL注入) import pymysql# 创建数据库连接 conn pymysql.connect(user "root",password "root",host "127.0.0.1",port 3306,database "test" )# 创建游标对象 cur conn.cursor(cursorpymysql.…...
基于Spring Boot + Vue 项目中引入deepseek方法
准备工作 在开始调用 DeepSeek API 之前,你需要完成以下准备工作: 1.访问 DeepSeek 官网,注册一个账号。 2.获取 API 密钥:登录 DeepSeek 平台,进入 API 管理 页面。创建一个新的 API 密钥(API Key&#x…...
Spring Boot集成Kafka并使用多个死信队列的完整示例
以下是Spring Boot集成Kafka并使用多个死信队列的完整示例,包含代码和配置说明。 1. 添加依赖 (pom.xml) <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId&…...
全面了解CSS语法 ! ! !
CSS(层叠样式表)是网页设计的灵魂之一,它赋予了网页活力与美感。无论是为一个简单的个人博客增添色彩,还是为复杂的企业网站设计布局,CSS都是不可或缺的工具。那么,CSS语法到底是什么样的呢?它背…...
Springboot使用ThreadLocal提供线程局部变量,传递登录用户名
文章目录 概述使用创建ThreadLocalUtil工具类在登录拦截器中使用ThreadLocal存储登录用户名在/userInfo接口中获取登录用户名 注意事项参考视频 概述 使用 创建ThreadLocalUtil工具类 utils/ThreadLocalUtil.java package org.example.utils;/*** ThreadLocal 工具类*/ Supp…...
排序算法——选择排序
一、介绍 「排序算法sortingalgorithm」用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用,因为有序数据通常能够被更有效地查找、分析和处理。 如图所示,排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求…...
AlphaFold蛋白质结构数据库介绍
AlphaFold Protein Structure Database (AlphaFold DB) 是 DeepMind + EMBL-EBI 合作开发的公开蛋白质结构预测数据库,是利用 AlphaFold2/AlphaFold3 AI模型 预测的全基因组级蛋白质三维结构库。 网址: https://alphafold.ebi.ac.uk 项目内容主办单位DeepMind + EMBL-EBI上线…...
Roboflow标注数据集
使用Roboflow进行标注 关键点标注目标检测标注图像分类标注分割标注 Roboflow是一款易于使用的在线 图像标注。 关键点标注 每个图像的标注包括: 1、边界框坐标(每个物品应该有一个边界框,用*[x1,y1,x2,y2]*格式即左上角和右下角点描述&…...
大厂经验:第三方包Paramunittest参数化 VS Unittest内置参数化文本管理器subtest
大厂经验:第三方包Paramunittest参数化 VS Unittest内置参数化文本管理器subtest 代码解析 Paramunittest 核心逻辑 paramunittest.parametrized((Testerr, test, Invalid Login or Password., test_login_admin is passed),(Sam, test, Invalid Login or Passwo…...
[特殊字符]适合五四青年节的SVG模版[特殊字符]
宝藏模版 往期推荐(点击阅读): 趣味效果|高大上|可爱风|年终总结I|年终总结II|循环特效|情人节I|情人节II|情人节IIII|妇女节I&…...
当插入排序遇上“凌波微步“——希尔排序的奇幻漂流
文章目录 一、排序江湖的隐藏高手二、分而治之的魔法1. 核心思想拆解2. 动态演示(脑补版) 三、C语言实现大揭秘代码要点解析: 四、性能分析与实战技巧1. 时间复杂度迷思2. 实测性能对比 五、为什么说它永不过时?六、进阶思考题 一…...
【Hot 100】 148. 排序链表
目录 引言十大排序算法1. 冒泡排序 (Bubble Sort)2. 选择排序 (Selection Sort)3. 插入排序 (Insertion Sort)4. 希尔排序 (Shell Sort)简单代码说明关键特点 5. 归并排序 (Merge Sort)6. 快速排序 (Quick Sort)7. 堆排序 (Heap Sort)8. 计数排序 (Counting Sort)9. 桶排序 (Bu…...
大连理工大学选修课——机器学习笔记(9):线性判别式与逻辑回归
线性判别式与逻辑回归 概述 判别式方法 产生式模型需要计算输入、输出的联合概率 需要知道样本的概率分布,定义似然密度的隐式参数也称为基于似然的分类 判别式模型直接构造判别式 g i ( x ∣ θ i ) g_i(x|\theta_i) gi(x∣θi),显式定义判别式…...
[特殊字符] 开发工作高内存占用场景下,Windows 内存压缩机制是否应该启用?实测分析与优化建议
在日常开发中,我们往往需要同时运行多个高占用内存的工具,例如: IntelliJ IDEA VMware 虚拟机 多个 Java 后端程序 这些应用程序非常“吃内存”,轻松就能把 16GB、甚至 24GB 的物理内存用满。那么,Windows 的“内存…...
相机的基础架构
📷 相机相关基础架构学习路径 一、了解手机相机系统架构 Android Camera HAL(如果你是做 Android 平台) 学习 Camera HAL3 架构(基于 camera_device_t, camera3_device_ops 接口) 熟悉 CameraService → CameraProvid…...
C# 类成员的访问:内部与外部
在 C# 编程中,了解如何从类的内部和外部访问成员是非常重要的。本文将详细介绍这两种访问方式,并通过示例代码展示其具体应用。 从类的内部访问成员 类的成员可以在类的内部自由地互相访问,即使这些成员被声明为private。在类的方法中&…...
DAPO:对GRPO的几点改进
DAPO:对GRPO的几点改进 TL; DR:对 GRPO 的几处细节进行了优化,包括去除 KL 约束、解耦 ppo-clip 的上下界,上界设置更高以鼓励探索、超长回答过滤、token level 损失计算等。相比于原始 GRPO,在 AIME24 上提升非常显著…...
从零构建 MCP Server 与 Client:打造你的第一个 AI 工具集成应用
目录 🚀 从零构建 MCP Server 与 Client:打造你的第一个 AI 工具集成应用 🧱 1. 准备工作 🛠️ 2. 构建 MCP Server(服务端) 2.1 初始化服务器 🧩 3. 添加自定义工具(Tools&…...
2025.4.27 Vue.js 基础学习笔记
一、Vue.js 简介 Vue.js(简称 Vue)是一个用于构建用户界面的渐进式 JavaScript 框架。它具有以下特点: 轻量级 :核心库体积小,性能优秀,不占用过多资源,加载速度快,适合各种规模的应…...
基于用户场景的汽车行驶工况构建:数据驱动下的能耗优化革命
行业现状:标准工况与用户场景的割裂 全球汽车行业普遍采用WLTC工况进行能耗测试,但其与真实道路场景差异显著。据研究,WLTC工况下车辆能耗数据比实际道路低10%-30%,导致用户对续航虚标投诉激增(数据来源:东…...
IoTDB集群部署中的网络、存储与负载配置优化
一、引言 在现代计算机系统和应用程序中,网络I/O性能是决定整体系统表现的关键因素之一。特别是在IoTDB集群环境中,网络I/O的重要性尤为突出,特别是在处理大量测点数据、客户端请求以及集群内部通信时。本文将介绍IoTDB数据库集群部署过程中…...
Unity URPShader:实现和PS一样的色相/饱和度调整参数效果(修复)
目录 前言: 一、问题原因 二、算法修复 三、全代码 前言: 在之前的文章我已经实现了标题所述的内容功能:Unity URPShader:实现和PS一样的色相/饱和度调整参数效果-CSDN博客 但在偶然测试的时候,发现当采样的图片为…...
告别手动时代!物联网软件开发让万物自动互联
清晨,智能窗帘随着阳光自动拉开;运动时,手表精准记录着健康数据;回到家,室温早已调节至最舒适状态...这些场景的实现,都离不开物联网软件开发的技术支撑。在智能家居软件开发、智能穿戴软件开发、医疗器械软…...
Vue ui初始化项目并使用iview写一个菜单导航
winR 输入命令 vue ui浏览器会自动打开http://localhost:8000/ 找到创建 image.png 选择一个目录创建vue项目 image.png 点击再此创建新项目 image.png 我一般都是再已经有git仓库的目录进行项目创建,所以这个勾去掉 点击下一步 image.png 这里可以选择默认&#x…...
函数调用及Chain——SQL+GLM
Langchainchain数据库操作_langchain 操作数据库-CSDN博客 本文和基于上述链接 进一步。 初始化数据库&模型 # temperature0,此处仅需要SQL语句,不需要多样化返回。 from langchain.chains.sql_database.query import create_sql_query_chain from …...
数据科学与计算
Seaborn的介绍 Seaborn 是一个建立在 Matplotlib 基础之上的 Python 数据可视化库,专注于绘制各种统计图形,以便更轻松地呈现和理解数据。 Seaborn 的设计目标是简化统计数据可视化的过程,提供高级接口和美观的默认主题,使得用户…...
【AI提示词】二八法则专家
提示说明 精通二八法则(帕累托法则)的广泛应用,擅长将其应用于商业、管理、个人发展等领域,深入理解其在不同场景中的具体表现和实际意义。 提示词 # Role: 二八法则专家## Profile - language: 中文 - description: 精通二八法…...
PostgreSQL Patroni集群组件作用介绍:Patroni、etcd、HAProxy、Keepalived、Watchdog
1. Watchdog 简介 1.1 核心作用 • 主节点故障检测 Watchdog 会定时检测数据库主节点(或 Pgpool 主节点)的运行状态。 一旦主节点宕机,它会发起故障切换请求。 • 协调主备切换 多个 Pgpool 节点时,Watchdog 保证只有一个 Pg…...
【计算机视觉】图像分割:Segment Anything (SAM):通用图像分割的范式革命
Segment Anything:通用图像分割的范式革命 技术突破与架构创新核心设计理念关键技术组件 环境配置与快速开始硬件要求安装步骤基础使用示例 深度功能解析1. 多模态提示融合2. 全图分割生成3. 高分辨率处理 模型微调与定制1. 自定义数据集准备2. 微调训练配置 常见问…...
改进系列(10):基于SwinTransformer+CBAM+多尺度特征融合+FocalLoss改进:自动驾驶地面路况识别
目录 1.代码介绍 1. 主训练脚本train.py 2. 工具函数与模型定义utils.py 3. GUI界面应用infer_QT.py 2.自动驾驶地面路况识别 3.训练过程 4.推理 5.下载 代码已经封装好,对小白友好。 想要更换数据集,参考readme文件摆放好数据集即可,…...
大型连锁酒店集团数据湖应用示例
目录 一、应用前面临的严峻背景 二、数据湖的精细化构建过程 (一)全域数据整合规划 (二)高效的数据摄取与存储架构搭建 (三)完善的元数据管理体系建设 (四)强大的数据分析平台…...
element.scrollIntoView(options)
handleNextClick 函数详解 功能描述 该函数实现在一个表格中“跳转到下一行”的功能,并将目标行滚动至视图顶部。通常用于导航或高亮显示当前选中的数据行。 const handleNextClick () > {// 如果当前已经是最后一行,则不执行后续操作if (current…...
python查看指定的进程是否存在
import os class Paly_Install(object):"""项目根目录"""def get_path(self):self.basedir os.path.dirname(os.path.abspath(__file__))"""安装失败的txt文件"""def test_app(self):self.app["com.faceboo…...