Flutter多引擎无法Attach问题分析及热重载卡死问题处理

6,926 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

公司的iOS项目是原生嵌Flutter类型,使用Flutter多引擎(Engine group)方案。与以前项目使用的Flutter boost方案不同, 发现多引擎方案的attach功能非常不稳定,经常出现无法attach以及Hot reload, Hot restart卡死的情况,非常影响开发效率。本文会简单分析问题原因及提供一些解决方案。

Flutter attach的原理

Flutter通过将更新的源代码文件注入到正在运行的Dart 虚拟机(VM)来实现热重载。在虚拟机使用新的字段和函数更新类之后, Flutter 框架会自动重新构建 widget 树,以便快速查看更改的效果。

attach的过程就是一个连接VM的过程,应用以Debug模式运行后,会启动一个VM服务,并且使用mDNS协议( mDNS/DNS-SD 是用于本地局域网服务发现的协议)广播。执行attach操作时,会通过mDNS协议去查找当前应用所匹配的VM服务,再通过WS协议进行连接,这个时候就可能出现如下几种情况,导致连接失败:

  1. 找到的VM服务太多,需要选择连接哪一个。
  2. 调度问题,没有去连正确的 VM 服务。
  3. mDNS缓存没有刷新
  4. mDNS查找问题

1. 找到的VM服务太多,需要选择连接哪一个

当出现这类问题时,一般会出现这类log:

There are multiple observatory ports available.
Rerun this command with one of the following passed in as the appId:

  flutter attach --app-id com.xxx.test
  flutter attach --app-id com.xxx.test (2)

多引擎方案相对于单引擎方案及纯Flutter项目,很容易出现这类问题,这种情况如提示所示,在项目的运行配置中指定app-id,再运行尝试,具体操作如图所示:

多引擎方案中,这个操作大概率不能解决attach问题,我们也不能明确知道自己运行的应用对应的是哪个app-id, 后面那个com.xxx.test (2)相对于第一个附加了(2),实际上都是对应同一个bundle identifier的应用,只是由于先来后到的原因,后到的被附加了(2),如果有更多,可能还会看到附加了(3)的等。所有我们需要如图所示在后面加上-v参数来获取详细日志,以便于分析问题产生的原因。

2. 调度问题,没有去连正确的VM服务

当我们指定 app-id 后, 还是会经常出现attach失败,这是什么原因呢?我们可以通过命令: ns-sd -Z _dartobservatory._tcp 来获取当前VM服务列表,输出如下:

➜ ✗ dns-sd -Z _dartobservatory._tcp
Browsing for _dartobservatory._tcp
DATE: ---Sat 28 May 2022---
20:19:44.892  ...STARTING...

; To direct clients to browse a different domain, substitute that domain in place of '@'
lb._dns-sd._udp                                 PTR     @

; In the list of services below, the SRV records will typically reference dot-local Multicast DNS names.
; When transferring this zone file data to your unicast DNS server, you'll need to replace those dot-local
; names with the correct fully-qualified (unicast) domain name of the target host offering the service.

_dartobservatory._tcp                           PTR     com\.xxx\.test._dartobservatory._tcp
com\.xxx\.test._dartobservatory._tcp    SRV     0 0 57624 yanfang.local. ; Replace with unicast FQDN of target host
com\.xxx\.test._dartobservatory._tcp    TXT     "authCode=8DjE8_OckNk="

_dartobservatory._tcp                           PTR     com\.xxx\.test\032(2)._dartobservatory._tcp
com\.xxx\.test\032(2)._dartobservatory._tcp SRV     0 0 64646 yanfang.local. ; Replace with unicast FQDN of target host
com\.xxx\.test\032(2)._dartobservatory._tcp TXT     "authCode=J0BgTyk73m8="

_dartobservatory._tcp                           PTR     com\.xxx\.test\032(3)._dartobservatory._tcp
com\.xxx\.test\032(3)._dartobservatory._tcp SRV     0 0 61345 star.local. ; Replace with unicast FQDN of target host
com\.xxx\.test\032(3)._dartobservatory._tcp TXT     "authCode=E0fcBt3fdk2="

在输出中可以看到VM服务的app-id, 端口设备名。 如第一条,app-idcom.xxx.test, 端口是:57624, 设备名是:yanfang.local

输出中有3个服务,分别是:com\.xxx\.test._dartobservatory._tcpcom\.xxx\.test\032(2)._dartobservatory._tcp,其实这两个都是bundle identifiercom.xxx.test 应用所对应的VM服务,由于先来后到的原因,后到的就被加了一个(2)以做区分,在上述log内容中,我们甚至可能会看到同事的设备名称,这是由于mDNS协议是查找整个局域网内的VM服务,当你和同事在调试同一个项目时,mDNS会把你和同事同样app-idVM服务一起找出来,那么哪个是你当前调试应用对应的VM服务呢? 虽然你指定了app-id,但是你先运行app-idcom.xxx.test,后运行你就变成了com.xxx.test (2)。这种情况下有两种方案来解决这个问题:

  1. attach时拔掉网线,断开网络连接,让mDNS找不到局域网内其它设备的VM服务
  2. 每次运行前执行 ns-sd -Z _dartobservatory._tcp 查看当前的VM服务,根据上面的设备名和端口选择正确的app-id进行配置。

3. 存在mDNS缓存,没有刷新

有时候,我们调用ns-sd -Z _dartobservatory._tcp命令后会发现,同一台设备中,存在多个同名的VM服务,但是明明一台设备不可能同时运行两个相同bundle identifier的应用。我怀疑可能是Flutter多引擎下存在bug,导致缓存没有刷新引起的。具体没有深究,欢迎评论区指点纠正。

在这类情况下就只能依赖于-v参数,查看attach时的log, 看是去尝试连接的哪一个VM服务。 log样例日下:

[+1902 ms] Checking for available port on com.xxx.test._dartobservatory._tcp.local
[   +3 ms] Checking for authentication code for com.xxx.test._dartobservatory._tcp.local
[  +67 ms] Connecting to service protocol: http://127.0.0.1:56954/SNvhcI0TrgU=/
[  +17 ms] Exception attempting to connect to the VM Service: SocketException: OS Error: Connection refused, errno = 61, address = 127.0.0.1, port = 60679
[        ] This was attempt #1. Will retry in 0:00:00.100000.

在上述log中可以看到尝试连接的VM服务端口和app-id,一般配合ns-sd -Z _dartobservatory._tcp命令来判断正确的app-id,然后指定app-id再运行即可。

4. mDNS查找问题

Flutter仓库的Issue46705有对这类问题进行说明,处理方式有:

  • 关闭个人热点
  • Mac -> 设置 -> 网络 -> iPhone USB -> 不勾选“除非需要,否则请停用”
  • 重试

在实践中,通过上述4个流程,可以保证一定attach成功。

Hot reload问题

Flutter多引擎方案相对其它方案来讲,千辛万苦attach成功后,还会有更高概率出现Hot reload卡死的情况,实际使用起来非常不稳定,我们可以通过如下事项缓解:

  • 关闭自动Hot reload和保存代码触发Hot reload功能。
  • 不在Hot reloadHot restart时切换页面。
  • 避免同时执行多次Hot reloadHot reload
  • Hot reload无效时,尝试执行Hot reload恢复状态。
  • 代码中有死循环,也会导致Hot reloadHot reload 卡死

做了如上操作后,Hot reload卡死的情况会缓解,但是依然会有一定概率出现,如果您有更好的方案,欢迎评论区告知。

参考资料

by 星的天空