概述
应用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.8
targetCompatibility 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中进行注册,如下所示。
<activity
android: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() {
@Override
public void onClick(View v) {
startActivity(
FlutterActivity.createDefaultIntent(MainActivity.this)
);
}
});
运行下面的代码,发现并不会跳转到Flutter页面,因为咱们并没有提供跳转的地址。上面的示例将演示如何应用自定义路由跳转到Flutter模块页面中,如下所示。
myButton.addOnClickListener(new OnClickListener() {
@Override
public 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 {
@Override
public 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,如下所示。
<application
android:name="MyApplication"
android:theme="@style/AppTheme">
</application>
上面咱们来看一下如何在FlutterActivity页面中应用缓存的FlutterEngine,当初应用FlutterActivity跳转到Flutter模块时须要应用下面的ID,如下所示。
myButton.addOnClickListener(new OnClickListener() {
@Override
public 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 {
@Override
public 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的主题改为咱们自定义的主题,如下所示。
<activity
android: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;
@Override
protected 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">
<FrameLayout
android: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 {
@Override
public void onPostResume() {
super.onPostResume();
flutterFragment.onPostResume();
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
flutterFragment.onNewIntent(intent);
}
@Override
public void onBackPressed() {
flutterFragment.onBackPressed();
}
@Override
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults
) {
flutterFragment.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
);
}
@Override
public void onUserLeaveHint() {
flutterFragment.onUserLeaveHint();
}
@Override
public 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;
@Override
public 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;
@Override
protected 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' do
install_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 *)application
didFinishLaunchingWithOptions:(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:self
action:@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:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(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*)application
openURL:(NSURL*)url
sourceApplication:(NSString*)sourceApplication
annotation:(id)annotation {
return [_lifeCycleDelegate application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
}
- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
[_lifeCycleDelegate application:application
performActionForShortcutItem:shortcutItem
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
completionHandler:(nonnull void (^)(void))completionHandler {
[_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
completionHandler: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];
发表回复