Flutter 的 main() 方法何时/何地从 Android 和 iOS 端调用?

Posted

技术标签:

【中文标题】Flutter 的 main() 方法何时/何地从 Android 和 iOS 端调用?【英文标题】:When/Where is Flutter's main() method called from the Android and iOS side of things? 【发布时间】:2021-08-10 14:13:19 【问题描述】:

如果我理解正确: 当我们运行一个使用 Flutter 构建的 android 应用程序时,它首先会转到 AndroidManfiest.xml 文件,查找 LAUNCHER 活动并启动它。此活动默认为MainActivity.kt,扩展FlutterActivity

但应用程序的 Flutter 部分从调用 main.dart 中的 main() 方法开始。

我的问题是,谁调用了这个main() 方法?

对于 Android,是 MainActivity 扩展 FlutterActivity 吗?还是FlutterActivity 本身有一些逻辑?还是完全有其他机制,我完全没有抓住重点?

同样的问题也适用于使用FlutterViewController 而不是FlutterActivityios

一个指向此源代码的链接,它只是阐明何时调用main() 会很棒。

【问题讨论】:

Dart 运行时(或调试版本中的 VM)执行 main。 Dart 运行时/VM 由 Flutter 引擎启动。 好的。你能指出在调试版本中触发 Dart VM 的代码吗?当我从 xcode 运行 iOS 应用程序时,必须在某些特定于 iOS 的文件中进行一些配置,这些配置的作用与 Swift/Obj-C iOS 应用程序不同,即触发这个 Dart VM。 我不记得确切的细节,但我认为您必须查看flutter_engine 中的 C++ 和 Objective-C 代码,例如FlutterDartProject.mm. 完美,这正是我好奇的地方。谢谢!在此处记录直接链接以供进一步阅读:iOS:github.com/flutter/engine/blob/… Android:github.com/flutter/engine/blob/… 【参考方案1】:

在 iOS 上

在您的 Flutter 项目 ios/Runner 目录中自动生成了一个 main.m 文件,该文件定义了 C program implements、int main(int argc, char* argv[]); 的常规主函数。

一个编译输出只能有一个main方法,当程序启动时编译器会立即运行。以下代码创建了一个UIApplicationMain,它“创建应用程序对象和应用程序委托并设置事件循环”:

#import "AppDelegate.h"

int main(int argc, char* argv[]) 
  @autoreleasepool 
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  

它是simpler in Swift,只需将AppDelegate 注释为@UIApplicationMain

AppDelegate 是启动 Flutter 的类,因为它扩展了 FlutterAppDelegate。当FlutterAppDelegate被实例化时,iOS会创建FlutterViewController,它会创建一个FlutterEngine。它创建FlutterViewController,因为FlutterViewController 是在Main.storyboard 中配置的,而Info.plist 已在应用程序中指定。所以从技术上讲,Flutter 应用程序是 Storyboard 应用程序?。

无论如何,当 iOS 创建故事板时,AppDelegate 上会设置 window 属性。您可以使用window.rootViewController 在 AppDelegate 中获取FlutterViewController。一个Objective-C++文件,FlutterViewController.mmsharedSetupWithProject方法使用[[FlutterEngine alloc]initWithName:...创建了一个FlutterEngine

- (void)sharedSetupWithProject:(nullable FlutterDartProject*)project
                  initialRoute:(nullable NSString*)initialRoute 
  // Need the project to get settings for the view. Initializing it here means
  // the Engine class won't initialize it later.
  if (!project) 
    project = [[[FlutterDartProject alloc] init] autorelease];
  
  FlutterView.forceSoftwareRendering = project.settings.enable_software_rendering;
  auto engine = fml::scoped_nsobject<FlutterEngine>[[FlutterEngine alloc]
                initWithName:@"io.flutter"
                     project:project
      allowHeadlessExecution:self.engineAllowHeadlessExecution
          restorationEnabled:[self restorationIdentifier] != nil];

  if (!engine) 
    return;
  

  _viewOpaque = YES;
  _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
  _engine = std::move(engine);
  _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
  [_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute];
  _engineNeedsLaunch = YES;
  _ongoingTouches.reset([[NSMutableSet alloc] init]);
  [self loadDefaultSplashScreenView];
  [self performCommonViewControllerInitialization];

最终,在FlutterEngine.mm 中,launchEngine 被调用,并带有入口点(dart 的主函数。)

- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil 
  // Launch the Dart application with the inferred run configuration.
  self.shell.RunEngine([_dartProject.get() runConfigurationForEntrypoint:entrypoint
                                                            libraryOrNil:libraryOrNil]);

Shell::RunEngine 是实现in shell.cc 的 C++ 函数。我现在要停在那里。这可能是事件循环开始的地方?,使用platform_runner-&gt;PostTask(...)


在安卓上

在生成的 Flutter 项目的 android 目录中,MainActivityAndroidManifest.xml 中声明为从主屏幕启动的应用程序。

当activity启动时,Android会调用activity的onCreate方法。因为MainActivity 扩展了FlutterActivity,所以调用了这个onCreate 方法。 FlutterActivityAndFragmentDelegate 被实例化,它的onAttach 方法被调用。最终,FlutterEngine 的 Java 表示使用以下命令创建:

    flutterEngine =
        new FlutterEngine(
            host.getContext(),
            host.getFlutterShellArgs().toArray(),
            /*automaticallyRegisterPlugins=*/ false,
            /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());

这使用FlutterJNI 与 C++ FlutterEngine 库进行通信。

/**
 * Interface between Flutter embedding's Java code and Flutter engine's C/C++ code.
 *
 * <p>Flutter's engine is built with C/C++. The Android Flutter embedding is responsible for
 * coordinating Android OS events and app user interactions with the C/C++ engine. Such coordination
 * requires messaging from an Android app in Java code to the C/C++ engine code. This communication
 * requires a JNI (Java Native Interface) API to cross the Java/native boundary.
 *

稍后,在FlutterActivityAndFragmentDelegate中,doInitialFlutterViewRun被调用,这会创建一个DartEntryPoint你的main方法再次,基于FlutterActivity的ActivityInfo。这将使用以下内容获取入口函数名称,但默认为"main"

  @NonNull
  public String getDartEntrypointFunctionName() 
    try 
      Bundle metaData = getMetaData();
      String desiredDartEntrypoint =
          metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
      return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
     catch (PackageManager.NameNotFoundException e) 
      return DEFAULT_DART_ENTRYPOINT;
    
  

然后,flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); 被调用。因此,您的主要方法已“被调用”。但这使用了FlutterJNI 和 FlutterEngine C++ 代码。首先调用flutterJNI.runBundleAndSnapshotFromLibrary,最后调用这个JNI原生方法:

  private native void nativeRunBundleAndSnapshotFromLibrary(
      long nativeShellHolderId,
      @NonNull String bundlePath,
      @Nullable String entrypointFunctionName,
      @Nullable String pathToEntrypointFunction,
      @NonNull AssetManager manager);

这个原生方法定义在platform_view_adroid_jni_impl.cc:

      
          .name = "nativeRunBundleAndSnapshotFromLibrary",
          .signature = "(JLjava/lang/String;Ljava/lang/String;"
                       "Ljava/lang/String;Landroid/content/res/AssetManager;)V",
          .fnPtr = reinterpret_cast<void*>(&RunBundleAndSnapshotFromLibrary),
      ,

RunBundleAndSnapshotFromLibrary 是 C++ 方法:

static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
                                            jobject jcaller,
                                            jlong shell_holder,
                                            jstring jBundlePath,
                                            jstring jEntrypoint,
                                            jstring jLibraryUrl,
                                            jobject jAssetManager) 
  auto asset_manager = std::make_shared<flutter::AssetManager>();

  asset_manager->PushBack(std::make_unique<flutter::APKAssetProvider>(
      env,                                             // jni environment
      jAssetManager,                                   // asset manager
      fml::jni::JavaStringToString(env, jBundlePath))  // apk asset dir
  );

  auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint);
  auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl);

  ANDROID_SHELL_HOLDER->Launch(asset_manager, entrypoint, libraryUrl);

AndroidShellHolder::Launch 在哪里:

void AndroidShellHolder::Launch(std::shared_ptr<AssetManager> asset_manager,
                                const std::string& entrypoint,
                                const std::string& libraryUrl) 
  if (!IsValid()) 
    return;
  

  asset_manager_ = asset_manager;
  auto config = BuildRunConfiguration(asset_manager, entrypoint, libraryUrl);
  if (!config) 
    return;
  
  shell_->RunEngine(std::move(config.value()));

就像 iOS 一样,Shell::RunEngine 被调用。

【讨论】:

以上是关于Flutter 的 main() 方法何时/何地从 Android 和 iOS 端调用?的主要内容,如果未能解决你的问题,请参考以下文章

iOS 何时何地为视图控制器获取服务器数据

BaseHTTPServer和SimpleHTTPServer有什么区别?何时何地使用它们?

在长过滤器链中何时何地使用 prepareForImageCapture

分裂何时何地发生?

何时何地使用 JMS?

Twig 渲染与包含 - 何时何地使用其中一个?