流程图

创建 FlutterEngineGroup

Flutter 引擎组的作用

  • 1、主要用于管理多个 Flutter 引擎,从 FlutterEngineGroup 生成的 FlutterEngine 具有常用共享资源(例如 GPU 上下文、字体度量和隔离线程的快照)的性能优势,从而加快首次渲染的速度、降低延迟并降低内存占用。
  • 2、官方说:采用 Group 管理多引擎在内存上,除了第一个 Engine 对象之外,后续每个 Engine 对象在 Android 和 iOS 上仅占用 180kB 。“实际上是否,待验证” 。
  • 3、例子 let engines = FlutterEngineGroup(name: “multiple-flutters”, project: nil)

FlutterEngineGroup 源码

  • 从源码上看 FlutterEngineGroup 类主要有一个 engines 数组,去维护管理这些引擎
@interface FlutterEngineGroup ()
@property(nonatomic, copy) NSString* name;
@property(nonatomic, retain) NSMutableArray<NSValue*>* engines;
// 对象来指定入口文件和 Assets
@property(nonatomic, retain) FlutterDartProject* project;
@end

@implementation FlutterEngineGroup {
  int _enginesCreatedCount;
}

/// 创建 FlutterEngineGroup ,内部初始化数组
- (instancetype)initWithName:(NSString*)name project:(nullable FlutterDartProject*)project {
  self = [super init];
  if (self) {
    _name = [name copy];
    _engines = [[NSMutableArray<NSValue*> alloc] init];
    _project = [project retain];
  }
  return self;
}
...

创建 FlutterEngine

1、makeEngineWithOptions

  • 1、引擎创建逻辑,当引擎池没有引擎时,通过makeEngine创建一个新引擎。

  • 2、如果引擎池里有引擎,则返回当前引擎,并且通过 spawnWithEntrypoint 方法设置相同的入口点、dart 库路径、路由、入口参数。

    • a、FlutterEngineGroup 是怎么做到部分的引擎资源共享呢?重点在于 spawnWithEntrypoint 内部实现,后面我们再说~
  • 3、例子: let newEngine = appDelegate.engines.makeEngine(with: options)

  • 4、makeEngineWithOptions 源码如下:

/// 创建并返回引擎
- (FlutterEngine*)makeEngineWithOptions:(nullable FlutterEngineGroupOptions*)options {
  NSString* entrypoint = options.entrypoint;
  NSString* libraryURI = options.libraryURI;
  NSString* initialRoute = options.initialRoute;
  NSArray<NSString*>* entrypointArgs = options.entrypointArgs;

  FlutterEngine* engine;
  // 当引擎没有创建过,那么就创建一个新的引擎
  if (self.engines.count <= 0) {
    engine = [self makeEngine];
    [engine runWithEntrypoint:entrypoint
                   libraryURI:libraryURI
                 initialRoute:initialRoute
               entrypointArgs:entrypointArgs];
  } else {
    // 已经存在引擎,则获取当前引擎,并通过 spawnWithEntrypoint 方法做一系列操作
    FlutterEngine* spawner = (FlutterEngine*)[self.engines[0] pointerValue];
    engine = [spawner spawnWithEntrypoint:entrypoint
                               libraryURI:libraryURI
                             initialRoute:initialRoute
                           entrypointArgs:entrypointArgs];
  }
  [_engines addObject:[NSValue valueWithPointer:engine]];

  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onEngineWillBeDealloced:)
                 name:kFlutterEngineWillDealloc
               object:engine];

  return engine;
}

2、makeEngine

  • makeEngine 的源码
// @property(nonatomic, readonly) NSMutableDictionary* pluginPublications;
// @property(nonatomic, readonly) NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* registrars;


/// 创建新的引擎
- (FlutterEngine*)makeEngine {
  NSString* engineName = [NSString stringWithFormat:@"%@.%d", self.name, ++_enginesCreatedCount];
  FlutterEngine* result = [[FlutterEngine alloc] initWithName:engineName project:self.project];
  return [result autorelease];
}

/// 实现逻辑
- (instancetype)initWithName:(NSString*)labelPrefix
                     project:(FlutterDartProject*)project
      allowHeadlessExecution:(BOOL)allowHeadlessExecution
          restorationEnabled:(BOOL)restorationEnabled {
  self = [super init];
  NSAssert(self, @"Super init cannot be nil");
  NSAssert(labelPrefix, @"labelPrefix is required");

  _restorationEnabled = restorationEnabled;
  _allowHeadlessExecution = allowHeadlessExecution;
  _labelPrefix = [labelPrefix copy];

  _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(self);

  if (project == nil) {
    _dartProject.reset([[FlutterDartProject alloc] init]);
  } else {
    _dartProject.reset([project retain]);
  }

  if (!EnableTracingIfNecessary([_dartProject.get() settings])) {
    NSLog(
        @"Cannot create a FlutterEngine instance in debug mode without Flutter tooling or "
        @"Xcode.\n\nTo launch in debug mode in iOS 14+, run flutter run from Flutter tools, run "
        @"from an IDE with a Flutter IDE plugin or run the iOS project from Xcode.\nAlternatively "
        @"profile and release mode apps can be launched from the home screen.");
    [self release];
    return nil;
  }

  _pluginPublications = [[NSMutableDictionary alloc] init];
  _registrars = [[NSMutableDictionary alloc] init];
  [self recreatePlatformViewController];

  _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
  _textureRegistry = [[FlutterTextureRegistryRelay alloc] initWithParent:self];
  _connections.reset(new flutter::ConnectionCollection());

  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onMemoryWarning:)
                 name:UIApplicationDidReceiveMemoryWarningNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationWillEnterForeground:)
                 name:UIApplicationWillEnterForegroundNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationDidEnterBackground:)
                 name:UIApplicationDidEnterBackgroundNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];

  return self;
}

a、初始化 FlutterDartProject 对象

  • 1、用于 Flutter 引擎中运行 Dart 代码。Flutter 引擎通过 FlutterDartProject 与 Dart 代码进行交互,比如启动 Dart VM,加载 Dart 库,执行 Dart 函数等等。在 Flutter 应用程序中,通常会创建一个 FlutterDartProject 实例用于管理 Dart 代码的执行。
  • 2、初始化 _settings ,从指定的 bundle 中读取 FlutterSettings 配置信息,并返回一个 FlutterSettings 对象。这个对象包含了应用的各种配置信息,例如引擎的版本、是否启用 Dart 开发者工具、是否支持热重载等。
struct Settings {
    Settings();
    ...
    // Enable the Impeller renderer on supported platforms. Ignored if Impeller is
    // not supported on the platform.
    bool enable_impeller = false;
}

- (instancetype)init {
  return [self initWithPrecompiledDartBundle:nil];
}

- (instancetype)initWithPrecompiledDartBundle:(nullable NSBundle*)bundle {
  self = [super init];

  if (self) {
    _settings = FLTDefaultSettingsForBundle(bundle);
  }

  return self;
}

flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) {
    auto command_line = flutter::CommandLineFromNSProcessInfo();

    // Precedence:
    // 1. Settings from the specified NSBundle.
    // 2. Settings passed explicitly via command-line arguments.
    // 3. Settings from the NSBundle with the default bundle ID.
    // 4. Settings from the main NSBundle and default values.

    NSBundle* mainBundle = [NSBundle mainBundle];
    NSBundle* engineBundle = [NSBundle bundleForClass:[FlutterViewController class]];

    bool hasExplicitBundle = bundle != nil;
    if (bundle == nil) {
        bundle = [NSBundle bundleWithIdentifier:[FlutterDartProject defaultBundleIdentifier]];
    }
    if (bundle == nil) {
        bundle = mainBundle;
    }

    auto settings = flutter::SettingsFromCommandLine(command_line);

    settings.task_observer_add = [](intptr_t key, const fml::closure& callback) {
        fml::MessageLoop::GetCurrent().AddTaskObserver(key, callback);
    };

    settings.task_observer_remove = [](intptr_t key) {
        fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
    };

    settings.log_message_callback = [](const std::string& tag, const std::string& message) {
        // TODO(cbracken): replace this with os_log-based approach.
        // https://github.com/flutter/flutter/issues/44030
        std::stringstream stream;
        if (!tag.empty()) {
            stream << tag << ": ";
        }
        stream << message;
        std::string log = stream.str();
        syslog(LOG_ALERT, "%.*s", (int)log.size(), log.c_str());
    };

    // The command line arguments may not always be complete. If they aren't, attempt to fill in
    // defaults.

    // Flutter ships the ICU data file in the bundle of the engine. Look for it there.
    if (settings.icu_data_path.empty()) {
        NSString* icuDataPath = [engineBundle pathForResource:@"icudtl" ofType:@"dat"];
        if (icuDataPath.length > 0) {
            settings.icu_data_path = icuDataPath.UTF8String;
        }
    }

    if (flutter::DartVM::IsRunningPrecompiledCode()) {
        if (hasExplicitBundle) {
            NSString* executablePath = bundle.executablePath;
            if ([[NSFileManager defaultManager] fileExistsAtPath:executablePath]) {
                settings.application_library_path.push_back(executablePath.UTF8String);
            }
        }

        // No application bundle specified.  Try a known location from the main bundle's Info.plist.
        if (settings.application_library_path.empty()) {
            NSString* libraryName = [mainBundle objectForInfoDictionaryKey:@"FLTLibraryPath"];
            NSString* libraryPath = [mainBundle pathForResource:libraryName ofType:@""];
            if (libraryPath.length > 0) {
                NSString* executablePath = [NSBundle bundleWithPath:libraryPath].executablePath;
                if (executablePath.length > 0) {
                    settings.application_library_path.push_back(executablePath.UTF8String);
                }
            }
        }

        // In case the application bundle is still not specified, look for the App.framework in the
        // Frameworks directory.
        if (settings.application_library_path.empty()) {
            NSString* applicationFrameworkPath = [mainBundle pathForResource:@"Frameworks/App.framework"
                                      ofType:@""];
            if (applicationFrameworkPath.length > 0) {
                NSString* executablePath =
                    [NSBundle bundleWithPath:applicationFrameworkPath].executablePath;
                if (executablePath.length > 0) {
                    settings.application_library_path.push_back(executablePath.UTF8String);
                }
            }
        }
    }

    // Checks to see if the flutter assets directory is already present.
    if (settings.assets_path.empty()) {
        NSString* assetsName = [FlutterDartProject flutterAssetsName:bundle];
        NSString* assetsPath = [bundle pathForResource:assetsName ofType:@""];

        if (assetsPath.length == 0) {
            assetsPath = [mainBundle pathForResource:assetsName ofType:@""];
        }

        if (assetsPath.length == 0) {
            NSLog(@"Failed to find assets path for "%@"", assetsName);
        } else {
            settings.assets_path = assetsPath.UTF8String;

            // Check if there is an application kernel snapshot in the assets directory we could
            // potentially use.  Looking for the snapshot makes sense only if we have a VM that can use
            // it.
            if (!flutter::DartVM::IsRunningPrecompiledCode()) {
                NSURL* applicationKernelSnapshotURL =
                    [NSURL URLWithString:@(kApplicationKernelSnapshotFileName)
 relativeToURL:[NSURL fileURLWithPath:assetsPath]];
                if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) {
                    settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String;
                } else {
                    NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path);
                }
            }
        }
    }

    // Domain network configuration
    // Disabled in https://github.com/flutter/flutter/issues/72723.
    // Re-enable in https://github.com/flutter/flutter/issues/54448.
    settings.may_insecurely_connect_to_all_domains = true;
    settings.domain_network_policy = "";

    // Whether to enable Impeller.
    NSNumber* nsEnableWideGamut = [mainBundle objectForInfoDictionaryKey:@"FLTEnableWideGamut"];
    // TODO(gaaclarke): Make this value `on` by default (pending memory audit).
    BOOL enableWideGamut = nsEnableWideGamut ? nsEnableWideGamut.boolValue : NO;
    settings.enable_wide_gamut = enableWideGamut;

    // Whether to enable Impeller.
    NSNumber* enableImpeller = [mainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"];
    // Change the default only if the option is present.
    if (enableImpeller != nil) {
        settings.enable_impeller = enableImpeller.boolValue;
    }

    NSNumber* enableTraceSystrace = [mainBundle objectForInfoDictionaryKey:@"FLTTraceSystrace"];
    // Change the default only if the option is present.
    if (enableTraceSystrace != nil) {
        settings.trace_systrace = enableTraceSystrace.boolValue;
    }

    NSNumber* enableDartProfiling = [mainBundle objectForInfoDictionaryKey:@"FLTEnableDartProfiling"];
    // Change the default only if the option is present.
    if (enableDartProfiling != nil) {
        settings.enable_dart_profiling = enableDartProfiling.boolValue;
    }

    // Leak Dart VM settings, set whether leave or clean up the VM after the last shell shuts down.
    NSNumber* leakDartVM = [mainBundle objectForInfoDictionaryKey:@"FLTLeakDartVM"];
    // It will change the default leak_vm value in settings only if the key exists.
    if (leakDartVM != nil) {
        settings.leak_vm = leakDartVM.boolValue;
    }

    #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
    // There are no ownership concerns here as all mappings are owned by the
    // embedder and not the engine.
    auto make_mapping_callback = [](const uint8_t* mapping, size_t size) {
        return [mapping, size]() { return std::make_unique<fml::NonOwnedMapping>(mapping, size); };
    };

    settings.dart_library_sources_kernel =
        make_mapping_callback(kPlatformStrongDill, kPlatformStrongDillSize);
    #endif  // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG

    // If we even support setting this e.g. from the command line or the plist,
    // we should let the user override it.
    // Otherwise, we want to set this to a value that will avoid having the OS
    // kill us. On most iOS devices, that happens somewhere near half
    // the available memory.
    // The VM expects this value to be in megabytes.
    if (settings.old_gen_heap_size <= 0) {
        settings.old_gen_heap_size = std::round([NSProcessInfo processInfo].physicalMemory * .48 /
                                        flutter::kMegaByteSizeInBytes);
    }

    // This is the formula Android uses.
    // https://android.googlesource.com/platform/frameworks/base/+/39ae5bac216757bc201490f4c7b8c0f63006c6cd/libs/hwui/renderthread/CacheManager.cpp#45
    CGFloat scale = [UIScreen mainScreen].scale;
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width * scale;
    CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height * scale;
    settings.resource_cache_max_bytes_threshold = screenWidth * screenHeight * 12 * 4;

    return settings;
}
Flutter 3.9.0 版才默认渲染引擎 skia or impeller ? 我可以修改关闭默认 skia 渲染模式吗? 以及其他默认配置调整吗?我们看看 flutter::Settings 默认设置的源码。
  • 从源码读到发现 mainBundle key FLTEnableImpeller 是设置 impeller 渲染模式。截图默认设置 YES,所以我们也可以关闭 impeller 采用 skia 渲染引擎~

![](/images/2023/FlutterEngineGroup 引擎启动流程/2.png)

  • 1、Flutter 3.9.0 版本中发现,默认Flutter Engine中资源缓存所占用的最大内存大小其实是按屏幕大小动态调整的~很神奇吧~~
  • 2、因此我们阅读源码去发现 Flutter 引擎一些默认配置~

b、初始化插件表 _pluginPublications 与 _registrars

  • 1、_pluginPublications:每当一个插件在 FlutterEngine 中注册时, key 对应的 value 会被设置 [NSNull null],并将一个 Flutter 插件注册到 registrar 中,发布到 Flutter 插件库中。
  • 2、_registrars: 管理插件的注册表,存储的是 FlutterPluginRegistrar 插件实例对象。
  • 3、源码可以查看注册插件的逻辑如下
- (void)publish:(NSObject*)value {
  _flutterEngine.pluginPublications[_pluginKey] = value;
}
...   
- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
  NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey);
  self.pluginPublications[pluginKey] = [NSNull null];
  FlutterEngineRegistrar* result = [[FlutterEngineRegistrar alloc] initWithPlugin:pluginKey
                                                                    flutterEngine:self];
  self.registrars[pluginKey] = result;
  return [result autorelease];
}
  • 例如注册 dart-native 插件 ,registry 是当前引擎 Engine 调用上述的 registrarForPlugin 注册 DartNativePlugin 插件。
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
  [DartNativePlugin registerWithRegistrar:[registry registrarForPlugin:@"DartNativePlugin"]];
}

c、recreatePlatformViewController

  • 1、设置渲染 API 为 软件渲染模式
  • 2、重新创建并配置与 Flutter 引擎相关的平台视图控制器 FlutterPlatformViewsController
- (void)recreatePlatformViewController {
  _renderingApi = flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering);
  _platformViewsController.reset(new flutter::FlutterPlatformViewsController());
}

d、初始化 Flutter 与原生宿主通信对象 _binaryMessenger && _textureRegistry

  • 1、FlutterBinaryMessengerRelay 是 Flutter 引擎内部通信的消息传递机制,它允许 Flutter 插件和宿主应用之间相互通信。
  • 2、FlutterTextureRegistryRelay 则是负责管理 Flutter 引擎中所有纹理的注册表。Flutter 引擎中的纹理用于在 Flutter 中绘制图像,比如从原生平台传递到 Flutter 中的图像或视频等资源。
  • 3、这两行代码是在初始化 FlutterEngine 对象时创建了这两个关键的对象,确保了后续 Flutter 插件和 Flutter 引擎之间的通信和纹理管理能够正常进行。
  • 4、_connections.reset(new flutter::ConnectionCollection()) 初始化连接集合,以便在 Flutter Engine 中创建和管理通信通道。
_binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
_textureRegistry = [[FlutterTextureRegistryRelay alloc] initWithParent:self];
_connections.reset(new flutter::ConnectionCollection());
Flutter 引擎实现原生与 Flutter 通信协议 FlutterBinaryMessenger
  • 原生通过注册的 FlutterMethodChannel 的 _messenger (FlutterBinaryMessenger)对象给 Flutter 端传消息调用 sendOnChannel:message 方法
/// 给 Flutter 发消息
- (void)invokeMethod:(NSString*)method arguments:(id)arguments {
    FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:method
                                                       arguments:arguments];
    NSData* message = [_codec encodeMethodCall:methodCall];
    [_messenger sendOnChannel:_name message:message];
}

/// 接受 Flutter 端的消息
- (void)setMethodCallHandler:(FlutterMethodCallHandler)handler {
    if (!handler) {
        if (_connection > 0) {
            [_messenger cleanUpConnection:_connection];
            _connection = 0;
        } else {
            [_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:nil];
        }
        return;
    }
    // Make sure the block captures the codec, not self.
    // `self` might be released before the block, so the block needs to retain the codec to
    // make sure it is not released with `self`
    NSObject<FlutterMethodCodec>* codec = _codec;
    FlutterBinaryMessageHandler messageHandler = ^(NSData* message, FlutterBinaryReply callback) {
        FlutterMethodCall* call = [codec decodeMethodCall:message];
        handler(call, ^(id result) {
            if (result == FlutterMethodNotImplemented) {
                callback(nil);
            } else if ([result isKindOfClass:[FlutterError class]]) {
                callback([codec encodeErrorEnvelope:(FlutterError*)result]);
            } else {
                callback([codec encodeSuccessEnvelope:result]);
            }
        });
    };
    _connection = SetMessageHandler(_messenger, _name, messageHandler, _taskQueue);
}

f、注册 App 生命相关通知

NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self
           selector:@selector(onMemoryWarning:)
               name:UIApplicationDidReceiveMemoryWarningNotification
             object:nil];

[center addObserver:self
           selector:@selector(applicationWillEnterForeground:)
               name:UIApplicationWillEnterForegroundNotification
             object:nil];

[center addObserver:self
           selector:@selector(applicationDidEnterBackground:)
               name:UIApplicationDidEnterBackgroundNotification
             object:nil];

[center addObserver:self
           selector:@selector(onLocaleUpdated:)
               name:NSCurrentLocaleDidChangeNotification
             object:nil];

3、runWithEntrypoint:

  • 1、这段代码实现了创建 Flutter Shell 的功能。Shell 是 Flutter 的核心部分之一,用于启动 Dart 代码和 Flutter 应用程序,从而展示 Flutter UI。Shell 是一个 C++ 应用程序,它与 Dart 代码之间使用 Embedder API 进行通信。在 iOS 平台上,Flutter 的 Shell 是由 Objective-C 代码创建的,与宿主应用程序交互。FlutterEngine 类是与 Shell 交互的主要接口。

  • 2、Shell 的作用?

    • a、Shell 是 Flutter 的底层框架,它提供了一个跨平台的渲染引擎、视图系统和一套基础库。Shell 是构建 Flutter 应用程序的基础,它将应用程序逻辑和 Flutter 引擎的交互封装在一起。
    • b、Flutter 应用程序通常包含一个或多个 Shell,每个 Shell 包含一个渲染线程和一个 Dart 执行上下文。Shell 接收来自 Dart 代码的指令,并将其翻译成图形、动画和其他视觉效果,以及响应用户的输入事件。Shell 还提供了对 Flutter 引擎的访问,使开发者可以配置引擎和访问它的功能。
/// 创建 Flutter Shell 并启动引擎
- (BOOL)runWithEntrypoint:(NSString*)entrypoint
               libraryURI:(NSString*)libraryURI
             initialRoute:(NSString*)initialRoute
           entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
  if ([self createShell:entrypoint libraryURI:libraryURI initialRoute:initialRoute]) {
    [self launchEngine:entrypoint libraryURI:libraryURI entrypointArgs:entrypointArgs];
  }
  return _shell != nullptr;
}

a、createShell

  • 这段代码实现了创建 Flutter Shell 的功能。Shell 是 Flutter 的核心部分之一,用于启动 Dart 代码和 Flutter 应用程序,从而展示 Flutter UI。Shell 是一个 C++ 应用程序,它与 Dart 代码之间使用 Embedder API 进行通信。在 iOS 平台上,Flutter 的 Shell 是由 Objective-C 代码创建的,与宿主应用程序交互。FlutterEngine 类是与 Shell 交互的主要接口。

  • 具体步骤如下:

  • 1、首先,该函数检查 Shell 是否已经创建。如果已经创建,函数直接返回。

  • 2、接下来,该函数将传入的 entrypoint、libraryURI 和 initialRoute 参数保存到对象属性中。entrypoint 表示 Dart 代码的入口点,libraryURI 表示应用程序的 Dart 库文件路径,initialRoute 表示应用程序的初始路由。如果 initialRoute 参数为空,该函数尝试从 Dart 配置中获取默认的路由。

  • 3、然后,该函数设置了 FlutterView.forceSoftwareRendering 属性。该属性用于控制是否启用软件渲染,根据 Dart 配置中的 enable_software_rendering 参数来决定是否启用。

  • 4、接着,该函数创建了一个 Flutter 的 PlatformData 对象。PlatformData 包含了许多平台相关的信息,如窗口大小、文本方向、缩放比例等。在 iOS 平台上,PlatformData 包含了平台视图的大小和位置信息。

  • 5、然后,该函数设置了入口点信息,并生成一个线程标签用于标识该 FlutterEngine 的线程。接着,函数创建了一个 ThreadHost 对象,该对象表示线程的主机,并使用 makeThreadHost 函数创建了三个线程:UI 线程、IO 线程和 Raster 线程。这些线程将在后续的 Shell 创建过程中使用。

  • 6、然后,该函数创建了两个回调函数 on_create_platform_view 和 on_create_rasterizer。on_create_platform_view 回调函数用于创建平台视图,而 on_create_rasterizer 回调函数用于创建光栅化器。这些回调函数将在后续的 Shell 创建过程中使用。

  • 7、接着,该函数创建了一个 TaskRunners 对象,该对象封装了线程标签、平台任务运行器、光栅化任务运行器、UI 任务运行器和 IO 任务运行器。这些任务运行器将在后续的 Shell 创建过程中使用。

  • 8、接下来,该函数检查应用程序是否处于后台状态。如果应用程序处于后台状态,则禁用 GPU,否则启用 GPU。禁用 GPU 可以减少电量消耗和热量,从而延长电池寿命。

  • 9、然后,该函数调用 Shell::Create 函数创建 Shell。Shell::Create 函数是一个同步函数,它会阻塞当前线程,直到 Shell 创建完成

  • 10、最后,调用 setupShell 设置 Shell , 并且判断如果是 isProfilerEnabled 模式下,开启性能分析

// 创建 Flutter Shell
- (BOOL)createShell:(NSString*)entrypoint
         libraryURI:(NSString*)libraryURI
       initialRoute:(NSString*)initialRoute {
  if (_shell != nullptr) {
    FML_LOG(WARNING) << "This FlutterEngine was already invoked.";
    return NO;
  }

  self.initialRoute = initialRoute;

  auto settings = [_dartProject.get() settings];
  if (initialRoute != nil) {
    self.initialRoute = initialRoute;
  } else if (settings.route.empty() == false) {
    self.initialRoute = [NSString stringWithCString:settings.route.c_str()
                                           encoding:NSUTF8StringEncoding];
  }

  FlutterView.forceSoftwareRendering = settings.enable_software_rendering;

  auto platformData = [_dartProject.get() defaultPlatformData];

  SetEntryPoint(&settings, entrypoint, libraryURI);

  NSString* threadLabel = [FlutterEngine generateThreadLabel:_labelPrefix];
  _threadHost = std::make_shared<flutter::ThreadHost>();
  *_threadHost = [FlutterEngine makeThreadHost:threadLabel];

  // Lambda captures by pointers to ObjC objects are fine here because the
  // create call is synchronous.
  flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
      [self](flutter::Shell& shell) {
        [self recreatePlatformViewController];
        return std::make_unique<flutter::PlatformViewIOS>(
            shell, self->_renderingApi, self->_platformViewsController, shell.GetTaskRunners());
      };

  flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
      [](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };

  flutter::TaskRunners task_runners(threadLabel.UTF8String,                          // label
                                    fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
                                    _threadHost->raster_thread->GetTaskRunner(),     // raster
                                    _threadHost->ui_thread->GetTaskRunner(),         // ui
                                    _threadHost->io_thread->GetTaskRunner()          // io
  );

  _isGpuDisabled =
      [UIApplication sharedApplication].applicationState == UIApplicationStateBackground;
  // Create the shell. This is a blocking operation.
  std::unique_ptr<flutter::Shell> shell = flutter::Shell::Create(
      /*platform_data=*/platformData,
      /*task_runners=*/task_runners,
      /*settings=*/settings,
      /*on_create_platform_view=*/on_create_platform_view,
      /*on_create_rasterizer=*/on_create_rasterizer,
      /*is_gpu_disabled=*/_isGpuDisabled);

  if (shell == nullptr) {
    FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: "
                   << entrypoint.UTF8String;
  } else {
    [self setupShell:std::move(shell)
        withVMServicePublication:settings.enable_vm_service_publication];
    if ([FlutterEngine isProfilerEnabled]) {
      [self startProfiler];
    }
  }

  return _shell != nullptr;
}

b、launchEngine

  • 配置启动 Dart 应用程序引擎的一些列配置包括

    • 1、代码库路径
    • 2、入口点(entrypoint)
    • 3、包含可执行文件的路径
    • 4、环境变量
    • 5、启动参数等…
// 启动引擎
- (void)launchEngine:(NSString*)entrypoint
libraryURI:(NSString*)libraryOrNil
entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
  // Launch the Dart application with the inferred run configuration.
  self.shell.RunEngine([_dartProject.get() runConfigurationForEntrypoint:entrypoint
                                        libraryOrNil:libraryOrNil
                                        entrypointArgs:entrypointArgs]);
}

- (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil
libraryOrNil:(nullable NSString*)dartLibraryOrNil
entrypointArgs:
(nullable NSArray<NSString*>*)entrypointArgs {
    auto config = flutter::RunConfiguration::InferFromSettings(_settings);
    if (dartLibraryOrNil && entrypointOrNil) {
        config.SetEntrypointAndLibrary(std::string([entrypointOrNil UTF8String]),
                               std::string([dartLibraryOrNil UTF8String]));

    } else if (entrypointOrNil) {
        config.SetEntrypoint(std::string([entrypointOrNil UTF8String]));
    }

    if (entrypointArgs.count) {
        std::vector<std::string> cppEntrypointArgs;
        for (NSString* arg in entrypointArgs) {
            cppEntrypointArgs.push_back(std::string([arg UTF8String]));
        }
        config.SetEntrypointArgs(std::move(cppEntrypointArgs));
    }

    return config;
}

4、spawnWithEntrypoint

  • 📢 当引擎组里 self.engines.count > 0 的时候,则通过下面源码创建一个引擎,同时它会共享第一个引擎的部分资源~

  • 该方法用于创建一个新的 FlutterEngine 实例,该实例包含一个 FlutterShell 实例和一个 FlutterDartProject 实例,并且会通过调用 FlutterShell 实例的 Spawn 方法来生成一个新的 Shell 实例,这个新的 Shell 实例可以用于控制新的 Flutter 引擎实例。

  • 具体步骤如下:

  • 1.根据 _dartProject 获取运行配置 configuration。

  • 2.从当前 FlutterShell 实例 _shell 中获取平台视图,并通过 on_create_platform_view 方法创建一个新的 PlatformViewIOS 实例,同时通过 on_create_rasterizer 方法创建一个新的 Rasterizer 实例。

  • 3.调用 _shell 的 Spawn 方法创建一个新的 Shell 实例,并将步骤2中创建的 PlatformViewIOS 和 Rasterizer 实例传递给 Spawn 方法。

  • 4.创建一个新的 FlutterEngine 实例 result,将 threadHost、 profiler、_profiler_metrics 和 _isGpuDisabled 属性复制到 result 中。

  • 5.调用 result 的 setupShell:withVMServicePublication: 方法,将步骤3中创建的 Shell 实例作为参数传递给 setupShell 方法,同时传递一个 NO 值,表示不启用 VM service publication。

  • 6.返回 result。

总之,这段代码是用于创建一个新的 Flutter 引擎实例的,并通过调用 FlutterShell 实例的 Spawn 方法来生成一个新的 Shell 实例,这个新的 Shell 实例可以用于控制新的 Flutter 引擎实例。

- (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint
                           libraryURI:(/*nullable*/ NSString*)libraryURI
                         initialRoute:(/*nullable*/ NSString*)initialRoute
                       entrypointArgs:(/*nullable*/ NSArray<NSString*>*)entrypointArgs {
  NSAssert(_shell, @"Spawning from an engine without a shell (possibly not run).");
  FlutterEngine* result = [[FlutterEngine alloc] initWithName:_labelPrefix
                                                      project:_dartProject.get()
                                       allowHeadlessExecution:_allowHeadlessExecution];
  flutter::RunConfiguration configuration =
      [_dartProject.get() runConfigurationForEntrypoint:entrypoint
                                           libraryOrNil:libraryURI
                                         entrypointArgs:entrypointArgs];

  fml::WeakPtr<flutter::PlatformView> platform_view = _shell->GetPlatformView();
  FML_DCHECK(platform_view);
  // Static-cast safe since this class always creates PlatformViewIOS instances.
  flutter::PlatformViewIOS* ios_platform_view =
      static_cast<flutter::PlatformViewIOS*>(platform_view.get());
  std::shared_ptr<flutter::IOSContext> context = ios_platform_view->GetIosContext();
  FML_DCHECK(context);

  // Lambda captures by pointers to ObjC objects are fine here because the
  // create call is synchronous.
  flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
      [result, context](flutter::Shell& shell) {
        [result recreatePlatformViewController];
        return std::make_unique<flutter::PlatformViewIOS>(
            shell, context, result->_platformViewsController, shell.GetTaskRunners());
      };

  flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
      [](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };

  std::string cppInitialRoute;
  if (initialRoute) {
    cppInitialRoute = [initialRoute UTF8String];
  }

  std::unique_ptr<flutter::Shell> shell = _shell->Spawn(
      std::move(configuration), cppInitialRoute, on_create_platform_view, on_create_rasterizer);

  result->_threadHost = _threadHost;
  result->_profiler = _profiler;
  result->_profiler_metrics = _profiler_metrics;
  result->_isGpuDisabled = _isGpuDisabled;
  [result setupShell:std::move(shell) withVMServicePublication:NO];
  return [result autorelease];
}
  • 通过 spawnWithEntrypoint 源码我们知道了采用 Group 管理多引擎,为什么能大大降低内存原因:因为多个引擎的公用的是同一个 _shell 部分资源~

题外话

  • Flutter 网络请求是在哪个线程中执行的吗?会阻塞引发 App 卡顿吗?

参考