Flutter混编(iOS)-远程依赖Beta

1,741 阅读5分钟

研究Flutter端端续续也已经半年多了,从1.5跟到1.9,但都是浅尝辄止,没有深入思考,正巧最近公司在大力搞这个东西,就又拿了起来。最近主要思考的是如何将Flutter放到自己的私有仓库库里,方便CI打包,而不是官网推荐的方式本地依赖。为什么要这么做呢?本地依赖有几个弊端。

  1. CI打包的问题,现在的CI打包已经成熟了,为了官网的混编,CI适配一下,成本较高,也不符合设计方式
  2. Native开发的时候不会关心Flutter工程,为了开发Native,本地搭建Flutter开发环境也不符合

想来想去也就只能研究研究远程依赖的方式。下面浅谈一下最近的一些成果,仅限于iOS,Android同学不要介意,不太会Android。

Flutter官网混编方式

Add Flutter to existing apps-master分支

官网现在的推荐方式比以前已经方便了很多了,而且已经到了Preview版本,相信不久的将来,我们会在稳定分支中使用它。

简单讲述下官网的混编方式。

Create a Flutter module

首先进入你的项目目录,键入下面命令:

$ cd some/path/
$ flutter create -t module my_flutter

完成后,目录结构如下面:

some/path/
  my_flutter/
    lib/main.dart
    .ios/
  MyApp/
    MyApp/
      AppDelegate.h
      AppDelegate.m (or swift)
      :
Add your Flutter app to your Podfile
  1. Add the following lines to your Podfile:
  flutter_application_path = 'path/to/my_flutter/'
  load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  1. For each Xcode target that needs to embed Flutter, callinstall_all_flutter_pods(flutter_application_path).
  target 'MyApp' do
    install_all_flutter_pods(flutter_application_path)
  end
  target 'MyAppTests' do
    install_all_flutter_pods(flutter_application_path)
  end
  1. Run pod install.

每次你添加Flutter plugin在some/path/my_flutter/pubspec.yaml,都需要在some/path/my_flutter执行flutter pub get以便通过podhelper.rb脚本刷新插件列表,然后在iOS项目目录再次执行pod install。 4. Disable bitcode for your targets

Flutter不支持bitcode,将iOS的targetENABLE_BITCODE设置为No。

进行以上操作后,iOS项目就集成了Flutter模块,还有具体细节,如何运行Flutter模块,AppDelegate需要进行部分修改,就不再赘述,大家参考官方文档Add Flutter to existing apps

Flutter依赖分析

混编依赖产物分析

打开Pod工程,会发现一共依赖了三个模块:

  • Flutter模块,包含Flutter.framework:Flutter库和引擎
  • my_flutter模块,App.framework:dart业务源码相关文件
  • FlutterPluginRegistrant模块,Flutter Plugin:编译出来的各种plugin的framework,这里描述的可能不正确,该模块应该是用来注册flutter plugin,由于demo工程没有flutter plugin,所以Flutter Plugin的模块,所以如果flutter项目有plugin,应该会有更多的模块
Podfile脚本分析
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

第一句,声明了flutter项目的地址,File.join拼接目录,目录为../my_flutter/.ios/Flutter/podhelper.rb,通过ruby的load方法将该目录podhelper.rb引入执行环境。

那么podhelper.rb中写了哪些东西呢?进入该文件的目录下查看,内容大致如下:

def install_all_flutter_pods(flutter_application_path = nil)
    ...
end

def install_flutter_engine_pod
    ...
end

def install_flutter_plugin_pods(flutter_application_path)
    ...
end

def install_flutter_application_pod(flutter_application_path)
    ...
end

都是方法,那么有什么用?根据官方文档指示,iOS工程需要flutter模块的target都需要在podfile中键入install_all_flutter_pods(flutter_application_path),这不就是podhelper.rb中的方法并且传入podfile声明的flutter_application_path嘛!再看该方法:

# Install pods needed to embed Flutter application, Flutter engine, and plugins
def install_all_flutter_pods(flutter_application_path = nil)
  flutter_application_path ||= File.join('..', '..')
  install_flutter_engine_pod
  install_flutter_plugin_pods(flutter_application_path)
  install_flutter_application_pod(flutter_application_path)
end

就是每个target下安装所需要的Flutter application, Flutter engine, and plugins,继续看着里的三个方法:

def install_flutter_engine_pod
    ...
    pod 'Flutter', :path => relative.to_s, :inhibit_warnings => true
end

def install_flutter_plugin_pods(flutter_application_path)
    ...
    pod 'FlutterPluginRegistrant', :path => File.join(relative, 'FlutterPluginRegistrant'), :inhibit_warnings => true
    ...
end

def install_flutter_application_pod(flutter_application_path)
    ...
    pod 'my_flutter', :path => relative.to_s, :inhibit_warnings => true
    ...
end

最终就是在target下执行各个flutter模块的pod操作,路径为本地路径。

综上就是官方混编的方式,其中有些细节没有进行梳理,比如plugin的引入,其实也是pod的操作,由于demo工程没有引入插件,暂时不进行讨论。

Flutter远程依赖的尝试

由于Flutter官方依赖的方式Pod本地引入,所以我猜想也可以Pod远程引入,目光集中在了Flutter.podspecmy_flutter.podspecFlutterPluginRegistrant.podspec三个podspec文件中,进过pod install日志分析,三个podspec定位在:

  • Flutter.podspec../my_flutter/.ios/Flutter/engine
  • my_flutter.podspec../my_flutter/.ios/Flutter
  • FlutterPluginRegistrant.podspec../vmovier_flutter/.ios/Flutter/FlutterPluginRegistrant

所以尝试把这三个podspec复制在了flutter项目的根目录下:

some/path/
  my_flutter/
    lib/main.dart
    .ios/
    Flutter.podspec
    my_flutter.podspec
    FlutterPluginRegistrant.podspec

并且对根目录下的podspec进行了部分修改,以便能正确的索引到响应的代码,以Flutter.podspec为例:

Pod::Spec.new do |s|
  s.name             = 'Flutter'
  s.version          = '1.0.0'
  s.summary          = 'High-performance, high-fidelity mobile apps.'
  s.description      = <<-DESC
Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
                       DESC
  s.homepage         = 'https://flutter.io'
  s.license          = { :type => 'MIT' }
  s.author           = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
  s.source           = { :git => 'git@git.lug.ustc.edu.cn:Sir.heng/vmovier.git', :tag => s.version.to_s }
  s.ios.deployment_target = '8.0'
  s.vendored_frameworks = '.ios/Flutter/engine/Flutter.framework'
end

其中原来的s.vendored_frameworksFlutter.framework改为了.ios/Flutter/engine/Flutter.framework,这样pod进行远程引入的时候就可以正确索引到Flutter.framework了,my_flutter.podspecFlutterPluginRegistrant.podspec这两个修改也是大同小异。

首先对iOS Native进行pod本地引入三个模块:

  pod 'Flutter', :path => '../my_flutter'
  pod 'my_flutter', :path => '../my_flutter'
  pod 'FlutterPluginRegistrant', :path => '../my_flutter'

pod install后运行Native项目,成功跑起来了,说明这样修改没问题。

下面就把Flutter项目推到远程就行了,然后进行远程引入:

  pod 'Flutter', :git => 'git@xxxxxx/my_flutter.git'
  pod 'my_flutter', :git => 'git@xxxxxx/my_flutter.git'
  pod 'FlutterPluginRegistrant', :git => 'git@xxxxxx/my_flutter.git'

pod install后运行Native项目,成功跑起来了,说明这样修改没问题。

需要注意的是,每次发布Flutter项目都需要进行,flutter build ios操作,以便将Flutter.frameworkmy_flutter.framework编译出来。

总结&现有问题

这样依赖后,项目可以正常运行,但是有几个问题:

  1. Flutter.framework 300M+,不知道打出包来会如何,待验证以及优化
  2. Flutter项目不能依赖Plugin,因为该方案,没有将Plugin引入,Plugin官方的依赖方式也是pod本地引入,但是多个Plugin会生成多个Plugin.podspec,改方案不能很好的支持,所以对于混编通信以及调用原生Api的需求,建议使用FlutterMethodChannel的方式,通信代码由Native进行编写与维护。

以上就是最近的总结,希望各位看官批评指正,互相交流