Today is based on analysis of Android Flutter under startup process, first look at the website provides the frame, the bottom layer Embedder is specific platform implementation, the Android platform code under the engine/shell/platform/Android, Including the Java layer of grafting in engine/shell/platform/android/IO/under the flutter. The Embedder layer is the key to the startup of the Flutter. After the application is started, the Flutter Engine is initialized through this layer. DartVM is created in the Engine and the dart entry method main method is executed in DartVM.

1. Code analysis of Android platform

This code is the Embedder layer, in engine/shell/platform/android/IO/under the flutter.

First look at onCreate in FlutterApplication

    @CallSuper
    public void onCreate(a) {
        super.onCreate();
        FlutterMain.startInitialization(this);
    }
Copy the code

Then go to FlutterMain:

    public static void startInitialization(Context applicationContext, FlutterMain.Settings settings) {
        if(Looper.myLooper() ! = Looper.getMainLooper()) {throw new IllegalStateException("startInitialization must be called on the main thread");
        } else if (sSettings == null) {
            sSettings = settings;
            long initStartTimestampMillis = SystemClock.uptimeMillis();
            initConfig(applicationContext);
            initAot(applicationContext);
            initResources(applicationContext);
            System.loadLibrary("flutter");
            longinitTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; nativeRecordStartTimestamp(initTimeMillis); }}Copy the code

Here configuration information is initialized, AOT mode or JIT mode variables are initialized, and resource files are initialized. The main idea is to copy the flutter related resources from the asset directory to the private directory

Finally call the JNI methods nativeRecordStartTimestamp records into c + +.

FlutterApplication executes onCreate in MainActivity, working mainly in its parent FlutterActivity class and delegating to FlutterActivityDelegate:

// FlutterActivity    
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.eventDelegate.onCreate(savedInstanceState);
}

// FlutterActivityDelegate
    @Override
public void onCreate(Bundle savedInstanceState) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Window window = activity.getWindow();
        window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        window.setStatusBarColor(0x40000000);
        window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
    }

    String[] args = getArgsFromIntent(activity.getIntent());
    FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);

    flutterView = viewFactory.createFlutterView(activity);
    if (flutterView == null) {
        FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
        flutterView = new FlutterView(activity, null, nativeView);
        flutterView.setLayoutParams(matchParent);
        activity.setContentView(flutterView);
        launchView = createLaunchView();
        if(launchView ! =null) { addLaunchView(); }}if (loadIntent(activity.getIntent())) {
        return;
    }

    String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
    if(appBundlePath ! =null) { runBundle(appBundlePath); }}Copy the code

You can see that there is a setContentView, which is where the content view is set, to summarize what the FlutterActivityDelegate does:

1. Set the status bar style based on the system version

2. Get the Intent

3.FlutterMain.ensureInitializationComplete

4.create FlutterNativeView

5.create FlutterView

6. Set the activity’s content view

7. Perform appBundlePath

The first two steps are nothing to talk about. Start with step 3.

2.FlutterMain

public static void ensureInitializationComplete(Context applicationContext, String[] args) {
        if(Looper.myLooper() ! = Looper.getMainLooper()) {throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
        } else if (sSettings == null) {
            throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
        } else if(! sInitialized) {try {
                sResourceExtractor.waitForCompletion();
                List<String> shellArgs = new ArrayList();
                shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
                if(args ! =null) {
                    Collections.addAll(shellArgs, args);
                }

                if (sIsPrecompiledAsSharedLibrary) {
                    shellArgs.add("--aot-shared-library-path=" + new File(PathUtils.getDataDirectory(applicationContext), sAotSharedLibraryPath));
                } else {
                    if (sIsPrecompiledAsBlobs) {
                        shellArgs.add("--aot-snapshot-path=" + PathUtils.getDataDirectory(applicationContext));
                    } else {
                        shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
                        shellArgs.add("--aot-snapshot-path=" + PathUtils.getDataDirectory(applicationContext) + "/" + sFlutterAssetsDir);
                    }

                    shellArgs.add("--vm-snapshot-data=" + sAotVmSnapshotData);
                    shellArgs.add("--vm-snapshot-instr=" + sAotVmSnapshotInstr);
                    shellArgs.add("--isolate-snapshot-data=" + sAotIsolateSnapshotData);
                    shellArgs.add("--isolate-snapshot-instr=" + sAotIsolateSnapshotInstr);
                }

                if(sSettings.getLogTag() ! =null) {
                    shellArgs.add("--log-tag=" + sSettings.getLogTag());
                }

                String appBundlePath = findAppBundlePath(applicationContext);
                String appStoragePath = PathUtils.getFilesDir(applicationContext);
                String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
                nativeInit(applicationContext, (String[])shellArgs.toArray(new String[0]), appBundlePath, appStoragePath, engineCachesPath);
                sInitialized = true;
            } catch (Exception var6) {
                Log.e("FlutterMain"."Flutter initialization failed.", var6);
                throw newRuntimeException(var6); }}}Copy the code

This method must be executed on the main thread and only once. And wait for sResourceExtractor to copy resources to the installation package directory to complete before continuing:

sResourceExtractor.waitForCompletion();

void waitForCompletion(a) {
        if (this.mExtractTask ! =null) {
            try {
                this.mExtractTask.get();
            } catch (ExecutionException | InterruptedException | CancellationException var2) {
                this.deleteFiles(); }}}private class ExtractTask extends AsyncTask<Void.Void.Void> {... }Copy the code

So what are we copying? Look at a debug apK:

Where VM starts with DARTVM, and ISOLATE is the business code we wrote in DART.

Take a look at the APK of the Release package. It’s a little different from Debug, with a few more files.

Going back to the previous code, copy these files from assets to Flutter_assets in the installation package private directory.

There are many pathnames in the code:

private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
private static String sAotSharedLibraryPath = "app.so";
private static String sAotVmSnapshotData = "vm_snapshot_data";
private static String sAotVmSnapshotInstr = "vm_snapshot_instr";
private static String sAotIsolateSnapshotData = "isolate_snapshot_data";
private static String sAotIsolateSnapshotInstr = "isolate_snapshot_instr";
private static String sFlx = "app.flx";
private static String sFlutterAssetsDir = "flutter_assets";
Copy the code

Some directories are initialized, including the appBundle path, application storage directory, engine cache directory, and so on. The JNI method nativeInit is then called to initialize the information at the C++ layer, set the paths to setting in FlutterMain, pass the construct FlutterMain, and save it to the global variable g_flutter_main.

// shell/platform/android/flutter_main.cc
static std: :unique_ptr<FlutterMain> g_flutter_main;
void FlutterMain::Init(JNIEnv* env,
                       jclass clazz,
                       jobject context,
                       jobjectArray jargs,
                       jstring bundlePath,
                       jstring appStoragePath,
                       jstring engineCachesPath) {
  std: :vector<std: :string> args;
  args.push_back("flutter");
  for (auto& arg : fml::jni::StringArrayToVector(env, jargs)) {
    args.push_back(std::move(arg));
  }
  auto command_line = fml::CommandLineFromIterators(args.begin(), args.end());

  auto settings = SettingsFromCommandLine(command_line);

  settings.assets_path = fml::jni::JavaStringToString(env, bundlePath);

  // Restore the callback cache.
  // TODO(chinmaygarde): Route all cache file access through FML and remove this
  // setter.
  blink::DartCallbackCache::SetCachePath(
      fml::jni::JavaStringToString(env, appStoragePath));

  fml::paths::InitializeAndroidCachesPath(
      fml::jni::JavaStringToString(env, engineCachesPath));

  blink::DartCallbackCache::LoadCacheFromDisk();

  if(! blink::DartVM::IsRunningPrecompiledCode()) {// Check to see if the appropriate kernel files are present and configure
    // settings accordingly.
    auto application_kernel_path =
        fml::paths::JoinPaths({settings.assets_path, "kernel_blob.bin"});

    if (fml::IsFile(application_kernel_path)) {
      settings.application_kernel_asset = application_kernel_path;
    }
  }

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

  settings.task_observer_remove = [](intptr_tkey) { fml::MessageLoop::GetCurrent().RemoveTaskObserver(key); }; .// Not thread safe. Will be removed when FlutterMain is refactored to no
  // longer be a singleton.
  g_flutter_main.reset(new FlutterMain(std::move(settings)));
}

Copy the code

Now, create FlutterNativeView, number four.

3.FlutterNativeView

    public FlutterNativeView(Context context, boolean isBackgroundView) {
        this.mNextReplyId = 1;
        this.mPendingReplies = new HashMap();
        this.mContext = context;
        this.mPluginRegistry = new FlutterPluginRegistry(this, context);
        this.mFlutterJNI = new FlutterJNI();
        this.mFlutterJNI.setRenderSurface(new FlutterNativeView.RenderSurfaceImpl());
        this.mFlutterJNI.setPlatformMessageHandler(new FlutterNativeView.PlatformMessageHandlerImpl());
        this.mFlutterJNI.addEngineLifecycleListener(new FlutterNativeView.EngineLifecycleListenerImpl());
        this.attach(this, isBackgroundView);
        this.assertAttached();
        this.mMessageHandlers = new HashMap();
    }
Copy the code

FlutterJNI is the bridge between the Java layer and the Flutter Engine. This includes creating and starting the Flutter Engine, notifying the current FlutterView Surface lifecycle, passing Platform data to dart layer, and passing back the result data returned by dart layer calling platform layer methods.

The attach method is then called:

// FlutterNativeView.java
private void attach(FlutterNativeView view, boolean isBackgroundView) {
        this.mFlutterJNI.attachToNative(isBackgroundView);
}

// FlutterJNI.java
@UiThread
public void attachToNative(boolean isBackgroundView) {
     this.ensureNotAttachedToNative();
     this.nativePlatformViewId = this.nativeAttach(this, isBackgroundView);
}
Copy the code

In the attachToNative method, the current flutterJNI object is passed to the c++ layer by calling JNI method nativeAttach (some subsequent methods of dart layer calling Java layer are realized by calling corresponding methods through this object). Get the nativePlatformViewId returned by the c++ layer. This value is very important. It is the object pointer value of the c++ layer AndroidShellHolder.

The corresponding C++ method is as follows, Create STD :: Unique_ptr (which holds and manages the AndroidShellHolder object through Pointers) by previously initializing the Settings stored in g_Flutter_main and the passed Java object flutterJNI, The reinterpret_cast() method forces the Pointer to the AndroidShellHolder object to a value of type long and returns it to the Java layer for preservation.

// platform_view_android_jni.cc
// Called By Java

static jlong AttachJNI(JNIEnv* env, jclass clazz, jobject flutterJNI, jboolean is_background_view) {
  fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
  auto shell_holder = std::make_unique<AndroidShellHolder>(
      FlutterMain::Get().GetSettings(), java_object, is_background_view);
  if (shell_holder->IsValid()) {
    return reinterpret_cast<jlong>(shell_holder.release());
  } else {
    return 0; }}Copy the code

Let’s move on to the very important AndroiudShellHolder class.

4.AndroiudShellHolder

It’s a long code, so if you look at the first half, the parameter is_background_view is false, ThreadHost initializes three threads ui_thread,gpu_thread, and io_thread, and the current thread is platform_thread.

  • platform_threadResponsible andEngineLayer of communication
  • io_threadResponsible for I/O operations
  • gpu_threadExecute GPU commands
  • ui_threadperformDartlayerisolatecode
// android_shell_holder.cc

AndroidShellHolder::AndroidShellHolder(
    blink::Settings settings,
    fml::jni::JavaObjectWeakGlobalRef java_object,
    bool is_background_view)
    : settings_(std::move(settings)), java_object_(java_object) {
  static size_t shell_count = 1;
  auto thread_label = std::to_string(shell_count++);

  FML_CHECK(pthread_key_create(&thread_destruct_key_, ThreadDestructCallback) ==
            0);

  if (is_background_view) {
    thread_host_ = {thread_label, ThreadHost::Type::UI};
  } else{ thread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU | ThreadHost::Type::IO}; }... }Copy the code

Four threads will hold MessageLoop, which can be used to add work tasks to threads. For details, see another thread section on platform channel practices and principles between Flutter and native. The Shell is then constructed:

// android_shell_holder.cc

blink::TaskRunners task_runners(thread_label,     // label
                                  platform_runner,  // platform
                                  gpu_runner,       // gpu
                                  ui_runner,        // ui
                                  io_runner         // io
  );
shell_ =
      Shell::Create(task_runners,             // task runners
                    settings_,                // settings
                    on_create_platform_view,  // platform view create callback
                    on_create_rasterizer      // rasterizer create callback
      );

// shell.cc

std: :unique_ptr<Shell> Shell::Create(
    blink::TaskRunners task_runners,
    blink::Settings settings,
    Shell::CreateCallback<PlatformView> on_create_platform_view,
    Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
  PerformInitializationTasks(settings);

  TRACE_EVENT0("flutter"."Shell::Create");

  auto vm = blink::DartVMRef::Create(settings);
  FML_CHECK(vm) << "Must be able to initialize the VM.";

  auto vm_data = vm->GetVMData();

  return Shell::Create(std::move(task_runners),             //
                       std::move(settings),                 //
                       vm_data->GetIsolateSnapshot(),       // isolate snapshot
                       blink::DartSnapshot::Empty(),        // shared snapshot
                       std::move(on_create_platform_view),  //
                       std::move(on_create_rasterizer),     //
                       std::move(vm)                        //
  );
}
Copy the code

In Shell Create, the DartVM is constructed using blink::DartVMRef::Create, based on the Settings parameter passed in. If you go through the DartVM creation process, the VM is constructed only once.

// dart_vm_lifecycle.cc
DartVMRef DartVMRef::Create(Settings settings,
                            fml::RefPtr<DartSnapshot> vm_snapshot,
                            fml::RefPtr<DartSnapshot> isolate_snapshot,
                            fml::RefPtr<DartSnapshot> shared_snapshot) {
  std::lock_guard<std::mutex> lifecycle_lock(gVMMutex);

  // If there is already a running VM in the process, grab a strong reference to
  // it.
  if (auto vm = gVM.lock()) {
    FML_DLOG(WARNING) << "Attempted to create a VM in a process where one was "
                         "already running. Ignoring arguments for current VM "
                         "create call and reusing the old VM.";
    // There was already a running VM in the process,
    return DartVMRef{std::move(vm)};
  }

  std::lock_guard<std::mutex> dependents_lock(gVMDependentsMutex);

  gVMData.reset();
  gVMServiceProtocol.reset();
  gVMIsolateNameServer.reset();
  gVM.reset();

  // If there is no VM in the process. Initialize one, hold the weak reference
  // and pass a strong reference to the caller.
  auto isolate_name_server = std::make_shared<IsolateNameServer>();
  auto vm = DartVM::Create(std::move(settings),          //
                           std::move(vm_snapshot),       //
                           std::move(isolate_snapshot),  //
                           std::move(shared_snapshot),   //
                           isolate_name_server           //
  );

  if(! vm) { FML_LOG(ERROR) <<"Could not create Dart VM instance.";
    return {nullptr};
  }

  gVMData = vm->GetVMData();
  gVMServiceProtocol = vm->GetServiceProtocol();
  gVMIsolateNameServer = isolate_name_server;
  gVM = vm;

  if (settings.leak_vm) {
    gVMLeak = vm;
  }

  return DartVMRef{std::move(vm)};
}
Copy the code

Then through DartVM: : Create to the execution of specific construction will be called in the Create DartVM constructor, by performing a dart: : bin: : BootstrapDartIo start () method to guide the dart: IO time handler:

//dart_vm.cc

DartVM::DartVM(std: :shared_ptr<const DartVMData> vm_data,
               std: :shared_ptr<IsolateNameServer> isolate_name_server)
    : settings_(vm_data->GetSettings()),
      vm_data_(vm_data),
      isolate_name_server_(std::move(isolate_name_server)),
      service_protocol_(std::make_shared<ServiceProtocol>()) {
  TRACE_EVENT0("flutter"."DartVMInitializer");

  gVMLaunchCount++;

  FML_DCHECK(vm_data_);
  FML_DCHECK(isolate_name_server_);
  FML_DCHECK(service_protocol_);

  FML_DLOG(INFO) << "Attempting Dart VM launch for mode: "
                 << (IsRunningPrecompiledCode() ? "AOT" : "Interpreter");

  {
    TRACE_EVENT0("flutter"."dart::bin::BootstrapDartIo");
    dart::bin::BootstrapDartIo();

    if(! settings_.temp_directory_path.empty()) { dart::bin::SetSystemTempDirectory(settings_.temp_directory_path.c_str()); }}... DartUI::InitForGlobal(); { TRACE_EVENT0("flutter"."Dart_Initialize");
    Dart_InitializeParams params = {};
    params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
    params.vm_snapshot_data =
        vm_data_->GetVMSnapshot().GetData()->GetSnapshotPointer();
    params.vm_snapshot_instructions =
        vm_data_->GetVMSnapshot().GetInstructionsIfPresent();
    params.create = reinterpret_cast<decltype(params.create)>(
        DartIsolate::DartIsolateCreateCallback);
    params.shutdown = reinterpret_cast<decltype(params.shutdown)>(
        DartIsolate::DartIsolateShutdownCallback);
    params.cleanup = reinterpret_cast<decltype(params.cleanup)>(
        DartIsolate::DartIsolateCleanupCallback);
    params.thread_exit = ThreadExitCallback;
    params.get_service_assets = GetVMServiceAssetsArchiveCallback;
    params.entropy_source = DartIO::EntropySource;
    char* init_error = Dart_Initialize(&params); . }}Copy the code

DartUI::InitForGlobal registers various native dart methods. Dart calls c++ layer methods, similar to jni registrations in Java:

// dart_ui.cc

void DartUI::InitForGlobal() {
  if(! g_natives) { g_natives =new tonic::DartLibraryNatives();
    Canvas::RegisterNatives(g_natives);
    CanvasGradient::RegisterNatives(g_natives);
    CanvasImage::RegisterNatives(g_natives);
    CanvasPath::RegisterNatives(g_natives);
    CanvasPathMeasure::RegisterNatives(g_natives);
    Codec::RegisterNatives(g_natives);
    DartRuntimeHooks::RegisterNatives(g_natives);
    EngineLayer::RegisterNatives(g_natives);
    FontCollection::RegisterNatives(g_natives);
    FrameInfo::RegisterNatives(g_natives);
    ImageFilter::RegisterNatives(g_natives);
    ImageShader::RegisterNatives(g_natives);
    IsolateNameServerNatives::RegisterNatives(g_natives);
    Paragraph::RegisterNatives(g_natives);
    ParagraphBuilder::RegisterNatives(g_natives);
    Picture::RegisterNatives(g_natives);
    PictureRecorder::RegisterNatives(g_natives);
    Scene::RegisterNatives(g_natives);
    SceneBuilder::RegisterNatives(g_natives);
    SceneHost::RegisterNatives(g_natives);
    SemanticsUpdate::RegisterNatives(g_natives);
    SemanticsUpdateBuilder::RegisterNatives(g_natives);
    Versions::RegisterNatives(g_natives);
    Vertices::RegisterNatives(g_natives);
    Window::RegisterNatives(g_natives);

    // Secondary isolates do not provide UI-related APIs.
    g_natives_secondary = newtonic::DartLibraryNatives(); DartRuntimeHooks::RegisterNatives(g_natives_secondary); IsolateNameServerNatives::RegisterNatives(g_natives_secondary); }}// window.cc
void Window::RegisterNatives(tonic::DartLibraryNatives* natives) {
  natives->Register({
      {"Window_defaultRouteName", DefaultRouteName, 1.true},
      {"Window_scheduleFrame", ScheduleFrame, 1.true},
      {"Window_sendPlatformMessage", _SendPlatformMessage, 4.true},
      {"Window_respondToPlatformMessage", _RespondToPlatformMessage, 3.true},
      {"Window_render", Render, 2.true},
      {"Window_updateSemantics", UpdateSemantics, 2.true},
      {"Window_setIsolateDebugName", SetIsolateDebugName, 2.true},
      {"Window_reportUnhandledException", ReportUnhandledException, 2.true}}); }Copy the code

Then DartVM initialization through Dart_Initialize Dart in the runtime environment, the back is not down to the, returns to the shell. Cc, after playing the vm will be assigned to the shell structure, through CreateShellOnPlatformThread shell structure, This method is executed in the PlatformThread thread.

std: :unique_ptr<Shell> Shell::Create(
    blink::TaskRunners task_runners,
    blink::Settings settings,
    fml::RefPtr<const blink::DartSnapshot> isolate_snapshot,
    fml::RefPtr<const blink::DartSnapshot> shared_snapshot,
    Shell::CreateCallback<PlatformView> on_create_platform_view,
    Shell::CreateCallback<Rasterizer> on_create_rasterizer,
    blink::DartVMRef vm) {
  PerformInitializationTasks(settings);

  TRACE_EVENT0("flutter"."Shell::CreateWithSnapshots");

  if(! task_runners.IsValid() || ! on_create_platform_view || ! on_create_rasterizer) {return nullptr;
  }

  fml::AutoResetWaitableEvent latch;
  std: :unique_ptr<Shell> shell;
  fml::TaskRunner::RunNowOrPostTask(
      task_runners.GetPlatformTaskRunner(),
      fml::MakeCopyable([&latch,                                          //
                         vm = std::move(vm),                              //
                         &shell,                                          //
                         task_runners = std::move(task_runners),          //
                         settings,                                        //
                         isolate_snapshot = std::move(isolate_snapshot),  //
                         shared_snapshot = std::move(shared_snapshot),    //
                         on_create_platform_view,                         //
                         on_create_rasterizer                             //] ()mutable {
        shell = CreateShellOnPlatformThread(std::move(vm),
                                            std::move(task_runners),      //
                                            settings,                     //
                                            std::move(isolate_snapshot),  //
                                            std::move(shared_snapshot),   //
                                            on_create_platform_view,      //
                                            on_create_rasterizer          //
        );
        latch.Signal();
      }));
  latch.Wait();
  return shell;
}
Copy the code

The next to look CreateShellOnPlatformThread, more code is divided into several parts, the first call new Shell structure, and then call on_create_platform_view, This method is passed in android_shell_holder.cc via Shell::Create

// shell.cc
std: :unique_ptr<Shell> Shell::CreateShellOnPlatformThread(
    blink::DartVMRef vm,
    blink::TaskRunners task_runners,
    blink::Settings settings,
    fml::RefPtr<const blink::DartSnapshot> isolate_snapshot,
    fml::RefPtr<const blink::DartSnapshot> shared_snapshot,
    Shell::CreateCallback<PlatformView> on_create_platform_view,
    Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
  if(! task_runners.IsValid()) { FML_LOG(ERROR) <<"Task runners to run the shell were invalid.";
    return nullptr;
  }

  auto shell =
      std: :unique_ptr<Shell>(new Shell(std::move(vm), task_runners, settings));

  // Create the platform view on the platform thread (this thread).
  auto platform_view = on_create_platform_view(*shell.get());
  if(! platform_view || ! platform_view->GetWeakPtr()) {return nullptr; }... }Copy the code

Take a look at on_create_platform_view, where PlatformViewAndroid is constructed and given to platform_view to manage

// android_shell_holder.cc
fml::WeakPtr<PlatformViewAndroid> weak_platform_view;
  Shell::CreateCallback<PlatformView> on_create_platform_view =
      [is_background_view, java_object, &weak_platform_view](Shell& shell) {
        std: :unique_ptr<PlatformViewAndroid> platform_view_android;
        if (is_background_view) {
          platform_view_android = std::make_unique<PlatformViewAndroid>(
              shell,                   // delegate
              shell.GetTaskRunners(),  // task runners
              java_object              // java object handle for JNI interop
          );

        } else {
          platform_view_android = std::make_unique<PlatformViewAndroid>(
              shell,                   // delegate
              shell.GetTaskRunners(),  // task runners
              java_object,             // java object handle for JNI interop
              shell.GetSettings()
                  .enable_software_rendering  // use software rendering
          );
        }
        weak_platform_view = platform_view_android->GetWeakPtr();
        return platform_view_android;
      };
Copy the code

Back in shell.cc, the IOManager object is constructed in the IO Thread and handed over to the IO_manager:

  // Create the IO manager on the IO thread. The IO manager must be initialized
  // first because it has state that the other subsystems depend on. It must
  // first be booted and the necessary references obtained to initialize the
  // other subsystems.
  fml::AutoResetWaitableEvent io_latch;
  std: :unique_ptr<IOManager> io_manager;
  auto io_task_runner = shell->GetTaskRunners().GetIOTaskRunner();
  fml::TaskRunner::RunNowOrPostTask(
      io_task_runner,
      [&io_latch,       //
       &io_manager,     //
       &platform_view,  //
       io_task_runner   //
  ]() {
        TRACE_EVENT0("flutter"."ShellSetupIOSubsystem");
        io_manager = std::make_unique<IOManager>(
            platform_view->CreateResourceContext(), io_task_runner);
        io_latch.Signal();
      });
  io_latch.Wait();
Copy the code

The Rasterizer object is then constructed in the GPU Thread and given to the Rasterizer to manage. On_create_rasterizer is also passed from android_shell_holder.cc via Shell::Create

  // Create the rasterizer on the GPU thread.
  fml::AutoResetWaitableEvent gpu_latch;
  std: :unique_ptr<Rasterizer> rasterizer;
  fml::WeakPtr<blink::SnapshotDelegate> snapshot_delegate;
  fml::TaskRunner::RunNowOrPostTask(
      task_runners.GetGPUTaskRunner(), [&gpu_latch,            //
                                        &rasterizer,           //
                                        on_create_rasterizer,  //
                                        shell = shell.get(),   //
                                        &snapshot_delegate     //
  ]() {
        TRACE_EVENT0("flutter"."ShellSetupGPUSubsystem");
        if (auto new_rasterizer = on_create_rasterizer(*shell)) {
          rasterizer = std::move(new_rasterizer);
          snapshot_delegate = rasterizer->GetSnapshotDelegate();
        }
        gpu_latch.Signal();
      });

  gpu_latch.Wait();
Copy the code

Then create the Engine in UIThread and hand it over to Engine to manage:

  // Create the engine on the UI thread.
  fml::AutoResetWaitableEvent ui_latch;
  std: :unique_ptr<Engine> engine;
  fml::TaskRunner::RunNowOrPostTask(
      shell->GetTaskRunners().GetUITaskRunner(),
      fml::MakeCopyable([&ui_latch,                                         //
                         &engine,                                           //
                         shell = shell.get(),                               //
                         isolate_snapshot = std::move(isolate_snapshot),    //
                         shared_snapshot = std::move(shared_snapshot),      //
                         vsync_waiter = std::move(vsync_waiter),            //
                         snapshot_delegate = std::move(snapshot_delegate),  //
                         io_manager = io_manager->GetWeakPtr()              //] ()mutable {
        TRACE_EVENT0("flutter"."ShellSetupUISubsystem");
        const auto& task_runners = shell->GetTaskRunners();

        // The animator is owned by the UI thread but it gets its vsync pulses
        // from the platform.
        auto animator = std::make_unique<Animator>(*shell, task_runners,
                                                   std::move(vsync_waiter));

        engine = std::make_unique<Engine>(*shell,                        //
                                          *shell->GetDartVM(),           //
                                          std::move(isolate_snapshot),   //
                                          std::move(shared_snapshot),    //
                                          task_runners,                  //
                                          shell->GetSettings(),          //
                                          std::move(animator),           //
                                          std::move(snapshot_delegate),  //
                                          std::move(io_manager)          //
        );
        ui_latch.Signal();
      }));

  ui_latch.Wait();
Copy the code

Platform_view engine rasterizer io_manager (platform_view engine rasterizer io_manager)

  // We are already on the platform thread. So there is no platform latch to
  // wait on.

  if(! shell->Setup(std::move(platform_view),  //
                    std::move(engine),         //
                    std::move(rasterizer),     //
                    std::move(io_manager))     //
  ) {
    return nullptr;
  }

  return shell;
Copy the code

Cc, the Shell object created by Shell::Create() is returned to the AndroidShellHolder object. The Embedder layer connects to the Engine layer through the Shell object, and everything else is done through the Shell object. The Shell object is in turn held by the AndroidShellHolder object, and the AndroidShellHolder object pointer is returned to the Java layer, The Java layer then passes this pointer value when calling the JNI method to get the AndroidShellHolder object for the Embedder layer and send a series of instructions to the Engine layer through the Shell object.

5.RunBundle

Now that you’ve created the runtime environment for the DART layer code execution, it’s time to load the DART layer code.

Back to the onCreate code in the FlutterActivityDelegate in the Java layer:

//FlutterActivityDelegate.java/onCreate
if (!this.loadIntent(this.activity.getIntent())) {
            String appBundlePath = FlutterMain.findAppBundlePath(this.activity.getApplicationContext());
            if(appBundlePath ! =null) {
                this.runBundle(appBundlePath); }}//FlutterActivityDelegate.java/runBundle
    private void runBundle(String appBundlePath) {
        if (!this.flutterView.getFlutterNativeView().isApplicationRunning()) {
            FlutterRunArguments args = new FlutterRunArguments();
            ArrayList<String> bundlePaths = new ArrayList();
            ResourceUpdater resourceUpdater = FlutterMain.getResourceUpdater();
            if(resourceUpdater ! =null) {
                File patchFile = resourceUpdater.getInstalledPatch();
                JSONObject manifest = resourceUpdater.readManifest(patchFile);
                if (resourceUpdater.validateManifest(manifest)) {
                    bundlePaths.add(patchFile.getPath());
                }
            }

            bundlePaths.add(appBundlePath);
            args.bundlePaths = (String[])bundlePaths.toArray(new String[0]);
            args.entrypoint = "main";
            this.flutterView.runFromBundle(args); }}Copy the code

Call your own runBundle. In the if statement you can see the resource extraction code, the dynamic update code embedded in the Flutter is not currently active, and then call FlutterView runFromBundle:

// FlutterView.java
public void runFromBundle(FlutterRunArguments args) {
        this.assertAttached();
        this.preRun();
        this.mNativeView.runFromBundle(args);
        this.postRun();
}
Copy the code

The Java layer will eventually be called into FlutterJNI, where nativePlatformViewId is the pointer to AndroidShellHolder in the Java layer, There is only one value in the array like /data/data/ package name /flutter/flutter_assets/. EntrypointFunctionName is main, PathToEntrypointFunction is null.

// FlutterJNI.java
  @UiThread
  public void runBundleAndSnapshotFromLibrary( @NonNull String[] prioritizedBundlePaths, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @NonNull AssetManager assetManager ) {
    ensureAttachedToNative();
    nativeRunBundleAndSnapshotFromLibrary(
        nativePlatformViewId,
        prioritizedBundlePaths,
        entrypointFunctionName,
        pathToEntrypointFunction,
        assetManager
    );
  }

  private native void nativeRunBundleAndSnapshotFromLibrary(
      long nativePlatformViewId,
      @NonNull String[] prioritizedBundlePaths,
      @Nullable String entrypointFunctionName,
      @Nullable String pathToEntrypointFunction,
      @NonNull AssetManager manager
  );
Copy the code

Then we get to the JNI layer, in PlatformViewAndrod:

// platform_view_android_jni.cc
{
          .name = "nativeRunBundleAndSnapshotFromLibrary",
          .signature = "(J[Ljava/lang/String;Ljava/lang/String;"
                       "Ljava/lang/String; Landroid/content/res/AssetManager;) V",
          .fnPtr =
              reinterpret_cast<void*>(&shell::RunBundleAndSnapshotFromLibrary),
},

// RunBundleAndSnapshotFromLibrary
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); }}}Copy the code

The logic above first creates the DirectoryAssetBundle object via bundlePath to be managed by the ASset_manager object. The size-config function key runs the config object. Through the ANDROID_SHELL_HOLDER – > Launch (STD: : move ()); Launched according to the runtime configuration information, ANDROID_SHELL_HOLDER is a macro that calls its methods by forcing the value of the AndroidShellHolder pointer held by the Java layer into the AndroidShellHolder object pointer.

// platform_view_android_jni.cc
#define ANDROID_SHELL_HOLDER \
  (reinterpret_cast<shell::AndroidShellHolder*>(shell_holder))
Copy the code

Launch engine->Run in UIThread

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

Look at the engine – > Run:

// engine.cc
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)); .return isolate_running ? Engine::RunStatus::Success
                         : Engine::RunStatus::Failure;
}

shell::Engine::RunStatus Engine::PrepareAndLaunchIsolate(
    RunConfiguration configuration) {
  ...

  if (configuration.GetEntrypointLibrary().empty()) {
    if(! isolate->Run(configuration.GetEntrypoint())) { FML_LOG(ERROR) <<"Could not run the isolate.";
      returnRunStatus::Failure; }}else {
    if(! isolate->RunFromLibrary(configuration.GetEntrypointLibrary(), configuration.GetEntrypoint())) { FML_LOG(ERROR) <<"Could not run the isolate.";
      returnRunStatus::Failure; }}return RunStatus::Success;
}
Copy the code

Finally, DartIsolate’s Run method is called, and the Dart layer main() method entry is executed via DartInvokeField. The Dart code runs and the Flutter interface is displayed in the FlutterView.

// dart_isolate.cc
FML_WARN_UNUSED_RESULT
bool DartIsolate::Run(const std: :string& entrypoint_name, fml::closure on_run) {
  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.";

  if (on_run) {
    on_run();
  }
  return true;
}

FML_WARN_UNUSED_RESULT
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

6. Summary

The whole code is quite long. A brief summary is that Flutter is displayed on FlutterView on Android platform, and FlutterView will be called to FlutterJNI through FlutterNativeView. In FlutterJNI, there’s an important object called AndroidShellHolder, which holds shell objects. The Embedder layer connects to the Engine layer through shell objects. All subsequent operations are done through Shell objects. The Pointer to the AndroidShellHolder object is returned to the Java layer, and then FlutterJNI passes the pointer to the Embedder layer when it calls the JNI method. It then sends a series of instructions to the Engine layer through Shell objects. The Engine object calls dart layer code execution.

Reference links:

  • Flutter startup process source analysis