一、什么是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编译上面