Flutter-boot 你集成成功了吗?

4,037 阅读7分钟

flutter-boot

首先区分两个概念'flutter-boot''flutter_boost'都是alibba开源的flutter工具,但是是两个完全不同的东西。

FlutterBoost

新一代Flutter-Native混合解决方案。 FlutterBoost是一个Flutter插件,它可以轻松地为现有原生应用程序提供Flutter混合集成方案。FlutterBoost的理念是将Flutter像Webview那样来使用。在现有应用程序中同时管理Native页面和Flutter页面并非易事。 FlutterBoost帮你处理页面的映射和跳转,你只需关心页面的名字和参数即可(通常可以是URL)。

Flutter-boot

这是一个帮助你在已有原生应用的情况下,搭建flutter混合开发环境的工具。 我们提供了标准的混合工程结构,同时支持混合栈(一套原生和flutter之前页面通信和过渡的方案)的快速接入。

以上是摘抄GitHub上flutter-boot 和 flutter_boost的官方介绍,也因为官方文档上的集成步骤太过简略,在集成的过程中才过了不少的坑,所以都记录下来并且分享出来,希望可以给看文章的你,带去一些解决方案和思路。

本文主要记录一次flutter-boot的集成过程,对于flutter_boost的使用放在后续的文章中再介绍。

Native工程环境

已有一个维护迭代过多个版本的Native工程,并且已经做过组件化,拆分了多个组件(就是常见的组合组件的方法,通过CocoaPods的方式添加安装各个组件,制作CocoaPods远程私有库,将其发不到公司的gitlab或GitHub,使工程能够Pod下载下来),本次集成flutter-boot的目的是要把其中一个组件,使用flutter重写。

这里强调做过组件化,是因为很多问题都是因为组件化了才引起的如果只是一个单纯的工程集成的话,会简单不少,而且后续如果只是某个组件需要依赖flutter,还需要有一些特殊的操作。

Flutter-boot集成步骤和遇到的问题

这里可以跟着我一起来操作,下面的步骤我都写得尽量详细,遇到的问题都有截图或者文字描述,以及解决方案跟在后面。

  1. $ cd somepath/my_repo

这里可以是你的Native路径 , 也可以是一个新创建的文件夹,这里我是用了一个新的文件夹路径,目的是保证之后的路径和官方文档上的目录结构一致。

somepath/my_repo
└──my_android
└──my_ios
└──my_flutter
    └──.git
    └──.gitignore
    └──android_shell
    └──ios_shell
    └──android
    └──ios
  1. $ flutter-boot init

问题一:link 失败,这里忘记截图了。。。特别抱歉

解决方案:

  • 出现这个问题的原因,是因为flutter-boot的版本支持问题,官方文档上还要求装1.5.0的版本(其实已经支持到1.9了),但是一般我们电脑上的版本都比这个高,所以,要先降低版本,切换到master分支,然后执行init命令
拥有^1.5.0的flutter环境
  • 找到npm包中flutter-boot的源码,修改一下判断版本号的代码,不过。。。不推荐这种方式(但是亲测有效,毕竟来回切换版本号太浪费时间了)
  1. $ 请输入flutter工程名称: my_flutter_module (可以回车跳过,会自动生成一个叫my_flutter_module的module)
  2. $ 请输入flutter仓库地址,回车跳过
  3. $ ? 是否存在iOS工程? Yes
  4. $ ? iOS工程本地地址 somepath/my_repo/my_ios 这里输入你的native工程目录

问题二:podfile内容copy出错,你也可能不会遇到这个问题,这里是flutter-boot本身的bug,只去匹配了字符串'end',刚好我们使用了这个日历的库,就到这里把podfile文件中的内容给截断了

pod 'FSCalendar', '2.8.0' # 日历

解决方案:

  • 在执行flutter-boot init 之前检查一下你的podfile文件,看是否也存在这种字符中含有'end',可以先删除掉,init命令执行完之后再手动添加,注意原有target和新生成的Runner target中都要添加
  • 查看从那里开始copy截断,手动把后面的补齐
  • 注意 只copy pod 引用 不需要copy post_install中的内容(如果你的podfile中有post_install的话)
post_install do |installer_representation|
    installer_representation.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            ...
        end
    end
end

问题三:

这里是你工程的post_install和init命令生成的fbpodhelper.rb中的post_install冲突导致,

解决方案:

  • 还是因为降低了flutter版本导致的,使用flutter 1.9版本就不会有这个问题,但是1.5会报这个错误
  • 还有同事反馈,pod版本升级到最新也不会有这个问题,我的是1.6.1,目前最新已经是1.8以上了,我还没有试这个-。-
  • 修改fbpodhelper.rb中如下代码:
flutter_application_path = fbFlutterPath
require File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

改为:

 # ============= Flutter config begin ============== #
  
  flutter_application_path = './fpf_flutter/'
  podhelper_path = File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  
  podhelper_content = File.read(podhelper_path);
  podhelper_post_isntall = "post_install do |installer|";
  # 当post_install重复时需要去重以避免发生pod install错误:multiple post_install hooks is unsupported
  if podhelper_content.scan(/(#{podhelper_post_isntall})/).length > 0 then
    
    podhelper_buffer = podhelper_content.gsub(podhelper_post_isntall, "def update_configs(installer, framework_dir)")
    eval(podhelper_buffer, binding)
    
    else
    eval(File.read(podhelper_path), binding)
  end
  
  # ============= Flutter config end ============== #
  1. 混合工程初始化完成
info [init] 你可以在创建Android工程后调用 flutter-boot link来关联flutter
info [init] 混合工程初始化完成
  1. 运行flutter

你以为到这里就真的完成了吗,run一下~~ 记得要选择 Runner target,好像确实可以成功。但是flutter-boot的作用是你可以有两个开发视角,flutter视角下,不需要关心native,native视角,甚至可以不用安装flutter环境。那么,去flutter视角看一下,把flutter module拖入VSCode打开,然后fn+f5,运行起来

那么在flutter视角运行起来了吗

问题四: Run Flutter Build Script 中的脚本./my_flutter_module/.ios/Flutter/flutter_export_environment.sh 路径找不到,一个很奇怪的现象,pod install成功后查看路径,没问题,但是flutter run之后,这里的路径就改变了,相对路径错误,确实找不到这个脚本

解决方案:

  • $ cd ./my_flutter_module/.ios/Flutter/podhelper.rb
  • 打开这个文件,其实就是一个ruby脚本,找到设置flutter_export_environment.sh路径的代码,做一下修改,改为:
flutter_export_environment_path = File.join(project_directory_pathname, relative, 'flutter_export_environment.sh');
  1. AppDelegate 中注册flutter 引擎

In AppDelegate.h:

@import UIKit;
@import Flutter;

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

In AppDelegate.m:

@import FlutterPluginRegistrant; // Only if you have Flutter Plugins

#import "AppDelegate.h"

@implementation AppDelegate

// This override can be omitted if you do not have any Flutter Plugins.
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
  [self.flutterEngine runWithEntrypoint:nil];
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

In ViewController.m

#import "AppDelegate.h"
#import "ViewController.h"
@import Flutter;

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self
               action:@selector(handleButtonAction)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Press me" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor blueColor]];
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}

- (void)handleButtonAction {
    FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:false completion:nil];
}
@end

至此,flutter-boot 环境集成成功!!! 喜大普奔啊,终于不报错了~~

但是,回到我们最初集成flutter-boot的目的,是要使用flutter重写某一个业务线。因为是组件化的工程,所以flutter引擎的注册也不希望暴露在APPdelegate中,而是放在一个管理各个业务线分发的平台组件中,那么就涉及到,某一个组件中要使用flutter。

用呗,主工程都能用,组件一样能用,然后,当你在组件代码中添加如下代码之后:

#import <Flutter/Flutter.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

找不着,各种找不着啊。。。好像各个业务线组件根本就不能引用flutter以及flutter_module

解决方案:

  • 各业务线的podspec文件中添加依赖:
  • $ s.dependency 'Flutter'
  • $ s.dependency 'FlutterPluginRegistrant'

到这里集成flutter-boot 才算取得了阶段性的胜利✌️,希望这篇文章,可以给广大勇于尝试flutter-boot的开发人员,带去一些福利(主要是节省一些时间,都不是什么难解决的问题,就是得不断的尝试)。

在这个过程中,也去看了GitHub上他们所有开放和关闭的issues,并且尝试了N次,过程比较坎坷,不过成功了还是很可的事😃。

2019.12.11 补充 flutter-boot 推到远端之后,其他同事拉代码,装flutter-boot之后遇到的一些问题记录:

问题一:

产生原因: 曾用 root 用户进行了局部安装npm包,留下所属权为 root 的文件,导致普通用户 无法访问 root的文件内容。

解决方案:

  • 找到报无权限文件夹:/usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
  • 查看无权限文件夹的权限:ls -la /usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
    发现权限拥有者是root,但应该是我们本机用户
  • 更改权限拥有者(后面是用户名和文件夹名): sudo chown -R 用户名 /usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
  • 再次查看文件夹权限就改成用户了,npm包就可以正常下载啦。

问题二:my_flutter_module中数据缺失

就是别的同事拉下flutter_module中的代码里面,没有engine这个文件夹

解决方案:

  • .gitignore中去掉对这个文件夹的忽略,然后再push一次

问题三: native视角如何使用

其实这个也不能算是个问题啦,应该归到上面的使用步骤里面,毕竟这个flutter-boot的环境搭建起来,是要给大家一起用的,又不是指给自己用😀

其实切换到native视角,就会觉得这个工具,和我最开始选择用flutter-boot的初衷有一些偏离。最初,我们是希望有一套完全不影响原生开发的框架,去接入flutter,完全不影响是什么意思呢,领导希望,native开发,不需要安装flutter环境,不需要,不需要,不需要,重要的地方强调一下,但是通过这几天对flutter-boot的研究,发现根本做不到这一点。

首先别人拉下native代码和flutter代码之后,需要执行link命令去做软连接,但是本身flutter-boot就是依赖flutter库的,可能说依赖不太准确,但是link命令中就有判断flutter版本的逻辑,如果没有flutter的开发环境,这一步就过不去,那你的native和flutter如何链接上呢??

解决方案:

  • 这个问题其实没有解决,只是有一个思路
  • flutter的所有产物,都是一个framework ,我们可以手动把这些framework拖入到工程中,不用pod引入
  • 其实大部分依赖的framework 都只需要拖进去一次(这里不考虑更新)
  • 只有App.framework 需要频繁的修改,这里可以自己进入一些脚本,重定向他的产物
  • native工程中之前的依赖和各种path 都需要修改
  • 这个方案,理论上可行,但是需要改动的地方太大,还有各种维护的问题,成本都很大

如果看这篇文章的小伙伴,有更好的方式,欢迎交流!!!