热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【干货】关于Flutter初始化,我必须告诉你的是...

作者:闲鱼技术-然道1.引言最近在做性能优化的时候发现,在混合栈开发中,第一次启动Flutter页面的耗时总会是第二次启动Flutter页面耗时的两倍左右,这样给人感觉很不好。分析

作者:闲鱼技术-然道

1. 引言

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

2. Flutter初始化时序

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

《【干货】关于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构造函数的调用栈:

《【干货】关于Flutter初始化,我必须告诉你的是...》
《【干货】关于Flutter初始化,我必须告诉你的是...》

FlutterNativeView构造函数调用栈

从上图的调用栈中我们知道FlutterNativeView的初始化主要做了些什么,我们再从源码角度较为深入的了解下:
FlutterNativeView的构造函数最终主要调用了一个nativeAttach方法。到这里就需要分析引擎层代码了,我们可以在JNI文件中找到对应的jni方法调用。(具体文件为http://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初始化流程主要如下图所示:

《【干货】关于Flutter初始化,我必须告诉你的是...》
《【干货】关于Flutter初始化,我必须告诉你的是...》

FlutterView初始化

3.4 Flutter Bundle初始化

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

《【干货】关于Flutter初始化,我必须告诉你的是...》
《【干货】关于Flutter初始化,我必须告诉你的是...》

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


推荐阅读
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Mono为何能跨平台
    概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 本文介绍了Java集合库的使用方法,包括如何方便地重复使用集合以及下溯造型的应用。通过使用集合库,可以方便地取用各种集合,并将其插入到自己的程序中。为了使集合能够重复使用,Java提供了一种通用类型,即Object类型。通过添加指向集合的对象句柄,可以实现对集合的重复使用。然而,由于集合只能容纳Object类型,当向集合中添加对象句柄时,会丢失其身份或标识信息。为了恢复其本来面貌,可以使用下溯造型。本文还介绍了Java 1.2集合库的特点和优势。 ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 这个问题困扰了我两天,卸载Dr.COM客户端(我们学校上网要装这个客户端登陆服务器,以后只能在网页里输入用户名和密码了),问题解决了。问题的现象:在实验室机台式机上安装openfire和sp ... [详细]
author-avatar
手机用户2502911563
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有