Flutter组件化方案尝试

1.安装Flutter

由于在国内访问Flutter有时可能会受到限制,Flutter官方为中国开发者搭建了临时镜像,大家可以将如下环境变量加入到用户环境变量中:

 

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export 

FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

vim .bash_profile

sorce .bash_profile

解压安装包到你想安装的目录

 

cd ~/development
unzip ~/Downloads/flutter_macos_v0.5.1-beta.zip

 

添加flutter相关工具到path中:

 

export PATH=/Users/fumeng/project/Flutter/flutter/bin:$PATH

运行 flutter doctor

flutter版本升级 flutter upgrade

二:在组件化中集成Flutter

在你的既有应用中集成 Flutter module,这里有两种方式可以将 Flutter 集成到你的既有应用中。

1.使用 CocoaPods 依赖管理和已安装的 Flutter SDK 。(推荐)

2.把 Flutter engine 、你的 dart 代码和所有 Flutter plugin 编译成 framework 。用 Xcode 手动集成到你的应用中,并更新编译设置。

 

 

https://flutter.cn/docs/development/add-to-app/ios/project-setup

1.创建FlutterModel

flutter create --template module my_flutter

flutter build ios

FlutterModel 可依赖到项目中的文件

 

engine: Flutter.framework

plugin: GeneratedPluginRegistrant

dependency: shared_preferences

在 `Podfile` 中添加下面代码:

 

flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

每个需要集成 Flutter 的 [Podfile target][],执行 `install_all_flutter_pods(flutter_application_path)`

 

target 'MyApp' do
       install_all_flutter_pods(flutter_application_path)
end

 

 

pod install

 

 

当你在 my_flutter/pubspec.yaml 改变了 Flutter plugin 依赖,需要在 Flutter module 目录运行 flutter pub get,来更新会被podhelper.rb 脚本用到的 plugin 列表,然后再次在你的应用目录 some/path/MyApp 运行 pod install.

 

 

 

podhelper.rb 脚本会把你的 plugins, Flutter.framework,和 App.framework 集成到你的项目中。

Framework

Framework 可以通俗的理解为封装了共享资源的具有层次结构的文件夹。共享资源可以是 nib文件、国际化字符串文件、头文件、库文件等等。它同时也是个 Bundle,里面的内容可以通过 Bundle 相关 API 来访问。Framework 可以是 static framework 或 dynamic framework。<font color=red> 在 iOS App 打包完成后,如果 Framework 包含了模拟器指令集(x86_64 或 i386),那么用 Xcode 发布 App 的时候,会报 unsupported architectures 的错误,所以需要我们手动或脚本去移除。</font>

XCFramework

XCFramework 是由 Xcode 创建的一个可分发的二进制包,它包含了 framework 或 library 的一个或多个变体,因此可以在多个平台(iOS、macOS、tvOS、watchOS) 上使用,包括模拟器。XCFramework 可以是静态的,也可以是动态的。xcframework 的好处就是用 Xcode 发布的时候,Xcode 会自动选用正确的指令集 Frameworks,省去了手动移除动态库中的模拟器指令集的工作。<font color=red>不过值得注意的是,Xcode 11 才引入 XCFramework 。</font>

 

 

解析 podhelper.rb源码
# Copyright 2014 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

require 'json'

# Install pods needed to embed Flutter application, Flutter engine, and plugins
# from the host application Podfile.
#
# @example
#   target 'MyApp' do
#     install_all_flutter_pods 'my_flutter'
#   end
# @param [String] flutter_application_path Path of the root directory of the Flutter module.
#                                          Optional, defaults to two levels up from the directory of this script.
#                                          MyApp/my_flutter/.ios/Flutter/../..
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

# Install Flutter engine pod.
#
# @example
#   target 'MyApp' do
#     install_flutter_engine_pod
#   end
def install_flutter_engine_pod
  current_directory = File.expand_path('..', __FILE__) // 脚本文件当前路径
  engine_dir = File.expand_path('engine', current_directory)
  framework_name = 'Flutter.xcframework'
  copied_engine = File.expand_path(framework_name, engine_dir)
  if !File.exist?(copied_engine)
    # Copy the debug engine to have something to link against if the xcode backend script has not run yet.
    # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
    release_framework_dir = File.join(flutter_root, 'bin', 'cache', 'artifacts', 'engine', 'ios-release')
    unless Dir.exist?(release_framework_dir)
      # iOS artifacts have not been downloaded.
      raise "#{release_framework_dir} must exist. Make sure \"flutter precache --ios\" has been run at least once"
    end
    FileUtils.cp_r(File.join(release_framework_dir, framework_name), engine_dir)
  end

  # Keep pod path relative so it can be checked into Podfile.lock.
  # Process will be run from project directory.
  engine_pathname = Pathname.new engine_dir
  # defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
  project_directory_pathname = defined_in_file.dirname
  relative = engine_pathname.relative_path_from project_directory_pathname

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

# Install Flutter plugin pods.
#
# @example
#   target 'MyApp' do
#     install_flutter_plugin_pods 'my_flutter'
#   end
# @param [String] flutter_application_path Path of the root directory of the Flutter module.
#                                          Optional, defaults to two levels up from the directory of this script.
#                                          MyApp/my_flutter/.ios/Flutter/../..
def install_flutter_plugin_pods(flutter_application_path)
  flutter_application_path ||= File.join('..', '..')

  # Keep pod path relative so it can be checked into Podfile.lock.
  # Process will be run from project directory.
  ios_project_directory_pathname = Pathname.new File.expand_path(File.join('..', '..'), __FILE__)
  # defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
  project_directory_pathname = defined_in_file.dirname
  relative = ios_project_directory_pathname.relative_path_from project_directory_pathname
  pod 'FlutterPluginRegistrant', :path => File.join(relative, 'Flutter', 'FlutterPluginRegistrant'), :inhibit_warnings => true

  symlinks_dir = File.join(relative, '.symlinks', 'plugins')
  FileUtils.mkdir_p(symlinks_dir)

  plugins_file = File.expand_path('.flutter-plugins-dependencies', flutter_application_path)
  plugin_pods = flutter_parse_dependencies_file_for_ios_plugin(plugins_file)
  plugin_pods.each do |plugin_hash|
    plugin_name = plugin_hash['name']
    plugin_path = plugin_hash['path']
    if (plugin_name && plugin_path)
      symlink = File.join(symlinks_dir, plugin_name)
      FileUtils.rm_f(symlink)
      File.symlink(plugin_path, symlink)
      pod plugin_name, :path => File.join(symlink, 'ios'), :inhibit_warnings => true
    end
  end
end

# Install Flutter application pod.
#
# @example
#   target 'MyApp' do
#     install_flutter_application_pod '../flutter_settings_repository'
#   end
# @param [String] flutter_application_path Path of the root directory of the Flutter module.
#                                          Optional, defaults to two levels up from the directory of this script.
#                                          MyApp/my_flutter/.ios/Flutter/../..
def install_flutter_application_pod(flutter_application_path)
  current_directory_pathname = Pathname.new File.expand_path('..', __FILE__)
  app_framework_dir = File.expand_path('App.framework', current_directory_pathname.to_path)
  app_framework_dylib = File.join(app_framework_dir, 'App')
  if !File.exist?(app_framework_dylib)
    # Fake an App.framework to have something to link against if the xcode backend script has not run yet.
    # CocoaPods will not embed the framework on pod install (before any build phases can run) if the dylib does not exist.
    # Create a dummy dylib.
    FileUtils.mkdir_p(app_framework_dir)
    `echo "static const int Moo = 88;" | xcrun clang -x c -dynamiclib -o "#{app_framework_dylib}" -`
  end

  # Keep pod and script phase paths relative so they can be checked into source control.
  # Process will be run from project directory.

  # defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
  project_directory_pathname = defined_in_file.dirname
  relative = current_directory_pathname.relative_path_from project_directory_pathname
  pod 'long1_flutter_module', :path => relative.to_s, :inhibit_warnings => true

  flutter_export_environment_path = File.join('${SRCROOT}', relative, 'flutter_export_environment.sh');
  script_phase :name => 'Run Flutter Build long1_flutter_module Script',
    :script => "set -e\nset -u\nsource \"#{flutter_export_environment_path}\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build",
    :execution_position => :before_compile
end

# .flutter-plugins-dependencies format documented at
# https://flutter.dev/go/plugins-list-migration
def flutter_parse_dependencies_file_for_ios_plugin(file)
  file_path = File.expand_path(file)
  return [] unless File.exists? file_path

  dependencies_file = File.read(file)
  dependencies_hash = JSON.parse(dependencies_file)

  # dependencies_hash.dig('plugins', 'ios') not available until Ruby 2.3
  return [] unless dependencies_hash.has_key?('plugins')
  return [] unless dependencies_hash['plugins'].has_key?('ios')
  dependencies_hash['plugins']['ios'] || []
end

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', '..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path) // unless与if相反  raise是抛出异常
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/) // 只匹配第一次,返回为MatchData类型。https://blog.csdn.net/cz9025/article/details/90202839
    return matches[1].strip if matches // strip 返回 str 的副本,移除了前导的空格和尾随的空格。
  end
  # This should never happen...
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

Generated.xcconfig

// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/lilonglong/lllProject/newFlutter/flutter
FLUTTER_APPLICATION_PATH=/Users/lilonglong/Desktop/course/Flutter/helloword/ios_flutter_module
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
SYMROOT=${SOURCE_ROOT}/../build/ios
OTHER_LDFLAGS=$(inherited) -framework Flutter
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=false
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.packages

.flutter-plugins-dependencies

{
    "info":"This is a generated file; do not edit or check into version control.",
    "plugins":{
        "ios":[
            {
                "name":"shared_preferences",
                "path":"/Users/lilonglong/lllProject/newFlutter/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.10/",
                "dependencies":[

                ]
            }
        ],
        "android":[
            {
                "name":"shared_preferences",
                "path":"/Users/lilonglong/lllProject/newFlutter/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.10/",
                "dependencies":[

                ]
            }
        ],
        "macos":[
            {
                "name":"shared_preferences_macos",
                "path":"/Users/lilonglong/lllProject/newFlutter/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_macos-0.0.1+10/",
                "dependencies":[

                ]
            }
        ],
        "linux":[
            {
                "name":"path_provider_linux",
                "path":"/Users/lilonglong/lllProject/newFlutter/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_linux-0.0.1+2/",
                "dependencies":[

                ]
            },
            {
                "name":"shared_preferences_linux",
                "path":"/Users/lilonglong/lllProject/newFlutter/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_linux-0.0.2+2/",
                "dependencies":[
                    "path_provider_linux"
                ]
            }
        ],
        "windows":[

        ],
        "web":[
            {
                "name":"shared_preferences_web",
                "path":"/Users/lilonglong/lllProject/newFlutter/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_web-0.1.2+7/",
                "dependencies":[

                ]
            }
        ]
    },
    "dependencyGraph":[
        {
            "name":"path_provider_linux",
            "dependencies":[

            ]
        },
        {
            "name":"shared_preferences",
            "dependencies":[
                "shared_preferences_linux",
                "shared_preferences_macos",
                "shared_preferences_web"
            ]
        },
        {
            "name":"shared_preferences_linux",
            "dependencies":[
                "path_provider_linux"
            ]
        },
        {
            "name":"shared_preferences_macos",
            "dependencies":[

            ]
        },
        {
            "name":"shared_preferences_web",
            "dependencies":[

            ]
        }
    ],
    "date_created":"2020-09-10 18:59:22.806998",
    "version":"1.20.2"
}

编译报错

Unexpected failure

解决办法

Pods-SPRViewModel_Example-resources.sh

删除

--app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}"

FlutterSDK 结构

1.Flutter.framwwork

 提供 FlutterEngine

2.Plugin

链接FlutterEngine

3.APP.framework

包含用户编写的所有Dart应用程序代码的AOT快照以及 armv7arm64 格式的Flutter框架和插件的Dart代码

-存放durt的编译产物 flutter页面

添加一个Flutter页面

为了在原生 iOS 应用中展示 Flutter 页面,需要使用到FlutterEngine 和 FlutterViewController。其中,FlutterEngine 充当 Dart VM 和 Flutter 运行时环境; FlutterViewController 依附于 FlutterEngine,给 Flutter 传递 UIKit 的输入事件,并展示被 FlutterEngine 渲染的每一帧画面

 

Flutter 调试热重载

在fluttermodule 中

flutter attach or flutter attach -d deviceId 

flutter: Observatory listening on http://127.0.0.1:60455/

flutter attach --debug-port=60455

在vscode中

 

Select the correct device using the status bar in VS Code, then run the Flutter: Attach to Flutter on Device command from the command palette.

dart文件更新后,需要

flutter build ios --debug

flutter build ios --release

flutter build iOS (默认是release)

来更新app.framework

 

Flutter组件化方案尝试

By lilonglong

Flutter组件化方案尝试

  • 81