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源码,逻辑还是比较清晰的。

  1. public static void startInitialization(Context applicationContext, Settings settings) {
  2. // other codes ...
  3. initConfig(applicationContext);
  4. initAot(applicationContext);
  5. initResources(applicationContext);
  6. System.loadLibrary("flutter");
  7. // other codes ...
  8. }

3.2 FlutterNativeView初始化

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

FlutterNativeView构造函数调用栈

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

  1. static const JNINativeMethod native_view_methods[] = {
  2. {
  3. .name = "nativeAttach",
  4. .signature = "(Lio/flutter/view/FlutterNativeView;)J",
  5. .fnPtr = reinterpret_cast<void*>(&shell::Attach),
  6. },
  7. // other codes ...
  8. };

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

  1. static jlong Attach(JNIEnv* env, jclass clazz, jobject flutterView) {
  2. auto view = new PlatformViewAndroid();
  3. // other codes ...
  4. view->Attach();
  5. // other codes ...
  6. }

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

  1. PlatformViewAndroid::PlatformViewAndroid()
  2. : PlatformView(std::make_unique<NullRasterizer>()),
  3. android_surface_(InitializePlatformSurface()) {}
  4. void PlatformViewAndroid::Attach() {
  5. CreateEngine();
  6. // Eagerly setup the IO thread context. We have already setup the surface.
  7. SetupResourceContextOnIOThread();
  8. UpdateThreadPriorities();
  9. }

其中: 
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代码如下:

  1. public void runFlutterBundle(){
  2. // other codes ...
  3. String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
  4. if (appBundlePath != null) {
  5. flutterView.runFromBundle(appBundlePath, null, "main", reuseIsolate);
  6. }
  7. }

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

  1. void PlatformViewAndroid::RunBundleAndSnapshot(JNIEnv* env, std::string bundle_path,
  2. std::string snapshot_override,
  3. std::string entrypoint,
  4. bool reuse_runtime_controller,
  5. jobject assetManager) {
  6. // other codes ...
  7. blink::Threads::UI()->PostTask(
  8. [engine = engine_->GetWeakPtr(),
  9. asset_provider = std::move(asset_provider),
  10. bundle_path = std::move(bundle_path), entrypoint = std::move(entrypoint),
  11. reuse_runtime_controller = reuse_runtime_controller] {
  12. if (engine)
  13. engine->RunBundleWithAssets(
  14. std::move(asset_provider), std::move(bundle_path),
  15. std::move(entrypoint), reuse_runtime_controller);
  16. });
  17. }

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

  1. bool DartController::SendStartMessage(Dart_Handle root_library,
  2. const std::string& entrypoint) {
  3. // other codes ...
  4. // Get the closure of main().
  5. Dart_Handle main_closure = Dart_GetClosure(
  6. root_library, Dart_NewStringFromCString(entrypoint.c_str()));
  7. // other codes ...
  8. // Grab the 'dart:isolate' library.
  9. Dart_Handle isolate_lib = Dart_LookupLibrary(ToDart("dart:isolate"));
  10. DART_CHECK_VALID(isolate_lib);
  11. // Send the start message containing the entry point by calling
  12. // _startMainIsolate in dart:isolate.
  13. const intptr_t kNumIsolateArgs = 2;
  14. Dart_Handle isolate_args[kNumIsolateArgs];
  15. isolate_args[0] = main_closure;
  16. isolate_args[1] = Dart_Null();
  17. Dart_Handle result = Dart_Invoke(isolate_lib, ToDart("_startMainIsolate"),
  18. kNumIsolateArgs, isolate_args);
  19. return LogIfError(result);
  20. }

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

4. 总结一下

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

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

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

  1. Z-stack之OSAL初始化流程

    转自点击打开链接 我使用的协议栈版本及例子信息: ZigBee2006\Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\Sam ...

  2. spring自动扫描、DispatcherServlet初始化流程、spring控制器Controller 过程剖析

    spring自动扫描1.自动扫描解析器ComponentScanBeanDefinitionParser,从doScan开始扫描解析指定包路径下的类注解信息并注册到工厂容器中. 2.进入后findCa ...

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

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

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

    在我们第一次学Servlet编程,学Java Web的时候,还没有那么多框架.我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转 ...

  5. u-boot中nandflash初始化流程分析(转)

    u-boot中nandflash初始化流程分析(转) 原文地址http://zhuairlunjj.blog.163.com/blog/static/80050945201092011249136/ ...

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

    初始化流程代码量比较少,也比较简单.主要是run函数.(我阅读的代码的linux内核版本是2.6.32.61) 四.初始化流程分析 run函数顾名思义,很简单这就是在RAID1开始运行时调用,进行一些 ...

  7. main之前初始化流程

    main之前初始化流程 本文分别介绍Keil调用的ARMCC以及ARM-NONE-EABI-GCC两个编译器在main之前的操作: Keil MDK启动文件 总结一下MDK的启动流程: 1.系统初始化 ...

  8. VxWorks各部分初始化流程

    一)configAll.h中定义所有定置系统配置的宏 INCLUDED SOFTWARE FACILITIES:定义了基本组件: EXCLUDED FACILITIES:定义了扩充组件,缺省不包括: ...

  9. interface21 - web - DispatcherServlet(DispatcherServlet初始化流程)

    前言 最近打算花点时间好好看看spring的源码,然而现在Spring的源码经过迭代的版本太多了,比较庞大,看起来比较累,所以准备从最初的版本(interface21)开始入手,仅用于学习,理解其设计 ...

随机推荐

  1. 2019.03.26 bzoj4444: [Scoi2015]国旗计划(线段树+倍增)

    传送门 题意简述:现在给你一个长度为mmm的环,有nnn条互不包含的线段,问如果强制选第iii条线段至少需要用几条线段覆盖这个环,注意用来的覆盖的线段应该相交,即[1,3],[4,5][1,3],[4 ...

  2. Codeforces 863 简要题解

    文章目录 A题 B题 C题 D题 E题 F题 G题 传送门 简要题解?因为最后一题太毒不想写了所以其实是部分题解... A题 传送门 题意简述:给你一个数,问你能不能通过加前导000使其成为一个回文数 ...

  3. 图像之王ImageMagick

    这是我目前能想到的名字.很久前某图像群看到有人推荐过,试了一下确实厉害,支持的格式之多让人叹服. http://www.imagemagick.org/script/formats.php 一般用法 ...

  4. \usepackage{ulem}带下划线的问题解决

    其实正文应该是斜体字的,但是有可能默认模板会导致斜体变下划线的问题,解决方法如下: \usepackage{ulem} 在 \documentclass[format=acmsmall, review ...

  5. 使用@Autowird注入报空指针异常

    new的对象不能调用此对象里面注入的其他类,如果想要调用注入的其他类,则此new的对象要使用@componet将此类注入. 原因:

  6. opencv2.4.13+python2.7学习笔记--opencv中的Gui特性--图片:读图像,显示图像,保存图像

    阅读对象:可以配置opencv+Python环境的任何人,毕竟写这篇文章的人就是小白. 1.环境说明 1.1opencv版本: 1.2Python版本: 1.3系统:win7 注: (1)opencv ...

  7. 缓存,减少对sql语句的访问

    一级缓存 SqlSession 的缓存  ------>自动开启 二级缓存: 做到从不同的缓存中共享数据 SqlSessionFactory 的缓存 --->需要手动开启 映射配置文件中配 ...

  8. 三级菜单,可以退出到上一级菜单和全部退出(low版本)

    menu = { '北京':{ '海淀':{ '五道口':{ 'soho':{}, '网易':{}, 'google':{} }, '中关村':{ '爱奇艺':{}, '汽车之家':{}, 'youk ...

  9. 我的C#跨平台之旅(六):发布应用

    由于此架构从一开始就将.NET Framework 的依赖降低到最低,且不依赖IIS,在ORM层面,完全实现代码优先,即真正做到数据库无关: Windows服务器部署: 在Windows应用服务器上安 ...

  10. 如何实现一个基于 jupyter 的 microservices

    零.背景: 现有基于 Node.js 的项目,但需要整合 Data Science 同事的基于 python(jupyter) 的代码部分,以实现额外的数据分析功能.于是设想实现一个 microser ...