1. How is main() called in FlutterUI?
2. How to modify the name of main() function in FlutterUI to call a custom entry function
flutterenginerun.png
On the Android side IO/flutter in the process of start/app/FlutterActivityDelegate initialized class calls onCreate, have been analyzed when FlutterEngine initialization FlutterEngine initialization process, Android SurferView has also been added to the Flutter engine, but flutterengine has not been started yet. Let’s look at how Flutter actually loads the Dart code
public void onCreate(Bundle savedInstanceState) { if (VERSION.SDK_INT >= 21) { Window window = this.activity.getWindow(); window.addFlags(-2147483648); window.setStatusBarColor(1073741824); window.getDecorView().setSystemUiVisibility(1280); } String[] args = getArgsFromIntent(this.activity.getIntent()); FlutterMain.ensureInitializationComplete(this.activity.getApplicationContext(), args); this.flutterView = this.viewFactory.createFlutterView(this.activity); if (this.flutterView == null) { FlutterNativeView nativeView = this.viewFactory.createFlutterNativeView(); this.flutterView = new FlutterView(this.activity, (AttributeSet)null, nativeView); this.flutterView.setLayoutParams(matchParent); this.activity.setContentView(this.flutterView); this.launchView = this.createLaunchView(); if (this.launchView ! = null) { this.addLaunchView(); } // Start loading the Dart code if (! this.loadIntent(this.activity.getIntent())) { String appBundlePath = FlutterMain.findAppBundlePath(); if (appBundlePath ! = null) { this.runBundle(appBundlePath); Java private Boolean loadIntent(Intent Intent) {String action = intent.getAction(); if ("android.intent.action.RUN".equals(action)) { String route = intent.getStringExtra("route"); String appBundlePath = intent.getDataString(); if (appBundlePath == null) { appBundlePath = FlutterMain.findAppBundlePath(); } if (route ! = null) { this.flutterView.setInitialRoute(route); } this.runBundle(appBundlePath); return true; } else { return false; }}Copy the code
Engine: The entry of the FlutterIU layer for run parameters is set here. The following places are where we change the flutter parameters by configuration: 1. The location of the code extracted from Apk; 2. This means that we can arbitrarily change the entry function name of the Flutter code, Private static String sFlutterAssetsDir = private static String sFlutterAssetsDir ="flutter_assets";
private void runBundle(String appBundlePath) {
if(! this.flutterView.getFlutterNativeView().isApplicationRunning()) { FlutterRunArguments args = new FlutterRunArguments(); args.bundlePath = appBundlePath; // The entry to the main function of the Flutter code can be changed arbitrarily. Args.entrypoint ="main"; this.flutterView.runFromBundle(args); }}Copy the code
nativeRunBundleAndSnapshotFromLibrary
Native methods are called in FlutterJNI to call native methods of FlutterEngine
@UiThread
public void runBundleAndSnapshotFromLibrary(@NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @NonNull AssetManager assetManager) {
this.ensureRunningOnMainThread();
this.ensureAttachedToNative();
this.nativeRunBundleAndSnapshotFromLibrary(this.nativePlatformViewId, bundlePath, entrypointFunctionName, pathToEntrypointFunction, assetManager);
}
Copy the code
PlatformViewAndroid initialization
When Android starts up, it loads the Flutter library with system. library and automatically calls JNI_OnLoad to initialize it
// This is called by the VM when the shared library is first loaded.
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// Initialize the Java VM.
fml::jni::InitJavaVM(vm);
JNIEnv* env = fml::jni::AttachCurrentThread();
bool result = false;
// Register FlutterMain.
result = shell::FlutterMain::Register(env);
FML_CHECK(result);
// Register PlatformView
result = shell::PlatformViewAndroid::Register(env);
FML_CHECK(result);
// Register VSyncWaiter.
result = shell::VsyncWaiterAndroid::Register(env);
FML_CHECK(result);
return JNI_VERSION_1_4;
}
Copy the code
Platform_view_android_jni registered local method of the code from the engine/SRC/flutter/shell/platform/android/platform_view_android_jni. Cc registered native code callback logic
{/ / / this is what we really care about FlutterEngine invocation logic. In the process of start name = "nativeRunBundleAndSnapshotFromLibrary." .signature = "(J[Ljava/lang/String;Ljava/lang/String;" "Ljava/lang/String;Landroid/content/res/AssetManager; )V", .fnPtr = reinterpret_cast<void*>(&shell::RunBundleAndSnapshotFromLibrary), },Copy the code
bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
// Start of methods from FlutterNativeView
{
.name = "nativeAttach",
.signature = "(Lio/flutter/embedding/engine/FlutterJNI; Z)J",
.fnPtr = reinterpret_cast<void*>(&shell::AttachJNI),
},
{
.name = "nativeDestroy",
.signature = "(J)V",
.fnPtr = reinterpret_cast<void*>(&shell::DestroyJNI),
},
{ /// This is the logic we really care about when FlutterEngine is started
.name = "nativeRunBundleAndSnapshotFromLibrary",
.signature = "(J[Ljava/lang/String;Ljava/lang/String;"
"Ljava/lang/String; Landroid/content/res/AssetManager;) V",
.fnPtr =
reinterpret_cast<void*>(&shell::RunBundleAndSnapshotFromLibrary),
},
{
.name = "nativeGetObservatoryUri",
.signature = "()Ljava/lang/String;",
.fnPtr = reinterpret_cast<void*>(&shell::GetObservatoryUri),
},
{
.name = "nativeDispatchEmptyPlatformMessage",
.signature = "(JLjava/lang/String; I)V",
.fnPtr =
reinterpret_cast<void*>(&shell::DispatchEmptyPlatformMessage),
},
{
.name = "nativeDispatchPlatformMessage",
.signature = "(JLjava/lang/String; Ljava/nio/ByteBuffer; II)V",
.fnPtr = reinterpret_cast<void*>(&shell::DispatchPlatformMessage),
},
{
.name = "nativeInvokePlatformMessageResponseCallback",
.signature = "(JILjava/nio/ByteBuffer; I)V",
.fnPtr = reinterpret_cast<void*>(
&shell::InvokePlatformMessageResponseCallback),
},
{
.name = "nativeInvokePlatformMessageEmptyResponseCallback",
.signature = "(JI)V",
.fnPtr = reinterpret_cast<void*>(
&shell::InvokePlatformMessageEmptyResponseCallback),
},
// Start of methods from FlutterView
{
.name = "nativeGetBitmap",
.signature = "(J)Landroid/graphics/Bitmap;",
.fnPtr = reinterpret_cast<void*>(&shell::GetBitmap),
},
{
.name = "nativeSurfaceCreated",
.signature = "(JLandroid/view/Surface;) V",
.fnPtr = reinterpret_cast<void*>(&shell::SurfaceCreated),
},
{
.name = "nativeSurfaceChanged",
.signature = "(JII)V",
.fnPtr = reinterpret_cast<void*>(&shell::SurfaceChanged),
},
{
.name = "nativeSurfaceDestroyed",
.signature = "(J)V",
.fnPtr = reinterpret_cast<void*>(&shell::SurfaceDestroyed),
},
{
.name = "nativeSetViewportMetrics",
.signature = "(JFIIIIIIIIII)V",
.fnPtr = reinterpret_cast<void*>(&shell::SetViewportMetrics),
},
{
.name = "nativeDispatchPointerDataPacket",
.signature = "(JLjava/nio/ByteBuffer; I)V",
.fnPtr = reinterpret_cast<void*>(&shell::DispatchPointerDataPacket),
},
{
.name = "nativeDispatchSemanticsAction",
.signature = "(JIILjava/nio/ByteBuffer; I)V",
.fnPtr = reinterpret_cast<void*>(&shell::DispatchSemanticsAction),
},
{
.name = "nativeSetSemanticsEnabled",
.signature = "(JZ)V",
.fnPtr = reinterpret_cast<void*>(&shell::SetSemanticsEnabled),
},
{
.name = "nativeSetAccessibilityFeatures",
.signature = "(JI)V",
.fnPtr = reinterpret_cast<void*>(&shell::SetAccessibilityFeatures),
},
{
.name = "nativeGetIsSoftwareRenderingEnabled",
.signature = "()Z",
.fnPtr = reinterpret_cast<void*>(&shell::GetIsSoftwareRendering),
},
{
.name = "nativeRegisterTexture",
.signature = "(JJLandroid/graphics/SurfaceTexture;) V",
.fnPtr = reinterpret_cast<void*>(&shell::RegisterTexture),
},
{
.name = "nativeMarkTextureFrameAvailable",
.signature = "(JJ)V",
.fnPtr = reinterpret_cast<void*>(&shell::MarkTextureFrameAvailable),
},
{
.name = "nativeUnregisterTexture",
.signature = "(JJ)V",
.fnPtr = reinterpret_cast<void*>(&shell::UnregisterTexture),
},
};
Copy the code
RunBundleAndSnapshotFromLibrary
1. Load the configuration file
2. Start FlutterEngine
3. Set the FlutterEngine to actually load the Dart code and execute the entry file for the first FlutterUI framework using the parameters passed in from Android described earlier
static void RunBundleAndSnapshotFromLibrary(JNIEnv* env, jobject jcaller, jlong shell_holder, jobjectArray jbundlepaths, jstring jEntrypoint, jstring jLibraryUrl, jobject jAssetManager) { auto asset_manager = std::make_shared<blink::AssetManager>(); for (const auto& bundlepath : fml::jni::StringArrayToVector(env, jbundlepaths)) { if (bundlepath.empty()) { continue; } // If we got a bundle path, attempt to use that as a directory asset // bundle or a zip asset bundle. const auto file_ext_index = bundlepath.rfind("."); if (bundlepath.substr(file_ext_index) == ".zip") { asset_manager->PushBack(std::make_unique<blink::ZipAssetStore>( bundlepath, "assets/flutter_assets")); } else { asset_manager->PushBack( std::make_unique<blink::DirectoryAssetBundle>(fml::OpenDirectory( bundlepath.c_str(), false, fml::FilePermission::kRead))); // Use the last path component of the bundle path to determine the // directory in the APK assets. const auto last_slash_index = bundlepath.rfind("/", bundlepath.size()); if (last_slash_index ! = std::string::npos) { auto apk_asset_dir = bundlepath.substr( last_slash_index + 1, bundlepath.size() - last_slash_index); asset_manager->PushBack(std::make_unique<blink::APKAssetProvider>( env, // jni environment jAssetManager, // asset manager std::move(apk_asset_dir)) // apk asset dir ); } } } auto isolate_configuration = CreateIsolateConfiguration(*asset_manager); if (! isolate_configuration) { FML_DLOG(ERROR) << "Isolate configuration could not be determined for engine launch."; return; } RunConfiguration config(std::move(isolate_configuration), std::move(asset_manager)); { auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl); if ((entrypoint.size() > 0) && (libraryUrl.size() > 0)) { config.SetEntrypointAndLibrary(std::move(entrypoint), std::move(libraryUrl)); } else if (entrypoint.size() > 0) { config.SetEntrypoint(std::move(entrypoint)); } } ANDROID_SHELL_HOLDER->Launch(std::move(config)); }Copy the code
AndroidShellHolder starts the FlutterEngine in the UI thread
void AndroidShellHolder::Launch(RunConfiguration config) {
if(! IsValid()) {return;
}
shell_->GetTaskRunners().GetUITaskRunner()->PostTask(
fml::MakeCopyable([engine = shell_->GetEngine(), //
config = std::move(config) //] ()mutable {
FML_LOG(INFO) << "Attempting to launch engine configuration...";
if(! engine || engine->Run(std::move(config)) ==
shell::Engine::RunStatus::Failure) {
FML_LOG(ERROR) << "Could not launch engine in configuration.";
} else {
FML_LOG(INFO) << "Isolate for engine configuration successfully "
"started and run."; }})); }Copy the code
Engine::Run
1. PrepareAndLaunchIsolate preprocessing the runtime environment
2. Obtain the initialized GetRootIsolate
3. Obtain DartState status
4. Add AddIsolateShutdownCallback callback methods
Engine::RunStatus Engine::Run(RunConfiguration configuration) { if (! configuration.IsValid()) { FML_LOG(ERROR) << "Engine run configuration was invalid."; return RunStatus::Failure; } auto isolate_launch_status = PrepareAndLaunchIsolate(std::move(configuration)); if (isolate_launch_status == Engine::RunStatus::Failure) { FML_LOG(ERROR) << "Engine not prepare and launch isolate."; return isolate_launch_status; } else if (isolate_launch_status == Engine::RunStatus::FailureAlreadyRunning) { return isolate_launch_status; } std::shared_ptr<blink::DartIsolate> isolate = runtime_controller_->GetRootIsolate().lock(); bool isolate_running = isolate && isolate->GetPhase() == blink::DartIsolate::Phase::Running; if (isolate_running) { tonic::DartState::Scope scope(isolate.get()); if (settings_.root_isolate_create_callback) { settings_.root_isolate_create_callback(); } if (settings_.root_isolate_shutdown_callback) { isolate->AddIsolateShutdownCallback( settings_.root_isolate_shutdown_callback); } } return isolate_running ? Engine::RunStatus::Success : Engine::RunStatus::Failure; }Copy the code
DartIsolate Run method
1. Initialize the current Scope
2. Get the loaded Dart library file as described in the previous Dart_RootLibrary article
3. Parse the import file
FML_WARN_UNUSED_RESULT
bool DartIsolate::Run(const std: :string& entrypoint_name) {
TRACE_EVENT0("flutter"."DartIsolate::Run");
if(phase_ ! = Phase::Ready) {return false;
}
tonic::DartState::Scope scope(this);
auto user_entrypoint_function =
Dart_GetField(Dart_RootLibrary(), tonic::ToDart(entrypoint_name.c_str()));
if(! InvokeMainEntrypoint(user_entrypoint_function)) {return false;
}
phase_ = Phase::Running;
FML_DLOG(INFO) << "New isolate is in the running state.";
return true;
}
Copy the code
DartIsolate InvokeMainEntrypoint
-
_getStartMainIsolateFunction find main Isolate file handle
-
Look for the _runMainZoned entry file in dart: UI
static bool InvokeMainEntrypoint(Dart_Handle user_entrypoint_function) {
if (tonic::LogIfError(user_entrypoint_function)) {
FML_LOG(ERROR) << "Could not resolve main entrypoint function.";
return false;
}
Dart_Handle start_main_isolate_function =
tonic::DartInvokeField(Dart_LookupLibrary(tonic::ToDart("dart:isolate")),
"_getStartMainIsolateFunction"{});if (tonic::LogIfError(start_main_isolate_function)) {
FML_LOG(ERROR) << "Could not resolve main entrypoint trampoline.";
return false;
}
if (tonic::LogIfError(tonic::DartInvokeField(
Dart_LookupLibrary(tonic::ToDart("dart:ui")), "_runMainZoned",
{start_main_isolate_function, user_entrypoint_function}))) {
FML_LOG(ERROR) << "Could not invoke the main entrypoint.";
return false;
}
return true;
}
Copy the code
Start FlutterUI main ()
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// primarySwatch: Colors.blue,), home: Scaffold( body: ... ,),); }}Copy the code
conclusion
Through the analysis of two documents, we have introduced the initialization process of Flutter engine and the operation process of FlutterEngine.
1.FlutterEngine initializes four different threads, initializes DartVM, initializes Engine, loads Dart code, and creates the runtime environment. 2. Call the main() method in FlutterUI to initialize the FlutterUI code