关于Flutter初始化流程,我必须告诉你的是...

1. 引言

最近在做性能优化的时候发现,在混合栈开发中,第一次启动Flutter页面的耗时总会是第二次启动Flutter页面耗时的两倍左右,这样给人感觉很不好。分析发现第一次启动Flutter页面会做一些初始化工作,借此,我梳理了下Flutter的初始化流程。

2. Flutter初始化时序

Flutter初始化主要分四部分,FlutterMain初始化、FlutterNativeView初始化、FlutterView初始化和Flutter Bundle初始化。
我们先看下Flutter初始化的时序图,来整体把握下Flutter初始化的一般流程:

Flutter初始化时序

3. 具体分析

3.1 FlutterMain初始化

这部分初始化工作是由Application.onCreate方法中调用开始的,在Application创建的时候就会初始化完成,不会影响Flutter页面的第一次启动,所以这里只是做一个简单分析。 
从FlutterMain.startInitialization方法代码中可以轻易看出来,初始化主要分四部分。 
前面三部分比较类似,分别是初始化配置信息、初始化AOT编译和初始化资源,最后一部分则是加载Flutter的Native环境。 
这部分感兴趣的同学可以看下FlutterMain.java源码,逻辑还是比较清晰的。

public static void startInitialization(Context applicationContext, Settings settings) {
    // other codes ...

    initConfig(applicationContext);
    initAot(applicationContext);
    initResources(applicationContext);
    System.loadLibrary("flutter");

    // other codes ...
}

3.2 FlutterNativeView初始化

先用一个图来展现FlutterNativeView构造函数的调用栈:

FlutterNativeView构造函数调用栈

从上图的调用栈中我们知道FlutterNativeView的初始化主要做了些什么,我们再从源码角度较为深入的了解下: 
FlutterNativeView的构造函数最终主要调用了一个nativeAttach方法。到这里就需要分析引擎层代码了,我们可以在JNI文件中找到对应的jni方法调用。(具体文件为platform_view_android_jni.cc)

static const JNINativeMethod native_view_methods[] = {
  {
      .name = "nativeAttach",
      .signature = "(Lio/flutter/view/FlutterNativeView;)J",
      .fnPtr = reinterpret_cast<void*>(&shell::Attach),
  },

  // other codes ...
};

从代码中很容易看出FlutterNativeView.attach方法最终调用了shell::Attach方法,而shell::Attach方法主要做了两件事: 
1. 创建PlatformViewAndroid。 
2. 调用PlatformViewAndroid::Attach。

static jlong Attach(JNIEnv* env, jclass clazz, jobject flutterView) {
  auto view = new PlatformViewAndroid();

  // other codes ...

  view->Attach();

  // other codes ...
}

那我们再分析下PlatformViewAndroid的构造函数和Attach方法都做了些什么呢?

PlatformViewAndroid::PlatformViewAndroid()
    : PlatformView(std::make_unique<NullRasterizer>()),
      android_surface_(InitializePlatformSurface()) {}

void PlatformViewAndroid::Attach() {
  CreateEngine();

  // Eagerly setup the IO thread context. We have already setup the surface.
  SetupResourceContextOnIOThread();

  UpdateThreadPriorities();
}

其中: 
1. PlatformViewAndroid的构造函数主要是调用了InitializePlatformSurface方法,这个方法主要是初始化了Surface,其中Surface有Vulkan、OpenGL和Software三种类型的区别。 
2. PlatformViewAndroid::Attach方法这里主要调用三个方法:CreateEngine、SetupResourceContextOnIOThread和UpdateThreadPriorities。 
2.1 CreateEngine比较好理解,创建Engine,这里会重新创建一个Engine对象。 
2.2 SetupResourceContextOnIOThread是在IO线程去准备资源的上下文逻辑。 
2.3 UpdateThreadPriorities是设置线程优先级,这设置GPU线程优先级为-2,UI线程优先级为-1。

3.3 FlutterView初始化

FlutterView的初始化就是纯粹的Android层啦,所以相对比较简单。分析FlutterView.java的构造函数就会发现,整个FlutterView的初始化在确保FlutterNativeView的创建成功和一些必要的view设置之外,主要做了两件事: 
1. 注册SurfaceHolder监听,其中surfaceCreated回调会作为Flutter的第一帧回调使用。 
2. 初始化了Flutter系统需要用到的一系列桥接方法。例如:localization、navigation、keyevent、system、settings、platform、textinput。 
FlutterView初始化流程主要如下图所示:

FlutterView初始化

3.4 Flutter Bundle初始化

Flutter Bundle的初始化是由调用FlutterActivityDelegate.runFlutterBundle开始的,先用一张图来说明下runFlutterBundle方法的调用栈: 

Flutter的Bundle初始化

我们再从源码角度较为深入了解下: 
FlutterActivity的onCreate方法在执行完FlutterActivityDelegate的onCreate方法之后会调用它的runFlutterBundle方法。FlutterActivityDelegate.runFlutterBundle代码如下:

public void runFlutterBundle(){
    // other codes ...

    String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
    if (appBundlePath != null) {
        flutterView.runFromBundle(appBundlePath, null, "main", reuseIsolate);
    }
}

很明显,这个runFlutterBundle并没有做太多事情,而且直接调用了FlutterView.runFromBundle方法。而后兜兜转转最后会调用到PlatformViewAndroid::RunBundleAndSnapshot方法。

void PlatformViewAndroid::RunBundleAndSnapshot(JNIEnv* env, std::string bundle_path,
                                               std::string snapshot_override,
                                               std::string entrypoint,
                                               bool reuse_runtime_controller,
                                               jobject assetManager) {
  // other codes ...

  blink::Threads::UI()->PostTask(
      [engine = engine_->GetWeakPtr(),
       asset_provider = std::move(asset_provider),
       bundle_path = std::move(bundle_path), entrypoint = std::move(entrypoint),
       reuse_runtime_controller = reuse_runtime_controller] {
        if (engine)
          engine->RunBundleWithAssets(
              std::move(asset_provider), std::move(bundle_path),
              std::move(entrypoint), reuse_runtime_controller);
      });
}

PlatformViewAndroid::RunBundleAndSnapshot在UI线程中调用Engine::RunBundleWithAssets,最终调用Engine::DoRunBundle。 
DoRunBundle方法最后只会调用RunFromPrecompiledSnapshot、RunFromKernel和RunFromScriptSnapshot三个方法中的一个。而这三个方法最终都会调用SendStartMessage方法。

bool DartController::SendStartMessage(Dart_Handle root_library,
                                      const std::string& entrypoint) {
  // other codes ...

  // Get the closure of main().
  Dart_Handle main_closure = Dart_GetClosure(
      root_library, Dart_NewStringFromCString(entrypoint.c_str()));

  // other codes ...

  // Grab the ‘dart:isolate‘ library.
  Dart_Handle isolate_lib = Dart_LookupLibrary(ToDart("dart:isolate"));
  DART_CHECK_VALID(isolate_lib);

  // Send the start message containing the entry point by calling
  // _startMainIsolate in dart:isolate.
  const intptr_t kNumIsolateArgs = 2;
  Dart_Handle isolate_args[kNumIsolateArgs];
  isolate_args[0] = main_closure;
  isolate_args[1] = Dart_Null();
  Dart_Handle result = Dart_Invoke(isolate_lib, ToDart("_startMainIsolate"),
                                   kNumIsolateArgs, isolate_args);
  return LogIfError(result);
}

而SendStartMessage方法主要做了三件事: 
1. 获取Flutter入口方法(例如main方法)的closure。 
2. 获取FlutterLibrary。 
3. 发送消息来调用Flutter的入口方法。

4. 总结一下

本次主要分析了下FlutterActivity的onCreate方法中的Flutter初始化部分逻辑,很明显会发现主要耗时在FlutterNativeView、FlutterView和Flutter Bundle的初始化这三块,将这三部分的初始化工作前置就可以比较容易的解决引言中提出的问题。经测试发现,这样改动之后,Flutter页面第一次启动时长和后面几次启动时长差不多一样了。 
对于FlutterMain.startInitialization的初始化逻辑、SendStartMessage发送的消息如何最终调用Flutter中的入口方法逻辑没有进一步深入分析,这些内容后续再继续分析撰文分享。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

原文地址:https://www.cnblogs.com/yunqishequ/p/10019369.html

时间: 2024-10-10 20:21:46

关于Flutter初始化流程,我必须告诉你的是...的相关文章

深入浅出高性能服务发现、配置框架Nacos系列 3: 服务发现:Nacos客户端初始化流程

上一章节,我们从全局了解了一下Nacos项目的模块架构,做到了心中有数,现在,我们去逐步去挖掘里面的代码细节,很多人在学习开源的时候,无从下手,代码那么多,从哪个地方开始看呢?我们可以从一个接口开始入手,这个接口是你使用过的,知道它大概做什么事,有体感的,大家还记得第一章时,我们写的HelloWorld吗,对,就从里面的接口开始剥洋葱. 这个是Nacos的github代码地址,开始之前先start关注一下,加上watch,后续Nacos的邮件列表也会通知到你,可以关注到Nacos的最新实时消息,

SylixOS CAN总线初始化流程解析

概述 本文档是在AT91SAM9X25平台上进行SylixOS CAN总线驱动开发时,对CAN总线初始化流程的分析. 适用于正在学习CAN总线开发的技术工程师. 技术实现 CAN总线的初始化流程可以分成两个部分: 一部分是CAN总线通道资源初始化,主要工作是对通道相关的管脚和中断以及总线编程时需要的时钟等资源的初始化:另一部分是CAN总线的硬件初始化,主要工作是对总线的波特率的设置.接收和发送数据邮箱(相当于缓存区)的初始化以及接收中断和错误中断的使能. CAN总线通道资源初始化 在AT91SA

SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

在我们第一次学Servlet编程,学Java Web的时候,还没有那么多框架.我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转到我们定义好的jsp页面.Servlet类编写完之后在web.xml里注册这个Servlet类. 除此之外,没有其他了.我们启动web服务器,在浏览器中输入地址,就可以看到浏览器上输出我们写好的页面.为了更好的理解上面这个过程,你需要学习关于Servlet生命周期的三个阶段,就是所谓的“init-s

Android技术20:Android的初始化流程

Android系统是如何启动的呢,应用程序是如何启动.下面简要介绍下初始化流程. 1.Android系统的初始化 1.1Android系统会首先启动Linux基础系统,然后引导加载Linux内核并启动初始化进程Init Linux Kernel---->Init(pid=1) 1.2启动守护进程Daemons 启动Usb守护进程,管理USB连接 启动Android Debug Bridge守护进程管理ADB连接 启动Debuggerd Debug守护进程 启动无线接口守护进程管理无线通信 1.3

Android 6.0 SIM卡初始化流程

本文主要讲述Android 6.0 SIM卡初始化流程,这个过程也涉及到UICC框架的初始化,UICC(Universal Integrated Circuit Card)的架构图如下: /** * This class is responsible for keeping all knowledge about * Universal Integrated Circuit Card (UICC), also know as SIM's, * in the system. It is also

【开源】OSharp3.3框架解说系列(7.1):初始化流程概述

本文已同步到系列目录:OSharp快速开发框架解说系列 框架初始化 相对于OSharp 3.0,3.3版本最大的更新,就是从框架级别定义了初始化流程,对初始化功能进行了抽象与封装,不依赖于第三方实现,第三方实现仅作为可替换的服务实现方案存在. 例如,依赖注入功能中,接口与其实现类的映射配置,对象容器的构建,对象的解析获取,都将通过框架定义的API来完成,而Autofac,仅作为这些功能的实现方存在,如果不想使用Autofac,则可以很方便的切换成别的IoC组件. 具体的初始化功能是怎样抽象与定义

Raid1源代码分析--初始化流程

初始化流程代码量比较少,也比较简单.主要是run函数.(我阅读的代码的linux内核版本是2.6.32.61) 四.初始化流程分析 run函数顾名思义,很简单这就是在RAID1开始运行时调用,进行一些初始化的操作.主要是对RAID1中的conf进行初始化.run函数在md.c的do_md_run中被调用.   run函数的具体流程 0.传入参数mddev就是指RAID1所处的MD设备. 1.  定义相关变量. 1.1  定义conf指针,类型为raid1_private_data_s,是raid

Spring IOC容器的初始化流程

IOC初始化流程 Resource定位:指对BeanDefinition的资源定位过程.Bean 可能定义在XML中,或者是一个注解,或者是其他形式.这些都被用Resource来定位, 读取Resource获取BeanDefinition 并注册到 Bean定义注册表中. BeanDefinition的载入:把用户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition. 向IoC容器注册这些BeanDefinition. 获取Bean的流程

Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理

本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看源码追寻Shiro中的过滤器的实现原理. 初始化流程 ShiroFilterFactoryBean实现了FactoryBean接口,那么Spring在初始化的时候必然会调用ShiroFilterFactoryBean的getObject()获取实例,而ShiroFilterFactoryBean也在