Summary
In this research issue I'll explore some of the Dart virtual machine internals (on the Android platform), more specifically, the application startup flow starting from the launching of the application's main activity to the Flutter engine initialization and dynamic symbol resolution in libapp.so. This knowledge is key in order to determine how the AOT code calls VM services that ultimately call Dart native libraries or Flutter libraries (for example, from dart:ui).
Context
Evidence
I will try to explain how Flutter starts up from the MainActivity launching to the stage where the Flutter engine on libflutter.so is loaded into memory and called, describing how and where libapp.so is loaded into memory, how the Dart virtual machine is initialized by the Flutter engine's shell, how the Flutter engine passes it the information about Dart snapshots, and a few other things. Caution: the process described in the following text is mostly Android-specific, so do not extrapolate this behavior to other platforms. Android on itself is an odd case, given the embedder code is written in the language most commonly used in the platform: Java.
- The
// [...] comments mean that some parts of the function or code block were omitted, as to not lose focus on the more important lines.
- I'll try to link the source file where the definition of a class is the first time I mention it, I might forget to do this.
- I'll be re-reading these notes, adding more details and going in-depth about certain aspects of the flow that are potentially important, so the version you are reading now is probably not final (2026-02-28).
- If I miss anything or you have proposed corrections or improvements to the clarity of the writing, please feel free to let me know.
Embedder
All platforms wishing to launch a Flutter application must first implement an embedder. The embedder, simply put, is a platform-specific bootstrapping code that's in charge of providing the Flutter engine with the supplies it needs in order to get its process started. As I just mentioned, this embedder code is written in Java for the Android platform, and you can find the entirety of its source in Flutter's repository. When you create a new Flutter project, you will notice that Android-specific applications have a have a small portion of Java/Kotlin code that looks like this:
package com.example.research_min_app_flutterdec
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
The class's implementation is empty because all of its logic is is on the FlutterActivity class defined in Flutter's source. Normally, one does not need to override anything as this class implements everything the Android Runtime needs to launch the application with a default intent, and getting the Flutter engine to run alongside it. This class, as you might have guessed, is also the entry point into Flutter's embedder for Android, and here's the function called when the class is loaded and executed:
FlutterActivity.java ➥
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
// [...]
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);
// [...]
}
This function will launch the application's splash art, switch views... etc. But the important part is the creation of the delegate instance, which is a member variable of the activity class. Both the embedding of a Flutter view as a whole activity or a Fragment are handled by the delegate of type FlutterActivityAndFragmentDelegate. It contains the main logic for Activity-related handlers such as onResume, onStart, etc... It is called "delegate" precisely because of that. Both FragmentActivity and FlutterActivity call it instead of implementing the logic in their own methods (though here I will only focus on FlutterActivity's flow), you can see that here:
FlutterActivity.java
@Override
protected void onStart() {
super.onStart();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
if (stillAttachedForEvent("onStart")) {
delegate.onStart();
}
}
// [...]
@Override
protected void onSaveInstanceState(Bundle outState) {
// [...]
}
Now, in the FlutterActivityAndFragmentDelegate class we'll find multiple important method definitions. But let us take a look at onAttach first since it's being invoked right after the object is constructed. In case you are wondering, the constructor itself doesn't do much at all, it just initializes the class's members:
FlutterActivityAndFragmentDelegate.java
FlutterActivityAndFragmentDelegate(@NonNull Host host, @Nullable FlutterEngineGroup engineGroup) {
this.host = host;
this.isFirstFrameRendered = false;
this.engineGroup = engineGroup;
}
For the sake of awareness, keep in mind that host is a member that holds a reference to the FlutterActivity, i.e the activity acting as host of the delegate instance, from which both its constructor and onAttach method were called. Now, the next method to look into right after the constructor is naturally, onAttach. There's a look to unpack from onAttach, but taking a look at one of the very first lines, we come across one of the most important parts of the flow, were the FlutterEngine is initialized for the first time:
FlutterActivityAndFragmentDelegate.java
void onAttach(@NonNull Context context) {
ensureAlive(); // 1
if (flutterEngine == null) {
setUpFlutterEngine(); // 2
}
if (host.shouldAttachEngineToActivity()) // 3
{
Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this delegate.");
flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle()); // 4
}
final Activity hostActivity = host.getActivity();
platformPlugin = host.providePlatformPlugin(hostActivity, flutterEngine);
sensitiveContentPlugin = host.provideSensitiveContentPlugin(hostActivity, flutterEngine);
host.configureFlutterEngine(flutterEngine); // 5
isAttached = true;
}
I included all of the function's body as I'll be covering all functions called here. I've marked each relevant line with a number to more easily describe what they do on a list:
ensureAlive will verify that the host reference isn't null before proceeding, throwing an error if it is.
- This is the next part of the flow, so I'll cover this separately.
- This function is hardcoded to always return
true.
- Calls the
FlutterEngineConnectionRegistry's attachToActivity method, which activates the PlatformViewController and attaches default ActivityAware plugins to the engine.
- Adds and initializes user-defined plugins to the
FlutterEngine through runtime inspection (reflection).
Now, setUpFlutterEngine is a bit harder to dissect, given it's the most important function call here by far. As the name suggests, this function creates a FlutterEngine object that is the representation of our current Flutter environment (on the Java side), and whose constructor performs multiple varied tasks. Let us skip over setUpFlutterEngine which just checks whether the FlutterActivity was called with certain intents, and if none matches, falls back to creating a normal FlutterEngine, by calling the createAndRunEngine method in the FlutterEngineGroup class, which in turn calls createEngine, which is just a wrapper to the massive FlutterEngine constructor. At this point, we are nearing the last of Java code that we see in the initialization flow before moving to C++. Let us now see part of what it does:
FlutterEngine.java
public FlutterEngine(
@NonNull Context context,
@Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins,
boolean waitForRestorationData,
@Nullable FlutterEngineGroup group) {
// [...]
FlutterInjector injector = FlutterInjector.instance(); // 1
if (flutterJNI == null) {
flutterJNI = injector.getFlutterJNIFactory().provideFlutterJNI(); //2
}
this.flutterJNI = flutterJNI; // 2
this.dartExecutor = new DartExecutor(flutterJNI, assetManager, engineId); // 3
this.dartExecutor.onAttachedToJNI(); // 3
// [...]
accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); // 4
// [...]
textInputChannel = new TextInputChannel(dartExecutor); // 4
if (flutterLoader == null) {
flutterLoader = injector.flutterLoader(); // 5
}
if (!flutterJNI.isAttached()) {
flutterLoader.startInitialization(context.getApplicationContext()); // 6
flutterLoader.ensureInitializationComplete(context, dartVmArgs); // 7
}
// [...]
if (!flutterJNI.isAttached()) {
attachToJni(); // 8
}
this.renderer = new FlutterRenderer(flutterJNI); // 9
// [...]
this.platformViewsControllerDelegator =
new PlatformViewsControllerDelegator(platformViewsController, platformViewsController2); // 10
this.pluginRegistry =
new FlutterEngineConnectionRegistry(
context.getApplicationContext(), this, flutterLoader, group); // 11
// [...]
}
Notice how despite removing a great portion of the constructor, it remains a formidable piece of code that does quite a lot. Again, I've marked each line with a number that corresponds to the following list's entry that describes what the line does:
-
A FlutterInjection instance is obtained. This creates a FlutterJNI.Factory and a FlutterLoader instances, as members of the FlutterInjection instance. They are later used in the following steps. The FlutterJNI.Factory instance is used to provide the JNI interface to all objects that will have to contact the engine's native functions.
-
A FlutterJNI instance is created and set as instance member for the FlutterEngine.
-
The dartExecutor member is initialized to a new DartExecutor instance, whose constructor gets a reference to the FlutterJNI created in 2., this class, in Dart's developers own words "configures, bootstraps, and starts executing Dart code." a DartMessenger instance and DefaultBinaryMessenger instance are created. The DartMessenger acts as a bi-directional communication tunnel between Dart and Java/Kotlin, and implements the BinaryMessenger and PlatformChannelMessenger interfaces. The former acts as message tunnel going from Android to Dart, the latter serves the same purpose for the opposite communication direction. After the constructor returns, the onAttachedToJNI method is called. This method will in turn initialize the JNIs "platform channel" (also set to the DartMessenger instance), in charge of receiving and processing asynchronous messages from Dart code.
// called by the Dart to send messages to Java/Kotlin
public void handlePlatformMessage(
@NonNull final String channel,
ByteBuffer message,
final int replyId,
final long messageData) {
// [...]
}
// called by Dart to reply to messages sent through the binarymessenger
private void handlePlatformMessageResponse(int replyId, ByteBuffer reply) {
if (platformMessageHandler != null) {
platformMessageHandler.handlePlatformMessageResponse(replyId, reply);
}
}
- Multiple channels are created. These channels can be Dart to Java, Java to Dart or bi-directional. They implement mechanisms to handle common events that might occur during runtime, such as Java notifying keypresses to Dart, or Dart asking Java to play a certain sound with the native API. These channels will use the
MethodChannel interface to send and/or receive messages to/from Dart, the receiving is done by registering a handler for when Dart sends a message through the named channel (by calling the aforementioned handlePlatformMessage). The channels might also register a BasicMessageChannel in the case they want to establish a simple mono-directional channel. An example of the usage of a MethodChannel for bidirectional1 communication and BasicMessageChannel for mono-directional communication in NavigationChannel and KeyEventChannel definitions:
public NavigationChannel(@NonNull DartExecutor dartExecutor) {
this.channel = new MethodChannel(dartExecutor, "flutter/navigation", JSONMethodCodec.INSTANCE);
channel.setMethodCallHandler(defaultHandler);
}
public KeyEventChannel(@NonNull BinaryMessenger binaryMessenger) {
this.channel =
new BasicMessageChannel<>(binaryMessenger, "flutter/keyevent", JSONMessageCodec.INSTANCE);
}
-
A FlutterLoader instance is obtained. This instance is in charge of the loading of native libraries (libflutter.so) in this case, and multiple other things that will be described in the following entries.
-
The startInitialization function is called. This function performs a few actions but the most important of them is the loading of the libapp.so library. It does this by invoking FlutterJNI.loadLibrary (which triggers the execution of JNI_OnLoad) inside a Callable object that's scheduled to run on a separate thread.
-
The ensureInitializationComplete function is called. This function is massive and likely the largest function in this flow so far, by a long shot. I will try to compress what it does as much as possible, so this function:
- Waits (blocks) for the completion of the
Future task that startInitialization set as result of the callable object.
- Creates a list of arguments that will be passed to the native
FlutterMain::Init2, there are more than 20+ flags, including, for example, the name of the snapshot-containing binary, which by default is libapp.so.
- Invokes
FlutterJNI.Init, which ultimately sets a few global jclass and jfieldID, registers a callback for when the Dart VM is initialzied, and selects the rendering API (OpenGL, Vulkan, etc...).
-
The attachToJni function will call FlutterJNI.attachToNative, which in turn invokes nativeAttach whose implementation is in C++, AttachJNI2. This function will create a smart pointer reference to an AndroidShellHolder, whose constructor will ultimately invoke Shell::Create and this function is the one that bootstraps the Dart VM inside the Flutter engine! (more on that flow on the second part, given it needs an analysis on its own):
static jlong AttachJNI(JNIEnv* env, jclass clazz, jobject flutterJNI) {
fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
std::shared_ptr<PlatformViewAndroidJNI> jni_facade =
std::make_shared<PlatformViewAndroidJNIImpl>(java_object);
auto shell_holder = std::make_unique<AndroidShellHolder>(
FlutterMain::Get().GetSettings(), jni_facade,
FlutterMain::Get().GetAndroidRenderingAPI());
if (shell_holder->IsValid()) {
return reinterpret_cast<jlong>(shell_holder.release());
} else {
return 0;
}
}
-
The renderer is created. At some point, a Surface will be passed to it (in theory, it should be after calling onCreateView from FlutterActivity.onCreate), after which the connectSurfaceToRenderer method is called, and finally startRenderingToSurface, which does exactly what it says.
-
The PlatformViewsControllerDelegator is initialized, and two PlatformViewsController passed as arguments. This is used to embed native views to onto the Flutter application, read more about this here if you are curious, but this isn't needed at all to understand Flutter's bootstrap flow.
-
Finally, the plugin registry FlutterEngineConnectionRegistry is created. The plugin registry is used to register Flutter plugins from the application's code, by calling flutterEngine.getPlugins().add(...), inside the pre-generated registerWith function, which as you might recall, is invoked from configureFlutterEngine.
1. Notice that MethodChannel can be used to both receive and send with handlePlatformMessage and dispatchPlatformMessage functions in FlutterJNI, respectively).
2. Both FlutterMain::Init and AttachJNI functions are made available to Java by JNI_OnLoad, when libflutter.so is loaded.
After this function returns we have an initialized JNI, Dart virtual machine, renderer, and of course, a fresh FlutterEngine that will be used as bridge from the native code to the C++ code in libflutter. Unwinding a bit more, let us remember that we initially were inside FlutterActivity.onCreate->FlutterActivityAndFragmentDelegate.onAttach->FlutterEngine (constructor). After all these functions return, FlutterActivity.onStart -> FlutterActivityAndFragmentDelegate.onStart is called, which in turn calls the last part of our Flutter app initialization flow, the doInitialFlutterViewRun.
FlutterActivityAndFragmentDelegate.java
private void doInitialFlutterViewRun() {
// [...]
String initialRoute = host.getInitialRoute(); // 1
// [...]
@Nullable String libraryUri = host.getDartEntrypointLibraryUri(); // 2
// [...]
flutterEngine.getNavigationChannel().setInitialRoute(initialRoute); // 3
String appBundlePathOverride = host.getAppBundlePath();
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath(); // 4
}
// Configure the Dart entrypoint and execute it.
DartExecutor.DartEntrypoint entrypoint =
libraryUri == null
? new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName())
: new DartExecutor.DartEntrypoint(
appBundlePathOverride, libraryUri, host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint, host.getDartEntrypointArgs()); // 5
}
This function will ultimately call executeDartEntrypoint, a function in charge of transferring control to our Dart main function (or the function chosen as entry-point), thus finally completing the initialization process. You see this function is a bit long, so here's the explanation of the relevant steps before the calling of the entry-point:
- Flutter can use a named-route based navigation system similar to many other UI frameworks. The initial route (do not mistake for entry-point) is the first screen that Flutter will render by default,
/ is the default route, if nothing provided.
- This obtains the Dart URI for whatever source file the entry-point function is defined at, for example
package:myapp/main.dart.
- Uses the
NavigationChannel (initialized during the FlutterEngine constructor) to tell Flutter to set the initial route.
- The app bundle is path is set. This is
flutter_assets by default.
- A
DartEntrypoint instance is created. If libraryUri is null, the entry-point function will be the default value, which is DEFAULT_DART_ENTRYPOINT = 'main', and the library URI will be considered to be the file where the main function is defined. Finally, the executeDartEntrypoint is called passing the DartEntrypoint instance and arguments, which can be accessed by adding a parameter of type List<String> to the entry-point function.
Summary
In this research issue I'll explore some of the Dart virtual machine internals (on the Android platform), more specifically, the application startup flow starting from the launching of the application's main activity to the Flutter engine initialization and dynamic symbol resolution in
libapp.so. This knowledge is key in order to determine how the AOT code calls VM services that ultimately call Dart native libraries or Flutter libraries (for example, fromdart:ui).Context
Evidence
I will try to explain how Flutter starts up from the
MainActivitylaunching to the stage where the Flutter engine onlibflutter.sois loaded into memory and called, describing how and wherelibapp.sois loaded into memory, how the Dart virtual machine is initialized by the Flutter engine's shell, how the Flutter engine passes it the information about Dartsnapshots, and a few other things. Caution: the process described in the following text is mostly Android-specific, so do not extrapolate this behavior to other platforms. Android on itself is an odd case, given the embedder code is written in the language most commonly used in the platform: Java.// [...]comments mean that some parts of the function or code block were omitted, as to not lose focus on the more important lines.Embedder
All platforms wishing to launch a Flutter application must first implement an embedder. The embedder, simply put, is a platform-specific bootstrapping code that's in charge of providing the Flutter engine with the supplies it needs in order to get its process started. As I just mentioned, this embedder code is written in Java for the Android platform, and you can find the entirety of its source in Flutter's repository. When you create a new Flutter project, you will notice that Android-specific applications have a have a small portion of Java/Kotlin code that looks like this:
The class's implementation is empty because all of its logic is is on the
FlutterActivityclass defined in Flutter's source. Normally, one does not need to override anything as this class implements everything the Android Runtime needs to launch the application with a default intent, and getting the Flutter engine to run alongside it. This class, as you might have guessed, is also the entry point into Flutter's embedder for Android, and here's the function called when the class is loaded and executed:FlutterActivity.java➥This function will launch the application's splash art, switch views... etc. But the important part is the creation of the
delegateinstance, which is a member variable of the activity class. Both the embedding of a Flutter view as a whole activity or a Fragment are handled by thedelegateof typeFlutterActivityAndFragmentDelegate. It contains the main logic forActivity-related handlers such asonResume,onStart, etc... It is called "delegate" precisely because of that. BothFragmentActivityandFlutterActivitycall it instead of implementing the logic in their own methods (though here I will only focus onFlutterActivity's flow), you can see that here:FlutterActivity.javaNow, in the
FlutterActivityAndFragmentDelegateclass we'll find multiple important method definitions. But let us take a look atonAttachfirst since it's being invoked right after the object is constructed. In case you are wondering, the constructor itself doesn't do much at all, it just initializes the class's members:FlutterActivityAndFragmentDelegate.javaFor the sake of awareness, keep in mind that
hostis a member that holds a reference to theFlutterActivity, i.e the activity acting as host of the delegate instance, from which both its constructor andonAttachmethod were called. Now, the next method to look into right after the constructor is naturally,onAttach. There's a look to unpack fromonAttach, but taking a look at one of the very first lines, we come across one of the most important parts of the flow, were theFlutterEngineis initialized for the first time:FlutterActivityAndFragmentDelegate.javaI included all of the function's body as I'll be covering all functions called here. I've marked each relevant line with a number to more easily describe what they do on a list:
ensureAlivewill verify that thehostreference isn't null before proceeding, throwing an error if it is.true.FlutterEngineConnectionRegistry'sattachToActivitymethod, which activates thePlatformViewControllerand attaches defaultActivityAwareplugins to the engine.FlutterEnginethrough runtime inspection (reflection).Now,
setUpFlutterEngineis a bit harder to dissect, given it's the most important function call here by far. As the name suggests, this function creates aFlutterEngineobject that is the representation of our current Flutter environment (on the Java side), and whose constructor performs multiple varied tasks. Let us skip oversetUpFlutterEnginewhich just checks whether theFlutterActivitywas called with certain intents, and if none matches, falls back to creating a normalFlutterEngine, by calling thecreateAndRunEnginemethod in theFlutterEngineGroupclass, which in turn callscreateEngine, which is just a wrapper to the massiveFlutterEngineconstructor. At this point, we are nearing the last of Java code that we see in the initialization flow before moving to C++. Let us now see part of what it does:FlutterEngine.javaNotice how despite removing a great portion of the constructor, it remains a formidable piece of code that does quite a lot. Again, I've marked each line with a number that corresponds to the following list's entry that describes what the line does:
A
FlutterInjectioninstance is obtained. This creates aFlutterJNI.Factoryand aFlutterLoaderinstances, as members of theFlutterInjectioninstance. They are later used in the following steps. TheFlutterJNI.Factoryinstance is used to provide the JNI interface to all objects that will have to contact the engine's native functions.A
FlutterJNIinstance is created and set as instance member for theFlutterEngine.The
dartExecutormember is initialized to a newDartExecutorinstance, whose constructor gets a reference to theFlutterJNIcreated in2., this class, in Dart's developers own words "configures, bootstraps, and starts executing Dart code." aDartMessengerinstance andDefaultBinaryMessengerinstance are created. TheDartMessengeracts as a bi-directional communication tunnel between Dart and Java/Kotlin, and implements theBinaryMessengerandPlatformChannelMessengerinterfaces. The former acts as message tunnel going from Android to Dart, the latter serves the same purpose for the opposite communication direction. After the constructor returns, theonAttachedToJNImethod is called. This method will in turn initialize the JNIs "platform channel" (also set to theDartMessengerinstance), in charge of receiving and processing asynchronous messages from Dart code.MethodChannelinterface to send and/or receive messages to/from Dart, the receiving is done by registering a handler for when Dart sends a message through the named channel (by calling the aforementionedhandlePlatformMessage). The channels might also register aBasicMessageChannelin the case they want to establish a simple mono-directional channel. An example of the usage of aMethodChannelfor bidirectional1 communication andBasicMessageChannelfor mono-directional communication inNavigationChannelandKeyEventChanneldefinitions:A
FlutterLoaderinstance is obtained. This instance is in charge of the loading of native libraries (libflutter.so) in this case, and multiple other things that will be described in the following entries.The
startInitializationfunction is called. This function performs a few actions but the most important of them is the loading of thelibapp.solibrary. It does this by invokingFlutterJNI.loadLibrary(which triggers the execution ofJNI_OnLoad) inside aCallableobject that's scheduled to run on a separate thread.The
ensureInitializationCompletefunction is called. This function is massive and likely the largest function in this flow so far, by a long shot. I will try to compress what it does as much as possible, so this function:Futuretask thatstartInitializationset as result of the callable object.FlutterMain::Init2, there are more than 20+ flags, including, for example, the name of the snapshot-containing binary, which by default islibapp.so.FlutterJNI.Init, which ultimately sets a few globaljclassandjfieldID, registers a callback for when the Dart VM is initialzied, and selects the rendering API (OpenGL, Vulkan, etc...).The
attachToJnifunction will callFlutterJNI.attachToNative, which in turn invokesnativeAttachwhose implementation is in C++,AttachJNI2. This function will create a smart pointer reference to anAndroidShellHolder, whose constructor will ultimately invokeShell::Createand this function is the one that bootstraps the Dart VM inside the Flutter engine! (more on that flow on the second part, given it needs an analysis on its own):The renderer is created. At some point, a
Surfacewill be passed to it (in theory, it should be after callingonCreateViewfromFlutterActivity.onCreate), after which theconnectSurfaceToRenderermethod is called, and finallystartRenderingToSurface, which does exactly what it says.The
PlatformViewsControllerDelegatoris initialized, and twoPlatformViewsControllerpassed as arguments. This is used to embed native views to onto the Flutter application, read more about this here if you are curious, but this isn't needed at all to understand Flutter's bootstrap flow.Finally, the plugin registry
FlutterEngineConnectionRegistryis created. The plugin registry is used to register Flutter plugins from the application's code, by callingflutterEngine.getPlugins().add(...), inside the pre-generatedregisterWithfunction, which as you might recall, is invoked fromconfigureFlutterEngine.1. Notice that
MethodChannelcan be used to both receive and send withhandlePlatformMessageanddispatchPlatformMessagefunctions inFlutterJNI, respectively).2. Both
FlutterMain::InitandAttachJNIfunctions are made available to Java byJNI_OnLoad, whenlibflutter.sois loaded.After this function returns we have an initialized JNI, Dart virtual machine, renderer, and of course, a fresh
FlutterEnginethat will be used as bridge from the native code to the C++ code inlibflutter. Unwinding a bit more, let us remember that we initially were insideFlutterActivity.onCreate->FlutterActivityAndFragmentDelegate.onAttach->FlutterEngine (constructor). After all these functions return,FlutterActivity.onStart->FlutterActivityAndFragmentDelegate.onStartis called, which in turn calls the last part of our Flutter app initialization flow, thedoInitialFlutterViewRun.FlutterActivityAndFragmentDelegate.javaThis function will ultimately call
executeDartEntrypoint, a function in charge of transferring control to our Dartmainfunction (or the function chosen as entry-point), thus finally completing the initialization process. You see this function is a bit long, so here's the explanation of the relevant steps before the calling of the entry-point:/is the default route, if nothing provided.package:myapp/main.dart.NavigationChannel(initialized during theFlutterEngineconstructor) to tell Flutter to set the initial route.flutter_assetsby default.DartEntrypointinstance is created. IflibraryUriis null, the entry-point function will be the default value, which isDEFAULT_DART_ENTRYPOINT='main', and the library URI will be considered to be the file where themainfunction is defined. Finally, theexecuteDartEntrypointis called passing theDartEntrypointinstance and arguments, which can be accessed by adding a parameter of typeList<String>to the entry-point function.