文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Flutter 2 Router 从入门到放弃 - 实现原理与源码分析(一)

2024-12-02 23:47

关注

一、Flutter 2 源码编译调试

工欲善其事,必先利其器,这里我们先对源码编译和调试步骤进行说明:

源码编译

安装 depot_tools,配置环境变量

  1. git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 
  2. export PATH=/path/to/depot_tools:$PATH 

创建空的 engine 目录并在目录中创建 .gclient 配置文件,在 .gclient 中配置从 flutter/engine 主工程 fork 出的 github 工程地址,.gclient 配置如下

  1. solutions = [ 
  2.   { 
  3.     "managed"False, 
  4.     "name""src/flutter", 
  5.     "url""https://github.com/Alex0605/engine.git", 
  6.     "custom_deps": {}, 
  7.     "deps_file""DEPS", 
  8.     "safesync_url""", 
  9.   }, 

在 engine 目录中执行 gclient sync

切换源码。编译前的一个重要操作是将源码切换到本地 Flutter SDK 的 engine version 对应的提交点

  1. # 查看本地 Flutter SDK 引擎版本, 这个文件中是包含对应的 commit id 
  2. vim src/flutter/bin/internal/engine.version 
  3.  
  4. # 调整代码 
  5. cd engine/src/flutter 
  6. git reset --hard  
  7. gclient sync -D --with_branch_heads --with_tags 
  8.  
  9. # 准备构建文件 
  10. cd engine/src 
  11.  
  12. #Android 
  13. # 使用以下命令生成 host_debug_unopt 编译配置 
  14. ./flutter/tools/gn --unoptimized 
  15. # android arm (armeabi-v7a) 编译配置 
  16. ./flutter/tools/gn --android --unoptimized 
  17. # android arm64 (armeabi-v8a) 编译配置 
  18. ./flutter/tools/gn --android --unoptimized --runtime-mode=debug --android-cpu=arm64 
  19. # 编译 
  20. ninja -C out/host_debug_unopt -j 16 
  21. ninja -C out/android_debug_unopt -j 16 
  22. ninja -C out/android_debug_unopt_arm64 -j 16 
  23.  
  24. #iOS 
  25. # unopt-debug 
  26. ./flutter/tools/gn --unoptimized --ios --runtime-mode debug --ios-cpu arm 
  27. ./flutter/tools/gn --unoptimized --ios --runtime-mode debug --ios-cpu arm64 
  28.  
  29. ./flutter/tools/gn --unoptimized --runtime-mode debug --ios-cpu arm 
  30. ./flutter/tools/gn --unoptimized --runtime-mode debug --ios-cpu arm64 
  31.  
  32. ninja -C out/ios_debug_unopt_arm 
  33. ninja -C out/ios_debug_unopt 
  34. ninja -C out/host_debug_unopt_arm 
  35. ninja -C out/host_debug_unopt 

编译完成后的目录如下:

源码运行调试

通过命令创建一个 flutter 工程

flutter create --org com.wedotor.flutter source_code

用 android studio 打开创建的 android 工程

在 gradle.properties 文件中添加 localEngineOut 属性,配置如下:

  1. org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 
  2. android.useAndroidX=true 
  3. android.enableJitfier=true 
  4. localEngineOut=/Users/zhoujh/myproj/3-proj/flutter/engine/src/out/android_debug_unopt_arm64 

将 engine/src/flutter/shell/platform/android 工程(称之为* Flutter 引擎工程*)导入到 Android Studio

使用自定义 Flutter 引擎运行 Flutter App(称之为 Flutter App 工程),具体如 1-3 步所述

Flutter 引擎工程 中给源码设置断点并启动 Debugger 连接到已启动的 Flutter App 进程

PS:这里 C++ 代码我用的是 Clion 阅读,这里配置比较简单,将上面生成的 compile_commands.json 文件复制到 src/flutter 目录中,然后使用 Clion 打开项目,indexing 之后便可以跟踪跳转

二、Flutter 2 源码阅读

进行源码分析之前,先了解一下官方文档中提供的核心架构图,它也代表着整个 Flutter 架构。

Flutter 的架构主要分成三层:Framework,Engine 和 Embedder。

1)、Framework:Framework 使用 dart 实现,包括 Material Design 风格的 Widget,Cupertino(针对 iOS)风格的 Widgets,文本/图片/按钮等基础 Widgets,渲染,动画,手势等。此部分的核心代码是:flutter 仓库下的 flutter package,以及 sky_engine 仓库下的 io,async ,ui (dart:ui 库提供了 Flutter 框架和引擎之间的接口)等 package。其中 dart:ui 库是对 Engine 中 Skia 库的 C++ 接口的绑定。向上层提供了 window、text、canvas 等通用的绘制能力,通过 dart:ui 库就能使用 Dart 代码操作 Skia 绘制引擎。所以我们实际上可以通过实例化 dart:ui 包中的类(例如 Canvas、Paint 等)来绘制界面。然而,除了绘制,还要考虑到协调布局和响应触摸等情况,这一切实现起来都异常麻烦,这也正是 Framework 帮我们做的事。渲染层 Rendering 是在 ::dart:ui 库之上的第一个抽象层,它为你做了所有繁重的数学工作。为了做到这一点,它使用 RenderObject 对象,该对象是真正绘制到屏幕上的渲染对象。由这些 RenderObject 组成的树处理真正的布局和绘制。

2)、Engine:Engine 使用 C++ 实现,主要包括:Skia,Dart 和 Text。Skia 是开源的二维图形库,提供了适用于多种软硬件平台的通用 API。在安卓上,系统自带了 Skia,在 iOS 上,则需要 APP 打包 Skia 库,这会导致 Flutter 开发的 iOS 应用安装包体积更大。Dart 运行时则可以以 JIT、JIT Snapshot 或者 AOT 的模式运行 Dart 代码。

3)、Embedder:Embedder 是一个嵌入层,即把 Flutter 嵌入到各个平台上去,这里做的主要工作包括渲染 Surface 设置,线程设置,以及插件等。从这里可以看出,Flutter 的平台相关层很低,平台(如 iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在 Flutter 内部,这就使得它具有了很好的跨端一致性。

启动 app 时会在 Application onCreate 方法中创建 FlutterEngineGroup 对象

  1. public void onCreate() { 
  2.     super.onCreate(); 
  3.     // 创建 FlutterEngineGroup 对象 
  4.     engineGroup = new FlutterEngineGroup(this); 

在创建 FlutterEngineGroup 时,使通过该引擎组创建的子引擎共享资源,比单独通 FlutterEngine 构造函数创建,创建速度的更快、占用内存更少,在创建或重新创建第一个引擎时,行为与通过 FlutterEngine 构造函数创建相同。当创建后续的引擎时,会重新使用现有的引擎中的资源。共享资源会一直保留到最后一个引擎被销毁。删除 FlutterEngineGroup 不会使其现有的已创建引擎失效,但它无法再创建更多的 FlutterEngine。

  1. //src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java 
  2. public FlutterEngineGroup(@NonNull Context context, @Nullable String[] dartVmArgs) { 
  3.   FlutterLoader loader = FlutterInjector.instance().flutterLoader(); 
  4.   if (!loader.initialized()) { 
  5.     loader.startInitialization(context.getApplicationContext()); 
  6.     loader.ensureInitializationComplete(context, dartVmArgs); 
  7.   } 

FlutterLoader 的 startInitialization 将加载 Flutter 引擎的本机库 flutter.so 以启用后续的 JNI 调用。还将查找解压打包在 apk 中的 dart 资源,而且方法只会被调用一次。该方法具体调用步骤:

1)、settings 属性是否赋值来确定方法是否执行过;

2)、方法必须在主线程中执行,否则抛异常退出;

3)、获取 app 上下文;

4)、VsyncWaiter 是同步帧率相关的操作;

5)、记录初始化耗时时间;

6)、从 flutter2 开始,初始化配置、初始化资源、加载 flutter.so 动态库,都放在后台子线程中运行,加快了初始化速度。

  1. //src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java 
  2. public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) { 
  3.   //初始化方法只能运行一次 
  4.   if (this.settings != null) { 
  5.     return
  6.   } 
  7.  //必须在主线程上调用 startInitialization 
  8.   if (Looper.myLooper() != Looper.getMainLooper()) { 
  9.     throw new IllegalStateException("startInitialization must be called on the main thread"); 
  10.   } 
  11.  
  12.   // 获取 app 的上下文 
  13.   final Context appContext = applicationContext.getApplicationContext(); 
  14.  
  15.   this.settings = settings; 
  16.  
  17.   initStartTimestampMillis = SystemClock.uptimeMillis(); 
  18.  //获取 app 相关信息 
  19.   flutterApplicationInfo = ApplicationInfoLoader.load(appContext); 
  20.   VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE)) 
  21.       .init(); 
  22.  
  23.   //将后台线程用于需要磁盘访问的初始化任务 
  24.   Callable initTask = 
  25.       new Callable() { 
  26.         @Override 
  27.         public InitResult call() { 
  28.      //获取配置资源 
  29.           ResourceExtractor resourceExtractor = initResources(appContext); 
  30.      //加载 fluter 本地 so 库 
  31.           flutterJNI.loadLibrary(); 
  32.     
  33.           Executors.newSingleThreadExecutor() 
  34.               .execute
  35.                   new Runnable() { 
  36.                     @Override 
  37.                     public void run() { 
  38.            //预加载 skia 字体库 
  39.                       flutterJNI.prefetchDefaultFontManager(); 
  40.                     } 
  41.                   }); 
  42.  
  43.           if (resourceExtractor != null) { 
  44.       //等待初始化时的资源初始化完毕后才会向下执行,否则会一直阻塞 
  45.             resourceExtractor.waitForCompletion(); 
  46.           } 
  47.  
  48.           return new InitResult( 
  49.               PathUtils.getFilesDir(appContext), 
  50.               PathUtils.getCacheDirectory(appContext), 
  51.               PathUtils.getDataDirectory(appContext)); 
  52.         } 
  53.       }; 
  54.   initResultFuture = Executors.newSingleThreadExecutor().submit(initTask); 

initResources:将 apk 中的资源文件复制到应用本地文件中,在 DEBUG 或者在 JIT_RELEASE 模式下安装 Flutter 资源,主要由 ResourceExtractor 来异步执行资源文件的解压缩操作,最终会将 apk 中 assets 中的 Dart 资源 vm_snapshot_data、isolate_snapshot_data、kernel_blob.bin 文件安装到应用目录 app_flutter 目录下。

  1. private ResourceExtractor initResources(@NonNull Context applicationContext) { 
  2.   ResourceExtractor resourceExtractor = null
  3.   if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) { 
  4.   //获取 flutter 数据存储路径 
  5.     final String dataDirPath = PathUtils.getDataDirectory(applicationContext); 
  6.   //获取包名 
  7.     final String packageName = applicationContext.getPackageName(); 
  8.     final PackageManager packageManager = applicationContext.getPackageManager(); 
  9.     final AssetManager assetManager = applicationContext.getResources().getAssets(); 
  10.     resourceExtractor = 
  11.         new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager); 
  12.     resourceExtractor 
  13.         .addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData)) 
  14.         .addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData)) 
  15.         .addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB)); 
  16.     resourceExtractor.start(); 
  17.   } 
  18.   return resourceExtractor; 

在初始化 startInitialization 时,也会调用 ensureInitializationComplete 方法确认初始化是否完成,然后里面会把 so 文件的地址给到 shellArgs 里传入 FlutterJNI,因此我们可以通过修改 flutter 生成的代码或者使用 hook 等方式替换 ListshellArgs 的 add 方法,从而改变 so 的路径,进行热修复。

  1. public void ensureInitializationComplete( 
  2.     @NonNull Context applicationContext, @Nullable String[] args) { 
  3.   if (initialized) { 
  4.     return
  5.   } 
  6.   if (Looper.myLooper() != Looper.getMainLooper()) { 
  7.     throw new IllegalStateException( 
  8.         "ensureInitializationComplete must be called on the main thread"); 
  9.   } 
  10.   if (settings == null) { 
  11.     throw new IllegalStateException( 
  12.         "ensureInitializationComplete must be called after startInitialization"); 
  13.   } 
  14.   try { 
  15.     InitResult result = initResultFuture.get(); 
  16.  
  17.     List shellArgs = new ArrayList<>(); 
  18.  
  19.   // 此处省略具体参数配置代码... 
  20.  
  21.     long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; 
  22.  
  23.   // 初始化 JNI 
  24.     flutterJNI.init( 
  25.         applicationContext, 
  26.         shellArgs.toArray(new String[0]), 
  27.         kernelPath, 
  28.         result.appStoragePath, 
  29.         result.engineCachesPath, 
  30.         initTimeMillis); 
  31.  
  32.     initialized = true
  33.   } catch (Exception e) { 
  34.     Log.e(TAG, "Flutter initialization failed.", e); 
  35.     throw new RuntimeException(e); 
  36.   } 

FlutterJNI 初始化

  1. public void init( 
  2.       @NonNull Context context, 
  3.       @NonNull String[] args, 
  4.       @Nullable String bundlePath, 
  5.       @NonNull String appStoragePath, 
  6.       @NonNull String engineCachesPath, 
  7.       long initTimeMillis) { 
  8.     if (FlutterJNI.initCalled) { 
  9.       Log.w(TAG, "FlutterJNI.init called more than once"); 
  10.     } 
  11.   //调用 JNI 中 flutter 初始化方法 
  12.     FlutterJNI.nativeInit( 
  13.         context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); 
  14.     FlutterJNI.initCalled = true
  15.   } 

在初始化资源之后就开始加载 flutter.so,这个就是 Flutter Engine 源码编译后的产物。当运行时,它被 Android 虚拟机加载到虚拟内存中。(so 是一个标准的 ELF 可执行文件,主要分为 .data 和 .text 段,分别包含了数据和指令,加载到虚拟内存后,指令可以被 CPU 执行) 加载了 flutter.so 之后,最先被执行的是里面的 JNI_OnLoad 方法 ,会注册 FlutterMain 、PlatformView、VSyncWaiter 的 jni 方法。

  1. //src/flutter/shell/platform/android/library_loader.cc 
  2.  
  3. JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { 
  4.   // 开始进行 Java VM 的初始化,是保存当前的 Java VM 对象到一个全局的变量中 
  5.   fml::jni::InitJavaVM(vm); 
  6.  
  7.  //把当前的 thread 和 JavaVM 关联起来 
  8.   JNIEnv* env = fml::jni::AttachCurrentThread(); 
  9.   bool result = false
  10.  
  11.   // 注册 FlutterMain,就是把 Java 层的 native 方法和 C++层的方法关联起来 
  12.   result = flutter::FlutterMain::Register(env); 
  13.   FML_CHECK(result); 
  14.  
  15.   // 注册 PlatformView 
  16.   result = flutter::PlatformViewAndroid::Register(env); 
  17.   FML_CHECK(result); 
  18.  
  19.   // 注册 VSyncWaiter. 
  20.   result = flutter::VsyncWaiterAndroid::Register(env); 
  21.   FML_CHECK(result); 
  22.  
  23.   return JNI_VERSION_1_4; 

系统初始化完成之后,会调用 NativeInit 这个 native方法,对应的 FlutterMain.cc::Init 方法。这里初始化主要是根据传入的参数生成了一个 Settings 对象。

  1. // src/flutter/shell/platform/android/flutter_main.cc 
  2. void FlutterMain::Init(JNIEnv* env, 
  3.                        jclass clazz, 
  4.                        jobject context, 
  5.                        jobjectArray jargs, 
  6.                        jstring kernelPath, 
  7.                        jstring appStoragePath, 
  8.                        jstring engineCachesPath, 
  9.                        jlong initTimeMillis) { 
  10.   std::vector args; 
  11.   args.push_back("flutter"); 
  12.   for (auto& arg : fml::jni::StringArrayToVector(env, jargs)) { 
  13.     args.push_back(std::move(arg)); 
  14.   } 
  15.   auto command_line = fml::CommandLineFromIterators(args.begin(), args.end()); 
  16.  
  17.   auto settings = SettingsFromCommandLine(command_line); 
  18.  
  19.   int64_t init_time_micros = initTimeMillis * 1000; 
  20.   settings.engine_start_timestamp = 
  21.       std::chrono::microseconds(Dart_TimelineGetMicros() - init_time_micros); 
  22.  
  23.   flutter::DartCallbackCache::SetCachePath( 
  24.       fml::jni::JavaStringToString(env, appStoragePath)); 
  25.  
  26.   fml::paths::InitializeAndroidCachesPath( 
  27.       fml::jni::JavaStringToString(env, engineCachesPath)); 
  28.  
  29.   flutter::DartCallbackCache::LoadCacheFromDisk(); 
  30.  
  31.   if (!flutter::DartVM::IsRunningPrecompiledCode() && kernelPath) { 
  32.     auto application_kernel_path = 
  33.         fml::jni::JavaStringToString(env, kernelPath); 
  34.  
  35.     if (fml::IsFile(application_kernel_path)) { 
  36.       settings.application_kernel_asset = application_kernel_path; 
  37.     } 
  38.   } 
  39.  
  40.   settings.task_observer_add = [](intptr_t key, fml::closure callback) { 
  41.     fml::MessageLoop::GetCurrent().AddTaskObserver(key, std::move(callback)); 
  42.   }; 
  43.  
  44.   settings.task_observer_remove = [](intptr_t key) { 
  45.     fml::MessageLoop::GetCurrent().RemoveTaskObserver(key); 
  46.   }; 
  47.  
  48.   settings.log_message_callback = [](const std::string& tag, 
  49.                                      const std::string& message) { 
  50.     __android_log_print(ANDROID_LOG_INFO, tag.c_str(), "%.*s", 
  51.                         (int)message.size(), message.c_str()); 
  52.   }; 
  53.  
  54. #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG 
  55.   auto make_mapping_callback = [](const uint8_t* mapping, size_t size) { 
  56.     return [mapping, size]() { 
  57.       return std::make_unique(mapping, size); 
  58.     }; 
  59.   }; 
  60.  
  61.   settings.dart_library_sources_kernel = 
  62.       make_mapping_callback(kPlatformStrongDill, kPlatformStrongDillSize); 
  63. #endif   
  64.  //创建 Flutter 全局变量 
  65.   g_flutter_main.reset(new FlutterMain(std::move(settings))); 
  66.   g_flutter_main->SetupObservatoryUriCallback(env); 

从 args 解析出 Settings 的过程在 flutter_engine/shell/common/switches.cc,这里最重要的是 snapshot 路径的构建,构建完成的路径就是进程初始化拷贝到本地的路径, 最后生成了一个 FlutterMain 对象保存在全局静态变量中。

  1. if (aot_shared_library_name.size() > 0) { 
  2.     for (std::string_view name : aot_shared_library_name) { 
  3.       settings.application_library_path.emplace_back(name); 
  4.     } 
  5.   } else if (snapshot_asset_path.size() > 0) { 
  6.     settings.vm_snapshot_data_path = 
  7.         fml::paths::JoinPaths({snapshot_asset_path, vm_snapshot_data_filename}); 
  8.     settings.vm_snapshot_instr_path = fml::paths::JoinPaths( 
  9.         {snapshot_asset_path, vm_snapshot_instr_filename}); 
  10.     settings.isolate_snapshot_data_path = fml::paths::JoinPaths( 
  11.         {snapshot_asset_path, isolate_snapshot_data_filename}); 
  12.     settings.isolate_snapshot_instr_path = fml::paths::JoinPaths( 
  13.         {snapshot_asset_path, isolate_snapshot_instr_filename}); 
  14.   } 

后记

以上主要是 Flutter 2 FlutterEngineGroup 初始化的过程,下一节我们开始学习通过 FlutterEngineGroup创建 FlutterEngine 并绑定的 UI 页面的流程。

 

来源:微医大前端技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯