flutter 专题 一百零三

前不久,谷歌官方正式发布了Flutter的首个发布预览版(Release Preview 1),这标志着谷歌进入了Flutter正式版(1.0)发布前的最后阶段,同时作为Google的重量级跨平台开发方案,此次更新也吸引了多数的移动开发者的关注。使用 Flutter从头开始写一个 App是一件非常轻松惬意的事情,但在原生APP中接入 Flutter会是什么效果呢,似乎并不是一件容易的事情,下面就讲解在iOS原生应用中如何接入Flutter。

开发环境
  • Flutter:0.5.7
  • Xcode 9.4.1
  • Flutter工程:flutter/examples/hello_world

项目实例

Flutter(engine) 基础库

首先,在你的项目里面拖入Flutter.framework,这个库是 Flutter的Engine库,承载了 Dart运行时和绘图引擎。Flutter.framework和命令行工具版本是一一对应的,如果你不知道从哪里找这个文件,可以直接在 Flutter源码项目里面进行一次 flutter run 命令,然后你就能在 /<project>/ios/Flutter/ 目录下面就能找到Flutter.framework了,然后直接将它拖进项目即可。

接下来需要把 Flutter的基础代码引入现有工程,有了基础的 Flutter ViewController才可以显示 Flutter视图。然后,只需要在你现有的 ViewController中 Push过去就可以。例如:

复制代码
 - (void)jumpToFlutter {
    FlutterViewController *viewController = [FlutterViewController new];
    [self.navigationController pushViewController:viewController animated:YES];
}

需要注意的是,在使用的时候还需要把 AppDelegate里面的生命周期事件传递给 Flutter。实现的思路如下:

直接让现有的 AppDelegate继承 FlutterAppDelegate即可,但这带来的负面影响是 root ViewController会被设置为Flutter ViewController。

改造 AppDelegate实现。例如:

复制代码
// AppDelegate 或者模块的 Delegate

@interface DemoFlutterBaseAppDelegate : NSObject <ModularApplicationDelegate, FlutterPluginRegistry>

/**
 FlutterBinaryMessenger
 this determines which view controller is the flutter view controller
 nomally, flutter view controller provides the binary messages
 @return root Flutter ViewController
 */
- (NSObject<FlutterBinaryMessenger> *)binaryMessenger;

/**
 FlutterTextureRegistry
 this determines which view controller is the flutter view controller
 nomally, flutter view controller provides the custom textures
 @return root Flutter ViewController
 */
- (NSObject<FlutterTextureRegistry> *)textures;

@end

然后在实现中添加Flutter messenger 和 texture。

复制代码
// Registrar 声明和实现

@interface DemoFlutterAppDelegateRegistrar : NSObject<FlutterPluginRegistrar>

@property(nonatomic, copy) NSString *pluginKey;
@property(nonatomic, strong) DemoFlutterBaseAppDelegate *appDelegate;

- (instancetype)initWithPlugin:(NSString*)pluginKey appDelegate:(DemoFlutterBaseAppDelegate*)delegate;

@end

@implementation DemoFlutterAppDelegateRegistrar
- (instancetype)initWithPlugin:(NSString*)pluginKey appDelegate:(DemoFlutterBaseAppDelegate*)appDelegate {
    self = [super init];
    NSAssert(self, @"Super init cannot be nil");
    _pluginKey = [pluginKey copy];
    _appDelegate = appDelegate;
    return self;
}


- (NSObject<FlutterBinaryMessenger>*)messenger {
    return [_appDelegate binaryMessenger];
}

- (NSObject<FlutterTextureRegistry>*)textures {
    return [_appDelegate textures];
}

- (void)publish:(NSObject*)value {
    _appDelegate.pluginPublications[_pluginKey] = value;
}

- (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
                      channel:(FlutterMethodChannel*)channel {
    [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        [delegate handleMethodCall:call result:result];
    }];
}

- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
    [_appDelegate.pluginDelegates addObject:delegate];
}

- (NSString*)lookupKeyForAsset:(NSString*)asset {
    return [FlutterDartProject lookupKeyForAsset:asset];
}

- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
    return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
}

@end


// AppDelegate实现

@interface DemoFlutterBaseAppDelegate ()

@property(nonatomic, strong) DemoFlutterBaseViewController *rootController;
@property(readonly, nonatomic) NSMutableArray* pluginDelegates;
@property(readonly, nonatomic) NSMutableDictionary* pluginPublications;

@end

@implementation DemoFlutterBaseAppDelegate

/*
 * ... 这里写转发各种声明周期事件给 plugin
 */
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    for (id<FlutterPlugin> plugin in _pluginDelegates) {
        if ([plugin respondsToSelector:_cmd]) {
            [plugin application:application didFinishLaunchingWithOptions:launchOptions];
        }
    }
    return YES;
}

#pragma mark - getters for flutter

// 返回 FlutterViewController实例
- (FlutterViewController *)rootController {
    // ...
}

- (NSObject<FlutterBinaryMessenger> *)binaryMessenger
{
    if ([self.rootController conformsToProtocol:@protocol(FlutterBinaryMessenger)]) {
        return (NSObject<FlutterBinaryMessenger> *)self.rootController;
    }
    return nil;
}

- (NSObject<FlutterTextureRegistry> *)textures
{
    if ([self.rootController conformsToProtocol:@protocol(FlutterTextureRegistry)]) {
        return (NSObject<FlutterTextureRegistry> *)self.rootController;
    }
    return nil;
}

- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
    NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey);
    self.pluginPublications[pluginKey] = [NSNull null];
    return [[DemoFlutterAppDelegateRegistrar alloc] initWithPlugin:pluginKey appDelegate:self];
}

- (BOOL)hasPlugin:(NSString*)pluginKey {
    return _pluginPublications[pluginKey] != nil;
}

- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
    return _pluginPublications[pluginKey];
}

@end

到此,Flutter的运行环境其实就准备好了,无论是 Hot Restart还是 AOT都可以支持。接下来我们实现 Debug Hot Restart,即Flutter中最重要特性之一:特重载。

首先在你的 Flutter代码目录下执行一遍 Flutter build bundle,这可以帮助我们打包出一个 Flutter Asset,然后把这个 flutter_assets 目录拖入项目。

对你的项目进行一次 build,确保能够得到一个 .app 文件。然后新建一个文件夹叫做 Payload,把 .app文件放入 Payload文件夹,然后压缩成 zip文件。然后执行如下的命令:

复制代码
flutter run --use-application-binary /path/to/Payload.zip

如有要打包到真机上运行,需要做如下的一些替换操作。

  1. 把 Flutter.framework替换成flutter/bin/cache/artifacts/engine/ios-release/Flutter.framework,因为上一步我们用的库其实是JIT Runtime。
  2. 在 Flutter代码项目下面执行 flutter build aot --release --target-platform ios --ios-arch armv7,arm64 然后我们可以在 build目录下拿到一个打包好的 App.framework,不过别忘记在里面放一个 Info.plist。并且把这个库拖到工程里面。
  3. 删除工程里面的 flutter_assets文件夹下的 isolate_snapshot_data、kernel_blob.bin、platform.dill、vm_snapshot_data这几个文件。
  4. 编译打包给真机运行。

其实 Flutter官方有支持现有 App 集成的计划,并且现在文档也有一部分介绍,但其实整体工具链还没支持上来,目前所支持的程度和上文的方法也大同小异,所以还需要更多的摸索。

相关推荐
友善的猴子6 小时前
Adobe Photoshop 2025 Mac中文 Ps图像编辑
macos·adobe·photoshop
伊织code7 小时前
macOS Chrome - 打开开发者工具,设置 Local storage
chrome·macos·设置·开发者工具·local storage
独隅8 小时前
在 macOS 上修改 最大文件描述符限制(Too many open files) 和 网络端口相关参数 需要调整系统级配置的详细步骤
网络·macos
louisgeek10 小时前
Flutter Future 和 Stream 的区别
flutter
追风林11 小时前
mac 解压 nsz 文件
macos
sunly_14 小时前
Flutter:图片在弹窗外部的UI布局
flutter·ui
getapi16 小时前
Flutter 强制横屏
前端·javascript·flutter
斑驳的岁月19 小时前
MacOs java环境配置+maven环境配置踩坑实录
java·macos·maven
爱学习的大牛1231 天前
flutter 获取通话记录和通讯录
开发语言·flutter