likes
comments
collection
share

Flutter attach error: connection to device ended too early

作者站长头像
站长
· 阅读数 9

问题

flutter attach OPPO R11s Plus 设备时报错

Error connecting to the service protocol: Exception: connection to device ended too early

google 了一下,没有找到相关的解决方法

过程

使用 VS Code 打开 flutter_tools 调试,配置launch.json 文件如下:

{
  "configurations": [
    {
      "name": "flutter_tools",
      "request": "launch",
      "type": "dart",
      "args": [
        "attach",
        "--app-id",
        "com.flutter.test",
      ],
      "program": "/Users/noif/flutter/packages/flutter_tools/lib/executable.dart",
      "cwd": "/Users/noif/Desktop/flutter_test",
    }
  ]
}

其中 program 为需要调试的程序入口文件,cwd 则是需要 attach 的 flutter 项目代码地址。

搜索报错信息 “connection to device ended too early” 发现是在 FlutterDevice 一个 Stream 的 onDone 方法中。

subscription = observatoryUris!.listen((Uri? observatoryUri) async {
  // ...
}, onError: (dynamic error) {
  globals.printTrace('Fail to handle observatory URI: $error');
}, onDone: () {
  _isListeningForObservatoryUri = false;
  if (!completer.isCompleted && !isWaitingForVm) {
    completer
        .completeError(Exception('connection to device ended too early'));
  }
});

往上追溯 observatoryUris 是何处创建的,来到 AttachCommand

class AttachCommand extends FlutterCommand {
  Future<ResidentRunner> createResidentRunner({
    required Stream<Uri> observatoryUris,
    required Device device,
    required FlutterProject flutterProject,
    required bool usesIpv6,
  }) async {
    // ...

    final FlutterDevice flutterDevice = await FlutterDevice.create(
      device,
      target: targetFile,
      targetModel: TargetModel(stringArgDeprecated('target-model')!),
      buildInfo: buildInfo,
      userIdentifier: userIdentifier,
      platform: _platform,
    );
    flutterDevice.observatoryUris = observatoryUris;
    // ...
}

继续往上看看 createResidentRunner 方法在何处调用,在 _attachToDevice 方法

Future<void> _attachToDevice(Device device) async {
  // ...

  Stream<Uri>? observatoryUri;
  bool usesIpv6 = ipv6!;
  final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
  final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
  final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;

  if (debugPort == null && debugUri == null) {
    if (device is FuchsiaDevice) {
      // ...
    } else if ((device is IOSDevice) || (device is IOSSimulator) || (device is MacOSDesignedForIPadDevice)) {
      // ...
    }
    // If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
    if (observatoryUri == null) {
      final ProtocolDiscovery observatoryDiscovery =
        ProtocolDiscovery.observatory(
          // If it's an Android device, attaching relies on past log searching
          // to find the service protocol.
          await device.getLogReader(includePastLogs: device is AndroidDevice),
          portForwarder: device.portForwarder,
          ipv6: ipv6!,
          devicePort: deviceVmservicePort,
          hostPort: hostVmservicePort,
          logger: _logger,
        );
      _logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
      observatoryUri = observatoryDiscovery.uris;
      // Determine ipv6 status from the scanned logs.
      usesIpv6 = observatoryDiscovery.ipv6;
    }
  } else {
    // ...
  }

  // ...
}

断点调试发现走到了最后的 ProtocolDiscovery 逻辑,再去其内部看下有啥

class PrototolDiscovery {
  final _BufferedStreamController<Uri> _uriStreamController = _BufferedStreamController<Uri>();
  Stream<Uri> get uris {
    final Stream<Uri> uriStream = _uriStreamController.stream
      .transform(_throttle<Uri>(
        waitDuration: throttleDuration,
      ));
    return uriStream.asyncMap<Uri>(_forwardPort);
  }
}

可以看到 uris 对应的 _uriStreamController.stream,所以可能就是在这里触发了 stream 的 close,搜索 _uriStreamController.close() 再加上断点跑一遍。

Flutter attach error: connection to device ended too early

从上图的堆栈中不能直接看出方法的调用处,只能继续搜索,发现在 ProtocolDiscovery 的初始化方法中关联到了 logReaderlogLines 中。

ProtocolDiscovery._(
  this.logReader,
  this.serviceName, {
  this.portForwarder,
  required this.throttleDuration,
  this.hostPort,
  this.devicePort,
  required this.ipv6,
  required Logger logger,
}) : _logger = logger,
     assert(logReader != null) {
  _deviceLogSubscription = logReader.logLines.listen(
    _handleLine,
    onDone: _stopScrapingLogs,
  );
}

logReaderAdbLogReader 类型,logLines 也是一个 Stream,所以再找找他对应的 _linesControllerclose 方法。

class AdbLogReader extends DeviceLogReader {
  void _start() {
    // ...
    unawaited(_adbProcess.exitCode.whenComplete(() {
      if (_linesController.hasListener) {
        _linesController.close();
      }
    }));
  }
}

这里的 _adbProcess 直接退出了,想看看这个 _abdProcess 的内部实现,发现搜索不到其对应的类型_ProcessImpl, 到这里思路有点断了,exitCode 也看不出啥有用的信息

Flutter attach error: connection to device ended too early

正在一筹莫展之际,发现这里有 _adbProcess 执行对应的参数

Flutter attach error: connection to device ended too early

就想着试试直接在命令行执行 adb 命令

$adb -s 8b9cc677 shell -x logcat -v time -s flutter

发现了一个 read: unexpected EOF 错误

Flutter attach error: connection to device ended too early

再去 google 下,终于发现以下的解决方案:

stackoverflow.com/questions/4…

Go to Settings > System > Developer options > Logger buffer sizes and choose a higher value.

在开发者模式中将 log 缓存调高就可以了。

总结

从这个排查过程可以发现,flutter attach 的源码就在 flutter_tools 中,遇到无法解决的问题可以直接打开源码断点调试看看。而 attach Android 设备是通过 adb logcat 抓取其中 flutter 相关日志,从日志中解析服务地址来实现的。

转载自:https://juejin.cn/post/7384368081466081306
评论
请登录