作者:闲鱼技术-然道

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中的入口方法逻辑没有进一步深入分析,这些内容后续再继续分析撰文分享。

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

  1. 深入分析 Flutter 初始化流程

    在调研 Flutter 动态化方案的时候,需要了解 Flutter 加载 dart 产物的流程,阅读了一部分源码,顺便也读了初始化相关的代码.于是梳理了一遍 Flutter 的初始化流程 flutte ...

  2. SD/eMMC初始化流程、读写流程(dwc mshc)

    目录 1.芯片简介 1.1 模块与接口 1.2 SD/eMMC初始化流程 1.3 SD/eMMC数据传输流程 1.3.1 传输模式 2.调试问题 2.1 uboot 2.1.1 连续读4个以上bloc ...

  3. Linux驱动——mmc sd card初始化流程(十一)

    Linux驱动--mmc sd card初始化流程(十一) 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客:   (1)[sd ...

  4. OSAL初始化流程分析

    我使用的协议栈版本及例子信息: ZigBee2006\Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SampleApp    ...

  5. Direct3D 12入门教程之 ---- Direct3D 12初始化流程

    注:以下内容参考自 书籍:<DirectX 12 3D>游戏开发实战, 微软官方的 DirectX样例程序:DirectX-Graphics-Samples, 参见github链接:htt ...

  6. [sd card] sd card初始化流程

    以SD 3.0为例. 建议先参考<[sd card] SD card初始化时的总线设置>. 一.sd card初始化流程思路说明 通过<SD_Ver3.00_Final_090416 ...

  7. Spring(二)IOC容器的初始化流程

    文章目录 一.Spring 核心容器类 1.1 BeanFactory 1.2 ApplicationContext 1.3 BeanDefinition 二.IOC容器的初始化 2.1 基于Xml的 ...

  8. Linux内核网络栈1.2.13-网卡设备的初始化流程

    参考资料 <<linux内核网络栈源代码情景分析>> 网卡设备的初始化 本文主要描述一下网卡设备的整个初始化的过程,该过程主要就是根据设备的硬件信息来获取与传输网络数据,注册相 ...

  9. 开机流程简介--init 处理系统初始化流程 (/etc/rc.d/rc.sysinit)

    init 处理系统初始化流程 (/etc/rc.d/rc.sysinit) 还记得上面提到 /etc/inittab 里头有这一句' si::sysinit:/etc/rc.d/rc.sysinit ...

最新文章

  1. GIS软件开发工具包TatukGIS Developer Kernel 发布 v11.3.0-Unstable1丨附下载
  2. 一个NPOI导出到excel文件的范例记录
  3. java线程阻塞唤醒的四种方式
  4. 718. Maximum Length of Repeated Subarray 最长重复子数组
  5. 教学思路C#之入门一 认识简单的C#结构
  6. C++中对于类来说头文件(.h)和源文件(.cpp)都应该写些什么 (类的常规创建)
  7. 算法设计与分析——递归与分治策略——循环日程赛
  8. 项目实战大全,提升经验的最好办法(一)
  9. 在js中网页面写入数据时需要注意的几点
  10. 2021.01.04
  11. Fiddler4入门——手机抓包
  12. 网页数据导出为Excel(带图片)
  13. 三种Moran's I(空间自相关性) 之我见
  14. 将一个数组划分成总和相等的两部分(分割数组)
  15. 数学_矩阵向量求导公式相关
  16. python如何设置窗口为活动窗口
  17. 基于Gin+Vue+ElementUI实现的OA办公系统
  18. [code] PTA 胡凡算法笔记 DAY003
  19. python写等腰三角形的性质_杨辉三角—知识点详解 - osc_8cfq8uoa的个人空间 - OSCHINA - 中文开源技术交流社区...
  20. CentOS7.5.1804安装yum流程

热门文章

  1. 把整个DIV变成超链接
  2. DHCP企业应用指南
  3. 持币过节也能让钱生钱
  4. shell脚本——注释(单行注释 多行注释)
  5. C语言入门经典——基础知识(指针 数组 多维数组)
  6. .NET Core微服务系列基础文章索引(目录导航Final版)
  7. String常用方法总结
  8. 洛谷 [P1198] 最大数
  9. 测试服务搭建之centos7下安装java
  10. 一款免费好用的代码在线比较工具