Flutter框架介绍 Flutter框架分三层即Framework,Engine, Embedder
Framework使用dart语言实现,包括UI,文本,图片,按钮等Widgets,渲染,动画,手势等。此部分的核心代码是flutter仓库下的flutter package,以及sky_engine仓库下的 io, async, ui(dart:ui库提供了Flutter框架和引擎之间的接口)等package。
Engine使用C++实现,是Flutter的核心引擎,主要包括Skia图形引擎、Dart运行时环境Dart VM、Text文本渲染引擎等。
Embedder是一个嵌入层,通过该层把Flutter嵌入到各个平台上去,Embedder的主要工作包括渲染Surface设置, 线程设置,以及插件等。平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。
FlutterEngine初始化流程剖析 既然FlutterEngine是整个flutter框架的核心,想要深入理解flutter跨平台UI渲染能力就必须对引擎逻辑有所了解以及掌握重要细节流程。下面我们就以android平台为例开始深入剖析FlutterEngine的启动流程。整个Flutter Engine源码包含了非常多的东西,DartVM、Render、Channel、Event等等,所以我们只能从相关的点去看,那么从FlutterEngine初始化来说,我们主要关注和Java层有关联的一些操作。相关的代码主要在flutter/shell/platform/android/io/flutter目录下。
FlutterEngine初始化 FlutterApplication启动时使用了Java层的FlutterMain来进行初始化。代码位置在flutter/shell/platform/android/io/flutter/app/FlutterApplication.java
1 2 3 4 public void onCreate () { super .onCreate(); FlutterMain.startInitialization(this ); }
FlutterMain的startInitialization的最终调用方法为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void startInitialization (@NonNull Context applicationContext, @NonNull FlutterMain.Settings settings) { if (!isRunningInRobolectricTest) { 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); initResources(applicationContext); System.loadLibrary("flutter" ); VsyncWaiter.getInstance((WindowManager)applicationContext.getSystemService("window" )).init(); long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; FlutterJNI.nativeRecordStartTimestamp(initTimeMillis); } } }
从以上代码我们可以看出flutter框架只会在主线程初始化,这也很好理解因为flutter本质是一个跨平台UI框架。初始化的过程也很简单一是配置一些启动参数,二是加载相关资源文件,还有就是注册GPU垂直同步信号。
flutter配置项与资源加载 initConfig 1 2 3 4 5 6 7 8 9 private static void initConfig (@NonNull Context applicationContext) { Bundle metadata = getApplicationInfo(applicationContext).metaData; if (metadata != null ) { sAotSharedLibraryName = metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, "libapp.so" ); sFlutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, "flutter_assets" ); sVmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, "vm_snapshot_data" ); sIsolateSnapshotData = metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, "isolate_snapshot_data" ); } }
从AndroidManfest.xml配置的applicaion节点获取meta-data数据,初始化以下默认值。这些值都是FlutterEngine加载过程中使用到的name,例如,抽取apk中asset资源时,flutter_assets打包目录,打包产物data名称。
1 2 3 4 private static String sAotSharedLibraryName = "libapp.so" ;private static String sVmSnapshotData = "vm_snapshot_data" ;private static String sIsolateSnapshotData = "isolate_snapshot_data" ;private static String sFlutterAssetsDir = "flutter_assets" ;
initResources 1 2 3 4 5 6 7 8 9 10 private static void initResources (@NonNull Context applicationContext) { (new ResourceCleaner(applicationContext)).start(); String dataDirPath = PathUtils.getDataDirectory(applicationContext); String packageName = applicationContext.getPackageName(); PackageManager packageManager = applicationContext.getPackageManager(); AssetManager assetManager = applicationContext.getResources().getAssets(); sResourceExtractor = new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager); sResourceExtractor.addResource(fromFlutterAssets(sVmSnapshotData)).addResource(fromFlutterAssets(sIsolateSnapshotData)).addResource(fromFlutterAssets("kernel_blob.bin" )); sResourceExtractor.start(); }
在Flutter打包apk的asset目录下,包括fluttter_asset目录/资源项,将资源从apk中抽取,保存在Context.getDir(“flutter”, 0) 目录下。
flutter.so加载 在初始化Flutter App资源之后加载了flutter.so,这个就是Flutter Engine源码编译后的产物。 在我们编译Flutter App时,它存在Flutter SDK的flutter.jar中,当生产APK之后,它存在于APK的lib目录下。而当运行时,它被Android虚拟机加载到虚拟内存中。我们知道系统执行完System.loadLibrary 后会在C++层执行一个回调函数就是JNI_OnLoad ,所以在flutter引擎源码中找到相关函数,代码位于flutter/shell/platform/android/library_loader.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 JNIEXPORT jint JNI_OnLoad (JavaVM* vm, void * reserved) { * fml::jni::InitJavaVM(vm); JNIEnv* env = fml::jni::AttachCurrentThread(); bool result = false ; * result = flutter::FlutterMain::Register(env); FML_CHECK(result); * result = flutter::PlatformViewAndroid::Register(env); FML_CHECK(result); * result = flutter::VsyncWaiterAndroid::Register(env); FML_CHECK(result); return JNI_VERSION_1_4; }
一开始进行了Java VM的初始化,其实保存一下当前的Java VM对象到一个全局的变量中, JavaVM的初始化在zygote进程中已经进行了,每个Android进程都是fork出来的。
1 2 3 4 5 6 static JavaVM* g_jvm = nullptr ;void InitJavaVM (JavaVM* vm) { FML_DCHECK(g_jvm == nullptr ); g_jvm = vm; }
一个进程中只有一个JavaVM对象,如果要在线程中访问JavaVM,就需要把当前的thread和JavaVM关联起来。所以调用AttachCurrentThread 我们可以得到一个JNIEnv对象, 每个线程都有一个。JNIEnv定义了很多和JNI调用相关的方法。
1 2 3 4 5 6 7 8 JNIEnv* AttachCurrentThread () { FML_DCHECK(g_jvm != nullptr ) << "Trying to attach to current thread without calling InitJavaVM first." ; JNIEnv* env = nullptr ; jint ret = g_jvm->AttachCurrentThread(&env, nullptr ); FML_DCHECK(JNI_OK == ret); return env; }
后面调用了三个Register方法,作用是注册jni方法,JNI_OnLoad中完成的是jni方法的动态注册,如果没有进行动态注册,JavaVM会默认按照java方法的package name + method name + params 来映射c++方法,这也是一种注册方式-静态注册。由于三个注册方法类似这里重点介绍一下 FlutterMain::Register方法,代码位于flutter/shell/platform/android/flutter_main.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 bool FlutterMain::Register(JNIEnv* env) { static const JNINativeMethod methods[] = { { .name = "nativeInit" , .signature = "(Landroid/content/Context;[Ljava/lang/String;Ljava/" "lang/String;Ljava/lang/String;Ljava/lang/String;)V" , .fnPtr = reinterpret_cast <void *>(&Init), }, { .name = "nativeRecordStartTimestamp" , .signature = "(J)V" , .fnPtr = reinterpret_cast <void *>(&RecordStartTimestamp), }, }; jclass clazz = env->FindClass("io/flutter/embedding/engine/FlutterJNI" ); if (clazz == nullptr ) { return false ; } return env->RegisterNatives(clazz, methods, fml::size(methods)) == 0 ; }
这个方法就是将C++方法Init和RecordStartTimestamp方法分别与ava层FlutterJNI中的nativeInit和nativeRecordStartTimestamp方法绑定,这样就能通过jni实现java代码调用C++代码。另外两个Register方法的作用是一样的,也是注册对应的native方法,具体代码在:flutter_engine/shell/platform/android/platform_view_android.h flutter_engine/shell/platform/android/vsync_waiter_android.h
C++层的初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 void FlutterMain::Init(JNIEnv* env, jclass clazz, jobject context, jobjectArray jargs, jstring kernelPath, 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); flutter::DartCallbackCache::SetCachePath( fml::jni::JavaStringToString(env, appStoragePath)); fml::paths::InitializeAndroidCachesPath( fml::jni::JavaStringToString(env, engineCachesPath)); flutter::DartCallbackCache::LoadCacheFromDisk(); if (!flutter::DartVM::IsRunningPrecompiledCode() && kernelPath) { auto application_kernel_path = fml::jni::JavaStringToString(env, kernelPath); 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_t key) { fml::MessageLoop::GetCurrent().RemoveTaskObserver(key); }; #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG auto make_mapping_callback = [](const uint8_t * mapping, size_t size) { return [mapping, size]() { return std ::make_unique<fml::NonOwnedMapping>(mapping, size); }; }; settings.dart_library_sources_kernel = make_mapping_callback(kPlatformStrongDill, kPlatformStrongDillSize); #endif g_flutter_main.reset(new FlutterMain(std ::move(settings))); g_flutter_main->SetupObservatoryUriCallback(env); }
上述代码只是将java层传过来的初始化参数校验拼装成flutter::Settings对象,然后在创建FlutterMain时传入。查看FlutterMain的构造函数可以看到并没有什么特殊操作,只是将setting对象赋值给常量成员_settings
1 2 FlutterMain::FlutterMain(flutter::Settings settings) : settings_(std ::move(settings)), observatory_uri_callback_() {}
注册垂直同步信号 startInitialization
方法中初始化了GPU垂直同步信号,用来将GPU发出的渲染信号通知给引擎和Dart层。
1 VsyncWaiter.getInstance((WindowManager)applicationContext.getSystemService("window" )).init();
在android平台上Choreographer 类管理GPU帧信号与View绘制相关工作,它有个doFrame 回调通知业务层某一帧开始绘制,其实VsyncWaiter 的init 方法就是向Choreographer 注册了一个doFrame 回调。
1 2 3 4 5 6 7 8 9 10 11 private final AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate = new AsyncWaitForVsyncDelegate() { public void asyncWaitForVsync (final long cookie) { Choreographer.getInstance().postFrameCallback(new FrameCallback() { public void doFrame (long frameTimeNanos) { float fps = VsyncWaiter.this .windowManager.getDefaultDisplay().getRefreshRate(); long refreshPeriodNanos = (long )(1.0E9 D / (double )fps); FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie); } }); } };
由以上代码可以看到在GPU发出Vsync信号后,会通过JNI调用C++层渲染逻辑,即上述代码中FlutterJNI.nativeOnVsync 完成的逻辑
总结 纵观FlutterApplication中的初始化逻辑,并没有很复杂和耗时的操作,都是一些准备工作,如下图:
总结其主要工作有以下几点:
确保该方法必须运行在主线程,否则抛出异常;
初始化配置参数,vm_snapshot_data、vm_snapshot_instr、isolate_snapshot_data、isolate_snapshot_instr、flutter_assets的值
获取应用根目录下的所有assets资源路径,提取产物资源,加载到内存;
加载libflutter.so库,调用JNI_OnLoad();
Android进程自身会创建JavaVM,此处将当前进程的JavaVM实例保存在静态变量g_jvm,再将当前线程和JavaVM建立关联
注册FlutterMain的JNI方法,nativeInit()和nativeRecordStartTimestamp()
注册PlatformViewAndroid的一系列方法,完成Java和C++的一些方法互调;
注册VSyncWaiter的nativeOnVsync()用于Java调用C++,asyncWaitForVsync()用于C++调用Java;
最终将FlutterApplication的启动耗时记录,通过engine_main_enter_ts变量。