<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是合理的,如下图所示。![](https://upload-images.jianshu.io/upload_images/24334488-c01909094f555d1b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
在其他应用程序中,Fragment仅仅作为UI的一部分,此时一个FlutterFragment可能被用来实现一个抽屉的内部,一个视频播放器,或一个单一的卡片。在这些情况下,FlutterFragment不需要全屏线上,因为在同一个屏幕中还有其他UI片段,如下图所示。
![](https://upload-images.jianshu.io/upload_images/24334488-daa931ab55526093.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
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
如果没有任何错误,界面如下图。
![](https://upload-images.jianshu.io/upload_images/24334488-f5f1b050075fc674.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
在上面的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/,如下图所示。
![](https://upload-images.jianshu.io/upload_images/24334488-0d2446da6020837c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
**2,内嵌框架**
生成的动态framework框架必须嵌入你的应用才能在运行时被加载。需要说明的是插件会帮助你生成 静态或动态框架。静态框架应该直接链接而不是嵌入,如果你在应用中嵌入了静态框架,你的应用将不能发布到 App Store 并且会得到一个 Found an unexpected Mach-O header code 的 archive 错误。
你可以从应用框架组中拖拽框架(除了 FlutterPluginRegistrant 以及其他的静态框架)到你的目标 ‘ build settings > Build Phases > Embed Frameworks,然后从下拉菜单中选择 “Embed & Sign”,如下图所示。
![](https://upload-images.jianshu.io/upload_images/24334488-78fcc9ba267ec452.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
**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,如下图所示。
![](https://upload-images.jianshu.io/upload_images/24334488-f2e3838d7aaee00c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
**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文件。
![](https://upload-images.jianshu.io/upload_images/24334488-95d70cdaae4589e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
**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*)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() { … };