Flutter - 我给官方提PR,解决run命令卡住问题 😃
# 一、背景
Flutter
项目在 iPhone
真机上运行会白屏,VSCode
右下角一直卡在 Installing and launching...
,AndroidStudio
亦是如此。
其它人的电脑又是正常的,遂着手进行问题的定位,并在解决问题后向 Flutter
官方提交了我的第一份 PR
。
PR
链接:https://github.com/flutter/flutter/pull/119443 (opens new window)
# 二、问题
使用命令 flutter run -d xxxx -v
运行项目,日志如下:
[ ] -------------------------
[ +187 ms] [LinXunFeng]$command source -s 0
'/tmp/3EB9B732-D866-46D2-88CB-513C2CF7455E/fruitstrap-lldb-prep-cmds-00008120_0019784E2690C01E'
[ ] Executing commands in
'/tmp/3EB9B732-D866-46D2-88CB-513C2CF7455E/fruitstrap-lldb-prep-cmds-00008120_0019784E2690C01E'.
[ ] [LinXunFeng]$ platform select remote-'ios' --sysroot '/Users/lxf/Library/Developer/Xcode/iOS
DeviceSupport/16.1.1 (20B101) arm64e/Symbols'
[ ] Platform: remote-ios
[ ] Connected: no
[ ] SDK Path: "/Users/lxf/Library/Developer/Xcode/iOS DeviceSupport/16.1.1 (20B101) arm64e/Symbols"
[ ] [LinXunFeng]$ target create
"/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example/build/ios/iphoneos/Runner.app"
[+3544 ms] Current executable set to
'/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example/build/ios/iphoneos/Runner.app' (arm64).
[ ] [LinXunFeng]$ script
fruitstrap_device_app="/private/var/containers/Bundle/Application/1915BC3A-57D0-41A5-8121-1769CF3F6777/Runner.app"
[ +124 ms] [LinXunFeng]$ script fruitstrap_connect_url="connect://127.0.0.1:65222"
[ ] [LinXunFeng]$ script fruitstrap_output_path=""
[ ] [LinXunFeng]$ script fruitstrap_error_path=""
[ ] [LinXunFeng]$ target modules search-paths add /usr "/Users/lxf/Library/Developer/Xcode/iOS
DeviceSupport/16.1.1 (20B101) arm64e/Symbols/usr" /System "/Users/lxf/Library/Developer/Xcode/iOS
DeviceSupport/16.1.1 (20B101) arm64e/Symbols/System"
"/private/var/containers/Bundle/Application/1915BC3A-57D0-41A5-8121-1769CF3F6777"
"/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example/build/ios/iphoneos"
"/var/containers/Bundle/Application/1915BC3A-57D0-41A5-8121-1769CF3F6777"
"/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example/build/ios/iphoneos" /Developer
"/Users/lxf/Library/Developer/Xcode/iOS DeviceSupport/16.1.1 (20B101) arm64e/Symbols/Developer"
[ +15 ms] [LinXunFeng]$ command script import
"/tmp/3EB9B732-D866-46D2-88CB-513C2CF7455E/fruitstrap_00008120_0019784E2690C01E.py"
[ +3 ms] [LinXunFeng]$ command script add -f fruitstrap_00008120_0019784E2690C01E.connect_command connect
[ ] [LinXunFeng]$ command script add -s asynchronous -f fruitstrap_00008120_0019784E2690C01E.run_command
run
[ ] [LinXunFeng]$ command script add -s asynchronous -f
fruitstrap_00008120_0019784E2690C01E.autoexit_command autoexit
[ ] [LinXunFeng]$ command script add -s asynchronous -f
fruitstrap_00008120_0019784E2690C01E.safequit_command safequit
[ ] [LinXunFeng]$ connect
[ +29 ms] [LinXunFeng]$ run
[ +109 ms] success
[+4740 ms] [LinXunFeng]$2023-02-02 08:57:41.425078+0800 Runner[5278:1703137] flutter: The Dart VM service is
listening on http://127.0.0.1:61063/
而正常运行的日志是这样的:
[ ] -------------------------
[ +359 ms] (lldb) command source -s 0
'/tmp/70CB9E5A-3BA1-4299-8F38-BE5135178035/fruitstrap-lldb-prep-cmds-00008120_0019784E2690C01E'
[ ] Executing commands in
'/tmp/70CB9E5A-3BA1-4299-8F38-BE5135178035/fruitstrap-lldb-prep-cmds-00008120_0019784E2690C01E'.
[ ] (lldb) platform select remote-'ios' --sysroot '/Users/lxf/Library/Developer/Xcode/iOS
DeviceSupport/16.1.1 (20B101) arm64e/Symbols'
[ ] Platform: remote-ios
[ ] Connected: no
[ ] SDK Path: "/Users/lxf/Library/Developer/Xcode/iOS DeviceSupport/16.1.1 (20B101) arm64e/Symbols"
[ ] (lldb) target create
"/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example/build/ios/iphoneos/Runner.app"
[+3984 ms] Current executable set to
'/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example/build/ios/iphoneos/Runner.app' (arm64).
[ ] (lldb) script
fruitstrap_device_app="/private/var/containers/Bundle/Application/ABD16CE1-675A-44C3-BE9C-18EC156B6499/Runner.app"
[ +136 ms] (lldb) script fruitstrap_connect_url="connect://127.0.0.1:65102"
[ ] (lldb) script fruitstrap_output_path=""
[ ] (lldb) script fruitstrap_error_path=""
[ ] (lldb) target modules search-paths add /usr "/Users/lxf/Library/Developer/Xcode/iOS
DeviceSupport/16.1.1 (20B101) arm64e/Symbols/usr" /System "/Users/lxf/Library/Developer/Xcode/iOS
DeviceSupport/16.1.1 (20B101) arm64e/Symbols/System"
"/private/var/containers/Bundle/Application/ABD16CE1-675A-44C3-BE9C-18EC156B6499"
"/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example/build/ios/iphoneos"
"/var/containers/Bundle/Application/ABD16CE1-675A-44C3-BE9C-18EC156B6499"
"/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example/build/ios/iphoneos" /Developer
"/Users/lxf/Library/Developer/Xcode/iOS DeviceSupport/16.1.1 (20B101) arm64e/Symbols/Developer"
[ +17 ms] (lldb) command script import
"/tmp/70CB9E5A-3BA1-4299-8F38-BE5135178035/fruitstrap_00008120_0019784E2690C01E.py"
[ +3 ms] (lldb) command script add -f fruitstrap_00008120_0019784E2690C01E.connect_command connect
[ ] (lldb) command script add -s asynchronous -f fruitstrap_00008120_0019784E2690C01E.run_command run
[ ] (lldb) command script add -s asynchronous -f fruitstrap_00008120_0019784E2690C01E.autoexit_command
autoexit
[ ] (lldb) command script add -s asynchronous -f fruitstrap_00008120_0019784E2690C01E.safequit_command
safequit
[ ] (lldb) connect
[ +38 ms] (lldb) run
[ +118 ms] success
[ ] Application launched on the device. Waiting for observatory url.
[+4521 ms] (lldb) 2023-02-02 08:55:53.841520+0800 Runner[5272:1701483] Warning: Unable to create restoration in
progress marker file
[ +45 ms] Observatory URL on device: http://127.0.0.1:60477/
[ +3 ms] Attempting to forward device port 60477 to host port 65119
[ ] executing: /Users/lxf/fvm/versions/3.3.7/bin/cache/artifacts/usbmuxd/iproxy 65119:60477 --udid
00008120-0019784E2690C01E --debug
[ +433 ms] fopen failed for data file: errno = 2 (No such file or directory)
[ ] Errors found! Invalidating cache...
[ +13 ms] fopen failed for data file: errno = 2 (No such file or directory)
[ ] Errors found! Invalidating cache...
[ +560 ms] Forwarded port ForwardedPort HOST:65119 to DEVICE:60477
[ ] Forwarded host port 65119 to device port 60477 for Observatory
[ ] Installing and launching... (completed in 19.1s)
[ ] Caching compiled dill
[ +16 ms] Connecting to service protocol: http://127.0.0.1:65119/
[ +92 ms] Launching a Dart Developer Service (DDS) instance at http://127.0.0.1:0, connecting to VM service at
http://127.0.0.1:65119/.
[ +45 ms] DDS is listening at http://127.0.0.1:65124/MCCddjLRlQk=/.
[ +22 ms] Successfully connected to service protocol: http://127.0.0.1:65119/
[ +13 ms] DevFS: Creating new filesystem on the device (null)
[ +10 ms] DevFS: Created new filesystem on the device
(file:///private/var/mobile/Containers/Data/Application/9066D3F0-F8FB-489D-99BD-FCAF266B118F/tmp/examplePN73Pp/exa
mple/)
[ ] Updating assets
[ +34 ms] Syncing files to device lxf的iPhone...
[ ] Compiling dart to kernel with 0 updated files
[ ] Processing bundle.
[ ] <- recompile package:scrollview_observer_example/main.dart ef8dbbad-3cff-4e06-9043-63987bdc88a2
[ ] <- ef8dbbad-3cff-4e06-9043-63987bdc88a2
[ ] Bundle processing done.
[ +30 ms] Updating files.
[ ] DevFS: Sync finished
[ ] Syncing files to device lxf的iPhone... (completed in 33ms)
[ ] Synced 0.0MB.
[ ] <- accept
[ +7 ms] Connected to _flutterView/0x10881a420.
[ +1 ms] Flutter run key commands.
[ ] r Hot reload. 🔥🔥🔥
[ ] R Hot restart.
[ ] h List all available interactive commands.
[ ] d Detach (terminate "flutter run" but leave application running).
[ ] c Clear the screen
[ ] q Quit (terminate the application on the device).
[ ] 💪 Running with sound null safety 💪
[ ] An Observatory debugger and profiler on lxf的iPhone is available at:
http://127.0.0.1:65124/MCCddjLRlQk=/
[ +29 ms] The Flutter DevTools debugger and profiler on lxf的iPhone is available at:
http://127.0.0.1:9102?uri=http://127.0.0.1:65124/MCCddjLRlQk=/
在不断谷歌 Flutter Installing and launching...
并按解决步骤操作后但问题依旧时,我认真对比了上述的命令执行日志,发现有一处可疑的地方,那就是:
[ +29 ms] [LinXunFeng]$ run
[ +109 ms] success
[ +38 ms] (lldb) run
[ +118 ms] success
为什么我会觉得它可疑呢?因为我之前学 iOS
逆向的时候自定义过 lldb
提示符,然后差不多在那个时间段后 Flutter
项目就不再能正常运行,而且从日志中可以看出到这里就不再往下执行了~
个性化提示符:
# ~/.lldbinit 文件的内容
settings set prompt "\[LinXunFeng]$"
将其注释掉,竟完美解决了这个问题~
# 三、探索
改回原来的 lldb
提示符就好了,那说明 Flutter
内部八成是根据这个固定的输出来做判断是否进入下一步,打开 Flutter
源码进行全局搜索 (lldb) run
,果不其然,在 flutter_tools
下可以搜到关键代码
// (lldb) run
// https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
static final RegExp _lldbRun = RegExp(r'\(lldb\)\s*run');
具体文件定位:flutter_tools/ios_deploy.dart#L285-L287 (opens new window)
在文件下搜索 _lldbRun
定位到 launchAndAttach
方法,如下
/// Launch the app on the device, and attach the debugger.
///
/// Returns whether or not the debugger successfully attached.
Future<bool> launchAndAttach() async {
// Return when the debugger attaches, or the ios-deploy process exits.
final Completer<bool> debuggerCompleter = Completer<bool>();
try {
_iosDeployProcess = await _processUtils.start(
_launchCommand,
environment: _iosDeployEnv,
);
String? lastLineFromDebugger;
final StreamSubscription<String> stdoutSubscription = _iosDeployProcess!.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
_monitorIOSDeployFailure(line, _logger);
// (lldb) run
// success
// 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: The Dart VM service is listening on http://127.0.0.1:57782/
if (_lldbRun.hasMatch(line)) {
_logger.printTrace(line);
_debuggerState = _IOSDeployDebuggerState.launching;
return;
}
// Next line after "run" must be "success", or the attach failed.
// Example: "error: process launch failed"
if (_debuggerState == _IOSDeployDebuggerState.launching) {
_logger.printTrace(line);
final bool attachSuccess = line == 'success';
_debuggerState = attachSuccess ? _IOSDeployDebuggerState.attached : _IOSDeployDebuggerState.detached;
if (!debuggerCompleter.isCompleted) {
debuggerCompleter.complete(attachSuccess);
}
return;
}
...
});
...
}
return debuggerCompleter.future;
}
具体文件定位:flutter_tools/ios_deploy.dart#L319-L351 (opens new window)
见名识义,将项目运行起来后进行附加调试,这段代码的执行流程如下:
- 调用
_processUtils
的start
方法执行_launchCommand
,然后监听其输出内容。 - 在监听到
(lldb) run
后将_debuggerState
赋值为_IOSDeployDebuggerState.launching
,以做为判断紧跟其后的success
是目标输出内容的依据 - 接着进行了最关键的一步
debuggerCompleter.complete(attachSuccess)
,告诉外部该功能已成功执行
而我的电脑因为自定义 LLDB prompt
导致这个小流程中的 2
和 3
无法顺利执行~~
# 四、修复
知道导致问题的原因后,我那就来尝试修复一下,思路很简单,就是先获取当前的 lldb
提示符然后将 _lldbRun
中的 (lldb)
字符串给替换掉,那如何获取当前的 lldb 提示符?
还记得上面提到的常量 _lldbRun
吗?定义处有相应的链接
// (lldb) run
// https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
static final RegExp _lldbRun = RegExp(r'\(lldb\)\s*run');
打开链接后可以找到上面日志里出现过的 platform select remote-'ios' --sysroot
,定义如下:
/*
* Startup script passed to lldb.
* To see how xcode interacts with lldb, put this into .lldbinit:
* log enable -v -f /Users/vargaz/lldb.log lldb all
* log enable -v -f /Users/vargaz/gdb-remote.log gdb-remote all
*/
#define LLDB_PREP_CMDS CFSTR("\
platform select remote-ios --sysroot '{symbols_path}'\n\
...
connect\n\
")
代码摘自:ios-control/ios-deploy.m#L33 (opens new window)
那就好办了,这个 platform select remote-'ios' --sysroot
的输出比 run
要早,所以只需要监听其被输出后进行字符串截取,就可以取到当前的 lldb
提示符
[ ] [LinXunFeng]$ platform select remote-'ios' --sysroot '/Users/lxf/Library/Developer/Xcode/iOS
DeviceSupport/16.1.1 (20B101) arm64e/Symbols'
具体代码:
1、定义 _lldbPlatformSelect
常量
- // (lldb) run
- // https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
- static final RegExp _lldbRun = RegExp(r'\(lldb\)\s*run');
+ // (lldb) platform select remote-'ios' --sysroot
+ // https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L33
+ // This regex is to get the configurable lldb prompt. By default this prompt will be "lldb".
+ static final RegExp _lldbPlatformSelect = RegExp(r"\s*platform select remote-'ios' --sysroot");
2、调整 launchAndAttach
方法
Future<bool> launchAndAttach() async {
// Return when the debugger attaches, or the ios-deploy process exits.
+ // (lldb) run
+ // https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
+ RegExp lldbRun = RegExp(r'\(lldb\)\s*run');
final Completer<bool> debuggerCompleter = Completer<bool>();
try {
_iosDeployProcess = await _processUtils.start(
_launchCommand,
environment: _iosDeployEnv,
);
String? lastLineFromDebugger;
final StreamSubscription<String> stdoutSubscription = _iosDeployProcess!.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
_monitorIOSDeployFailure(line, _logger);
+
+ // (lldb) platform select remote-'ios' --sysroot
+ // Use the configurable custom lldb prompt in the regex. The developer can set this prompt to anything.
+ // For example `settings set prompt "(mylldb)"` in ~/.lldbinit results in:
+ // "(mylldb) platform select remote-'ios' --sysroot"
+ if (_lldbPlatformSelect.hasMatch(line)) {
+ final String platformSelect = _lldbPlatformSelect.stringMatch(line) ?? '';
+ if (platformSelect.isEmpty) {
+ return;
+ }
+ final int promptEndIndex = line.indexOf(platformSelect);
+ if (promptEndIndex == -1) {
+ return;
+ }
+ final String prompt = line.substring(0, promptEndIndex);
+ lldbRun = RegExp(RegExp.escape(prompt) + r'\s*run');
+ _logger.printTrace(line);
+ return;
+ }
+
// (lldb) run
// success
// 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: The Dart VM service is listening on http://127.0.0.1:57782/
- if (_lldbRun.hasMatch(line)) {
+ if (lldbRun.hasMatch(line)) {
...
修改完进行测试,确认没有问题后向 Flutter
官方提交了 PR (opens new window)
# 五、调试
这里主要记录关于调试的一些准备和注意点。
# 准备工作
使用 VSCode
打开 flutter_tools
项目,调试前需要做的就两个操作:
1、调整 .vscode/launch.json
{
"name": "flutter_tools",
"request": "launch",
"type": "dart",
"args": [
"run",
"-v",
"-d",
"设备id"
],
"program": "bin/flutter_tools.dart"
},
2、指定运行的 flutter
项目
上面只是设置了打印日志和指定运行的设备id,还差指定运行的 flutter
项目。
由于 flutter
命令里没有提供相关参数,所以我在 lib/executable.dart
文件中 Cache.flutterRoot
的上一行添加如下代码,强行修改当前执行 flutter
命令的目录路径
...
// 设置当前执行flutter命令的目录路径
globals.fs.currentDirectory = '/Users/lxf/Desktop/LXF/github/flutter_scrollview_observer/example';
Cache.flutterRoot = Cache.defaultFlutterRoot(
...
不过该方式过于粗暴,后面经过摸索,发现在 .vscode/launch.json
配置 cwd
即可达到效果,完整配置如下:
{
"name": "flutter_tools",
"request": "launch",
"type": "dart",
"args": [
"run",
"-v",
"-d",
"设备id"
],
"program": "${workspaceFolder}/bin/flutter_tools.dart",
"env": {
"FLUTTER_ROOT": "${workspaceFolder}/../../"
},
"cwd": "/Users/lxf/Desktop/gitHub/flutter_scrollview_observer/example",
},
# 注意项
当你调试完 flutter_tools
的源码后,高高兴兴去到自己的 flutter
工程目录下执行 flutter run
时,你会发现改动不生效,这是因为存在如下缓存文件
flutter/bin/cache/flutter_tools.snapshot
解决方式:将该文件删除即可,当执行任意的 flutter
命令时会自动再次生成。
# 六、最后
这次提 PR
的过程也学到了很多,过程中比较令人记忆深刻的是以下几点:
- 每一行的内容不可以以空格结尾,否则
Linux analyze
这个Check
会失败。 100+
条Check
执行时间是真的久,好像是40+分钟
,每次提交都会重新执行。- 有的
Check
时不时会抽风,像Google testing
有时会一直卡住,luci-flutter
会经常失败,这些可以不用理它,因为其它人也会,当官方调整完会自动重新执行。 - 提交
PR
后到开始审核的时间好久,2023.1.29
提交,2023.2.3
审核,5天,你知道我这5天是怎么过来的吗? 😂
但不管怎么说,功夫不负有心人,最终于 2023.2.7
成功 Merge
,这是一次不错的开始 😃
- 01
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 02
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21
- 03
- Flutter - 轻松实现PageView卡片偏移效果09-08