Flutter Engine 编译与调试(2023) 您所在的位置:网站首页 Flutter20与30区别 Flutter Engine 编译与调试(2023)

Flutter Engine 编译与调试(2023)

#Flutter Engine 编译与调试(2023)| 来源: 网络整理| 查看: 265

概念 一、Flutter架构层

Engine 是Flutter 的核心,它主要使用 C++ 编写,并提供了 Flutter 应用所需的原语。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了 Flutter 核心 API 的底层实现,包括图形(通过 Skia)、文本布局、文件及网络 IO、辅助功能支持、插件架构和 Dart 运行环境及编译环境的工具链。

引擎将底层 C++ 代码包装成 Dart 代码,通过 dart:ui 暴露给 Flutter 框架层。该库暴露了最底层的原语,包括用于驱动输入、图形、和文本渲染的子系统的类。

通常,开发者可以通过 Flutter 框架层 与 Flutter 交互,该框架提供了以 Dart 语言编写的现代响应式框架。它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:

基础的 foundational 类及一些基层之上的构建块服务,如 animation**、 painting 和 **gestures,它们可以提供上层常用的抽象。 渲染层 用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。 widget 层 是一种组合的抽象。每一个渲染层中的渲染对象,都在 widgets 层中有一个对应的类。此外,widgets 层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。 Material 和 Cupertino 库提供了全面的 widgets 层的原语组合,这套组合分别实现了 Material 和 iOS 设计规范。 二、应用剖析

下图为你展示了一个通过 flutter create 命令创建的应用的结构概览。该图展示了引擎在架构中的定位,突出展示了 API 的操作边界,并且标识出了每一个组成部分。

1、Dart App

就是我们编写Flutter 应用dart代码的地方,一般都是指/lib下的代码

2、Framework

在我们下载下来的Flutter SDK flutter/packages/flutter/lib/

3、Engine

我们平时所用到的engine是编译好的引擎产物,所在位置如下:

这里我们以Android平台arm64为例,打开jar我们看看都有什么东西在里边,我们看到lib下包含一个so文件,文件大小33M

再看下lib下,主要包含Flutter的 相关的字节码文件1.3M

这样看来,引擎是由.java和c++代码编译而来,或者是.class和c++代码编译后组合而来。我们看到其中有app、和embedding文件夹 ,对应上面应用剖析图中的Embedder部分

我们简单看下源码长什么样子,右边的部分都属于flutter的源码

4、Embedder

上面我们了解到引擎产物中的embedding对应Embedder,源码对应engine源码库的这里

dev/engine/src/flutter/shell/platform

5、Runner

这个没什么好说的,对应各个平台的宿主APP。

那么我们如何自己修改和定制我们自己的引擎满足特殊需求,修改引擎源码,然后编译出引擎产物?当然可以,下面我们来一步一步开启引擎定制化操作。

准备 畅通的网络(‼️) github、配好ssh depot_tools git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 配置环境变量:export PATH=$PATH:/path/to/depot_tools 源码Clone

1、fork Flutter Engine到自己的Github仓库

2、创建引擎存放目录、添加.gclient文件

mkdir engine cd engine touch .gclient

.gclient内容如下(替换为自己的Github Flutter Engine仓库)

solutions = [ { "managed": False, "name": "src/flutter", "url": "[email protected]:/engine.git", "custom_deps": {}, "deps_file": "DEPS", "safesync_url": "", }, ]

3、同步代码

gclient sync

同步代码过程较为漫长(总共22G左右),当进度为100%时,依然会下载4~5个G的内容,请不要中断,可以在活动监视器中观察网络使用情况。

与官方仓库关联

1、查看当前远程仓库

cd src/flutter git remote -v origin [email protected]:/engine.git (fetch) origin [email protected]:/engine.git (push)

2、添加指向官方仓库的upstream

git remote add upstream [email protected]:flutter/engine.git

3、查看origin和upstream

git remote -v origin [email protected]:/engine.git (fetch) origin [email protected]:/engine.git (push) upstream [email protected]:flutter/engine.git (fetch) upstream [email protected]:flutter/engine.git (push)

4、从原仓库拉取代码并直接合并代码

git pull upstream 匹配版本

1、在实际开发中,一般不直接使用master的代码直接编译,都是需要获取指定版本的engine代码。可以通过本地安装的Flutter SDK版本来获取所对应的engine版本。

flutter channel stable//切换通道到稳定版 flutter upgrade //升级flutter sdk 到最新 cat dev/flutter/bin/internal/engine.version //获取当前版本commit id 1837b5be5f0f1376a1ccf383950e83a80177fb4e

2、切换分支同步代码

cd engine/src/flutter git reset --hard 1837b5be5f0f1376a1ccf383950e83a80177fb4e gclient sync -D --with_branch_heads --with_tags -v

本次 sync 时间较长(本人梯子6-8M/s要等待大概10分钟左右),依然要同步4~5个G的内容,请耐心等待。我的经验是关注活动监视器网络情况,如果收到数据速度小于梯子正常速度,中断后再次执行同步命令即可。

编译 一、创建目标工程 Android ./flutter/tools/gn --android --unoptimized ./flutter/tools/gn --android --unoptimized --android-cpu=arm64 ./flutter/tools/gn --android --runtime-mode=release ./flutter/tools/gn --android --android-cpu=arm64 --runtime-mode=release MacOS ./flutter/tools/gn --unoptimized ./flutter/tools/gn --runtime-mode=release 查阅其他平台 二、编译引擎 Android ninja -C out/android_debug_unopt ninja -C out/android_debug_unopt_arm64 ninja -C out/android_release ninja -C out/android_release_arm64 MacOS ninja -C out/host_debug_unopt ninja -C out/host_release

各平台首次编译时间较长,大概30-60分钟,以后改动代码后再次编译为增量更新,大大缩短编译时间。

应用产物

上面编译好 Flutter Engine 之后,就可以通过使用 Flutter tools 在编译我们App项目的时候指定为我们编译出来的 Flutter Engine 了。

一、调试阶段

先创建一个 Flutter App 工程:

flutter create --org com.fluency.engine engineplay

使用以下参数启动刚刚创建的应用

cd engineplay flutter run --local-engine-src-path /Users/fluency/dev/engine/src --local-engine=android_debug_unopt_arm64 flutter run --local-engine-src-path /Users/fluency/dev/engine/src --local-engine=host_debug_unopt

或将启动参数配置在IDE中

二、实际使用

替换本地Flutetr sdk 中的engine产物,引擎在sdk的位置:

/bin/cache/artifacts/engine

Android直接替换到flutter sdk中的engine产物不会生效.因为flutter 1.12.x之后打包流程会直接走远程下载flutter engine产物。我们需要修改flutter.gradle脚本,直接从本地读取engine编译打包。

Android 修改gradle.properties

local-engine-repo=engine/src/out/android_release # 这个自己通过gclient sync下载flutter engine的路径 local-engine-out=engine/src/out/android_release # arm64平台对应使用android_release_arm64 local-engine-build-mode=release 验证引擎

如何验证编译App的时候,确实是用了我们自编译的 Flutter Engine 呢?我们可以修改 Flutter Eingine ,加一些日志输出看看。

使用 Xcode 打开 engine/src/out/android_debug_unopt_arm64/flutter_engine.xcodeproj 工程文件。

然后,打开 代码文件 engine/src/flutter/shell/common/engine.cc 源代码文件。

再次编译引擎

ninja -C out/android_debug_unopt_arm64

再次使用自编译引擎启动app,观察控制台输出

发现启动app后,在引擎启动时确实打印了刚刚自己加入的log,说明当前应用确实使用了自己改动后编译的引擎,验证成功。

调试引擎

由于Host是Mac,所以这里以Mac为例调试引擎源码

找到flutter app 工程下macos/flutter/ephemeral/Flutter-Generated.xcconfig文件(IOS是Generated.xcconfig文件)

添加如下配置项:

FLUTTER_ENGINE=/Users/fluency/dev/engine/src LOCAL_ENGINE=host_debug_unopt ARCHS=x86_64 //(ios不加)

用Xcode打开Runner.xcworkspace

将对应平台的产物下flutter_engine.xcodeproj拖入Xcode中

拖入Xcode后:

此时我们可以随意在Flutter engine 引擎中打断点调试了,打断点后使用Xcode启动应用,代码会在断点位置停止

IOS的调试过程与Mac相同,如果断点没有断住,请检查Flutter-Generated.xcconfig和Generated.xcconfig的配置本地引擎地址和平台是否正确。好了,以上就是引擎调试的过程。

实际应用 import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.all(8.0), child: RichText( text: const TextSpan( text: "Engine 是Flutter 的核心,它主要使用 C++ 编写,并提供了 Flutter 应用所需的原语。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了 Flutter 核心 API 的底层实现,包括图形(通过 Skia)、文本布局、文件及网络 IO、辅助功能支持、插件架构和 Dart 运行环境及编译环境的工具链。", style: TextStyle(color: Colors.black)), textAlign: TextAlign.justify, ), ), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }

以上是Flutter demo中加入一段RichText Widget,当内容足够多时,文本块内容并不能完美对齐,我们看到textAlign已经设置为TextAlign.justify自适应对其,但效果并不令人满意。

image.png 按图索骥我们要修改TextAlign.justify的代码逻辑使其满足我们的需求,保证在任何情况下文字排版两边对其。最终我们找到引擎源码下该文件

/Users/fluency/dev/engine/src/third_party/skia/modules/skparagraph/src/TextLine.cpp

将justify方法的逻辑改为如下所示

void TextLine::justify(SkScalar maxWidth) { // Count words and the extra spaces to spread across the line // TODO: do it at the line breaking?.. constexpr auto kWhiteSpaceNumOfStart = 2; size_t allCharNums = 0; SkScalar textLen = 0; size_t posOfCharFirst = -1; size_t firstResult = 0; this->iterateThroughClustersInGlyphsOrder( false, false, [&](const Cluster* cluster, bool ghost) { textLen += cluster->width(); posOfCharFirst++; if (posOfCharFirst == firstResult && firstResult < kWhiteSpaceNumOfStart && cluster->isWhitespaceBreak()) { firstResult++; return true; } if (posOfCharFirst == 0 || (posOfCharFirst == firstResult)) { return true; } ++allCharNums; return true; }); if (allCharNums == 0) { return; } SkScalar step = (maxWidth - textLen) / allCharNums; SkScalar shift = 0; // Deal with the ghost spaces auto ghostShift = maxWidth - this->fAdvance.fX; // Spread the extra whitespaces size_t posOfCharSecond = -1; size_t result = 0; this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, bool ghost) { posOfCharSecond++; if (ghost) { if (cluster->run().leftToRight()) { shiftCluster(cluster, ghostShift, ghostShift); } return true; } auto prevShift = shift; if (posOfCharSecond == result && result < kWhiteSpaceNumOfStart && cluster->isWhitespaceBreak()) { result++; return true; } if (posOfCharSecond == 0 || posOfCharSecond == result) { return true; } shift += step; shiftCluster(cluster, shift, prevShift); return true; }); SkAssertResult(nearlyEqual(shift, maxWidth - textLen)); this->fWidthWithSpaces += ghostShift; this->fAdvance.fX = maxWidth; }

大概逻辑是计算每一行文字总宽度和空格数量,用排版最大宽度减去每行文字总宽度除以空格数量,得到每一个空格偏移量,重新遍历整行文字,在每个空格地方加上之前算的偏移量,最终等分每一个文字之间的间距。

执行ninja -C out/host_debug_unopt重新编译引擎源码,再看效果:

image.png 可以看到排版已经可以完美达到两边对齐了。至此通过修改引擎代码完成了“定制化”的需求。

总结

本人以Android开发者的角度来看,Flutter Engine更像Android生态中安装在手机中的操作系统的一部分,就像Flutter官方架构图一样,比如负责渲染、栅格化、提供Dart VM等等,只不过Android这一部分早已被提前安装进了手机硬件,而Flutter需要自己接管这一部分就需要将引擎打入应用包中,额外使用Embedder调用宿主系统API。

在绝大多数Flutter开发过程中不需要定制化引擎,但Flutter Engine也是代码编译的,有代码的地方就一定有bug,定制引擎给我们提供了一条解决“特殊”需求,或修复官方认为不是bug的bug一条出路。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有