Flutter引擎——下载、编译和调试

2022-03-28 09:07:19 浏览数 (1)

一、什么是Flutter引擎?

我们这里说的引擎,不单单指的是渲染引擎,它包含但不仅限于渲染引擎

我们打开一个Flutter工程,找到iOS文件夹,点开Runner.xcworkspace工程,找到products文件夹下面的Runner.app:

然后显示包内容,此时如果文件夹是空的,那么你就编译一下项目,之后该文件夹就有东西了,如下:

点进Frameworks文件夹:

其中,App.framework就是Dart代码;Flutter.framework就是Flutter引擎代码,我们今天这篇文章聊的就是Flutter的引擎,也就是说,我们今天要研究的就是Flutter.framework里面的内容。

看到这里你可能会有这么个疑问,研究这玩意儿干啥?就单纯为了装逼吗?实际上还真不是为了装逼。我们知道,苹果是不允许热更新的,很多流行的热更新框架都被苹果给封杀了,但这是不是意味着我们就百分百不能在iOS上面去做热更新呢?其实,我们是可以做到热更新的,但是这个前提是,你是用的这个热更新技术不要大面积使用,不要被苹果注意到、盯上,这样的话苹果的人工审核是审核不出来的。因此,如果你公司项目有该需求,并且你公司有足够的实力,那么就可以在自己公司内部组建一个专门的团队来研究热更新技术,而在Flutter中去研究热更新,就需要以Flutter引擎为基础做一些二次开发,因此研究Flutter引擎是非常有必要的

我们知道,FlutterSDK是有很多版本的,如下我现在使用的是2.8.1:

如果我们使用的FlutterSDK版本发生了变化,那么对应的Flutter引擎(即Flutter.framework)也会发生变化

我们使用的FlutterSDK是存放在本地的,当一个Flutter项目编译运行的时候,它是通过路径找到本地的FlutterSDK,然后将FlutterSDK中对应架构下面的引擎拷贝打包到该项目的可执行文件中的。

上图中,第二行2.8.1是FlutterSDK的版本,可以看到我这里使用的channel是stable,其实这里的channel就是git 分支,我们看一下有几个channel:

可以看到,flutter sdk有4个channel,我推荐使用stable,因为它是最稳定的版本。

如果我们要切换channel,那么就直接在flutter SDK路径下执行flutter channel master/stable即可:

第4行890a5fca2e是Flutter Engine的版本,其实它是一个commit ID 。

接下来我们来到Github的FlutterEngine仓库下:

可以看到,flutter的engine是由好多个分支以及版本的,默认情况下,从Github上面pull下来的都是主分支(main)的代码。上面也提到,890a5fca2e是Flutter Engine的版本,其实它是一个commit ID ,因此,我们是可以精确到某一个commit节点的,所以我们建议通过某一个commitID来精确下载某一个commit节点下的engine。如下:

二、Flutter引擎源代码的下载

参考文章:https://www.yuque.com/qingjiaowohank/etm87a/rz2kpa

1,准备部署工具depot_tools

在对应目录(我是在Flutter目录)下执行如下命令,克隆depot_tools项目:

代码语言:javascript复制
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

2,gitHub配置SSH

当git clone的时候,可能会报SSH失效的错误:

代码语言:javascript复制
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

这个是因为你GitHub的KEY过期了,长期不clone代码导致的。此时直接去百度一下该错误,按照搜索出来的方案一步步配置一下即可

3,配置工具的环境变量

代码语言:javascript复制
vim ~/.bash_profile

4,安装最后一个工具ant

代码语言:javascript复制
brew install ant

5,下载引擎

(1)

在对应位置下(我是在Flutter目录下)新建目录(注意,我们的路径不能有中文,否则后续Down下载的源码会有问题

代码语言:javascript复制
mkdir engine

(2)创建gclien配置文件。(我们需要通过gclien下载源码)

代码语言:javascript复制
touch .gclient

(3)配置.gclient文件。(尤其要确定CommitID 保持一致)

代码语言:javascript复制
vim .gclient

将如下复制粘贴进来:

代码语言:javascript复制
solutions = [
    {
        "managed": False,
        "name": "src/flutter",
        "url": "git@github.com:flutter/engine.git@6bc433c6b6b5b98dcf4cc11aff31cdee90849f32",
        "custom_deps": {},
        "deps_file": "DEPS",
        "safesync_url": "",
    },
]

这里的6bc433c6b6b5b98dcf4cc11aff31cdee90849f32是engine的commitID,需要找到你目前在使用的Flutter版本里面的engine的commitID,找到之后就在上面替换掉。那么如何找到这个engine的commitID呢?

这个engine的commitID存放在engine.version文件中,其路径如下:

然后我们cd到internal文件夹路径,通过cat命令查看engine.version文件

这里展示出来的890a5fca2e34db413be624fc83aeea8e61d42ce6就是当前FlutterSDK中的FlutterEngine版本的CommitID。

(4)执行gclient sync (这个操作将会fetch Flutter 所有的依赖。这里有10G文件,需要点时间,请保持网络!该操作需要访问国外网站)

代码语言:javascript复制
gclient sync

需要注意的是,如果报SSH KEY的错误,那么就配置一下GITHUB的SSH KEY即可。

(5)关于升级

当我们升级了Flutter的SDK,我们想要升级引擎代码,直接更新 .gclient 文件。

首先找到对应的engine的commitID:

然后将这个修改到.gclient文件中

然后进入src/flutter目录(/Users/liwei/Flutter/engine/src/flutter),进行一次 git pull,然后git reset --hard commitID

代码语言:javascript复制
git pull
git reset --hard commitID

这个命令就是告诉Git ,我下次下载就下载这个conmitID的

回到engine 目录(/Users/liwei/Flutter/engine),也就是.gclient文件所在的目录,执行如下命令:

代码语言:javascript复制
gclient sync --with_branch_heads --with_tags --verbose

三、Flutter引擎源代码的编译

Flutter 引擎的源代码是需要通过Ninja来编译的,而GN是一个生成Ninja编译所需的构建文件的元构建系统

接下来来到GN所在的tools路径,在该路径下进行如下构建代码的执行:

代码语言:javascript复制
#构建iOS设备使用的引擎
#真机debug版本
./gn --ios --unoptimized
#真机release版本(日常开发使用,如果我们要自定义引擎)
./gn --ios --unoptimized --runtime-mode=release
#模拟器版本
./gn --ios --simulator --unoptimized
#主机端(Mac)构建
./gn --unoptimized

构建完成会有四个Xcode工程

最后一步,就是使用ninja编译工程,这个操作是最耗时并且烧电脑的了。

首先配置一下环境变量:

代码语言:javascript复制
vim ~/.bash_profile

保存退出之后:

代码语言:javascript复制
source ~/.bash_profile

然后我们来到上面这个存储了4个Xcode工程的路径(/Users/liwei/Flutter/engine/src/out),执行如下命令:

代码语言:javascript复制
ninja -C host_debug_unopt && ninja -C ios_debug_sim_unopt && ninja -C ios_debug_unopt && ninja -C ios_release_unopt

接下来就是漫长的等待了。

需要注意的是,由于一些配置的问题,不同的设备在编译的时候可能会遇到不同的问题,当遇到问题的时候就去解决问题好了。

四、将本地Flutter 引擎绑定到自己的Flutter项目中

上面介绍了Flutter引擎,以及引擎的下载和编译,接下来我们就在实际项目中去玩一下自己编译好的引擎。

首先创建一个Flutter工程,然后flutter run,之后打开flutter工程的ios目录下的Xcode工程。

首先看到有3个配置文件:

Debug是开发环境的配置文件;Release是发布环境的配置文件;Generated是通用配置文件,在这里面配置的东西会在Debug和Release里面同时生效

接下来我们再来看个东西:

这里的RunScript是在编译当前工程的时候会去执行的脚本,这里的脚本是FlutterSDK中的xcode_backend.sh脚本,其路径如下:

我们可以打开看一下:

可以看到,脚本里面会使用到很多的环境变量(比如FLUTTER_ROOT),这些环境变量是在哪里定义的呢?就是在Generated配置文件中定义的,如下:

所以,脚本中使用到的很多环境变量其实是在配置文件中定义的,当该脚本执行的时候,会从配置文件中去读取对应的环境变量。

接下来我在通用配置文件中增加两项配置,如下:

可以看到,我配置的LOCAL_ENGINE环境变量是ios_release_unopt,但是我是在Debug模式下编译的代码,此时会编译不通过。

好,下面我改一下LOCAL_ENGINE环境变量的配置,改成ios_debug_unopt,如下:

然后再在Debug模式下编译就编译成功了。

这也充分说明了,应用程序在编译的时候访问了如下路径下的Flutter本地引擎的代码:

然后后面我们在该路径下调整引擎代码,然后编译刚创建的Flutter工程就能看到对应的效果了。

接下来我们将工程跑起来,然后使用lldb指令添加一个touchesBegan事件的断点,如下:

然后点按手机屏幕,就能监听到如下事件:

可以看到,断点最后走到了FlutterViewController.mm文件的- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event方法中,那么这个FlutterViewController.mm文件时存放在哪里呢?

我们接下来来到本地Flutter引擎的代码:

双击打开flutter_engine.xcodeproj工程,Command Shift o搜索FlutterViewController.mm,然后找到第1000行代码,添加个注释,如下:

此时我们再打开Flutter工程的ios工程中断点到的地方:

可以看到,刚才在Flutter本地引擎工程中所做的调整,体现到了我在Flutter工程的iOS工程断点到的地方,这说明,该Flutter工程所用的引擎就是这个Flutter本地引擎!

只要将你自己创建的Flutter原工程配置上Flutter引擎,你就可以使用你自己的本地Flutter引擎代码,并且可以进行断点调试。

接下来我就稍微修改下本地引擎的代码,看看能不能其作用,如下,我在本地引擎代码中增加了一个NSLog:

需要注意哦,此时还不可以生效奥~

接下来使用ninja来编译本地Flutter引擎代码

然后再使用Xcode重新编译Flutter工程的ios工程,这样的话每一次点击屏幕都会打印了:

此时,我们修改的本地引擎中的代码才真正生效!!

五、总结

本文主要是介绍了如何在一个Flutter工程中去使用自己本地的Flutter引擎。这里的Flutter引擎是以一个以编译之后的二进制文件的形式存在于Flutter工程中的,那么如何去获取到一个编译之后的二进制形式的Flutter引擎呢?

第一步,通过配置.gclient文件下载Flutter引擎源代码。由于Flutter引擎源代码是在国外服务器上的,所以这里的下载操作需要访问国外网站,并且需要一定的时间。

第二步,通过GN来构建编译Flutter引擎所需的文件。构建的结果就是,在out文件夹下面生成了四个Xcode工程文件夹。

第三步,通过ninja编译上面

0 人点赞