You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android模拟器gRPC连接报错:Error connecting: Connection shutting down.

Android模拟器中gRPC连接报错:Error connecting: Connection shutting down.

问题现象

在Android模拟器中尝试建立gRPC连接时,持续抛出以下错误:

Login failed: gRPC Error (code: 14, codeName: UNAVAILABLE, message: Error connecting: Connection shutting down., details: null, rawResponse: null, trailers: {})

已确认的其他情况:

  • 应用内与REST API、Firebase等外部服务通信正常
  • 使用grpcurl直接连接gRPC服务器成功
  • 尝试过localhost、10.0.2.2、GCP外部URL三种地址,报错结果一致

相关代码

Provider配置代码

import 'package:grpc/grpc.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '/api/v1/user/Auth.pbgrpc.dart';

final clientProvider = Provider.autoDispose<ClientChannel>((ref) {
  const host = '10.0.2.2';
  const port = 3000;
  final channel = ClientChannel(
    host,
    port: port,
    options: const ChannelOptions(
      credentials: host == '10.0.2.2'
          ? ChannelCredentials.insecure()
          : ChannelCredentials.secure(),
    ),
  );
  ref.onDispose(() {
    channel.shutdown();
  });
  return channel;
});

final tokenProvider = StateProvider<String?>((ref) => null);
final refreshTokenProvider = StateProvider<String?>((ref) => null);

CallOptions _callOptionsWithToken(Ref ref) {
  final token = ref.watch(tokenProvider);
  final refreshToken = ref.watch(refreshTokenProvider);
  final metadata = token != null && refreshToken != null
      ? <String, String>{
          'Authorization': 'Bearer $token',
          'X-Refresh-Token': refreshToken
        }
      : <String, String>{};
  return CallOptions(metadata: metadata);
}

final authControllerProvider =
    Provider.autoDispose<AuthControllerClient>((ref) {
  final channel = ref.watch(clientProvider);
  return AuthControllerClient(channel, options: _callOptionsWithToken(ref));
});

调用函数代码

Future<void> signIn(WidgetRef ref, String email, String password) async {
    final authController = ref.read(authControllerProvider);

    try {
      final response = await authController.signIn(
        UserAuthRequest(
          email: email,
          password: password,
        ),
      );
      print(response);

      if (response.accessToken != "") {
        ref
            .read(tokenProvider.notifier)
            .update((state) => response.accessToken);
      }
      if (response.refreshToken != "") {
        ref
            .read(refreshTokenProvider.notifier)
            .update((state) => response.refreshToken);
      }
    } catch (e) {
      print("Login failed: $e");
    }
  }

Auth.proto定义

syntax = "proto3";

package server.admin;
import "google/protobuf/empty.proto";

message AdminAuthRequest {
  string email = 1;
  string password = 2;
}
message AdminRefreshTokenRequest {
    string accessToken = 1;
    string refreshToken= 2;
}

message AdminAuthResponse {
    string accessToken = 1;
    int64 expiresIn = 2;
    string refreshToken= 3;
}
message AdminRegisterRequest {
  string Name = 1;
  string Mail = 2;
  string BelongStoreID = 4;
}

message ResetPasswordRequest{
  string email = 1;
}

message UpdatePasswordRequest{
  string password = 1;
}

message UpdateEmailRequest{
  string email = 1;
}

service AuthController {
  rpc Register(AdminRegisterRequest) returns (google.protobuf.Empty) {}
  rpc SignUp(AdminAuthRequest) returns (google.protobuf.Empty) {}
  rpc SignOut(google.protobuf.Empty) returns (google.protobuf.Empty) {}
  rpc Refresh(AdminRefreshTokenRequest) returns (AdminAuthResponse) {}
  rpc SignIn(AdminAuthRequest) returns (AdminAuthResponse) {}
  rpc ResetPasswordMail(ResetPasswordRequest) returns (google.protobuf.Empty) {}
  rpc UpdatePassword(UpdatePasswordRequest) returns (google.protobuf.Empty) {}
  rpc UpdateEmail(UpdateEmailRequest) returns (google.protobuf.Empty) {}
}

Flutter版本信息

Flutter 3.16.2 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 9e1c857886 (3 weeks ago) • 2023-11-30 11:51:18 -0600
Engine • revision cf7a9d0800
Tools • Dart 3.2.2 • DevTools 2.28.3

排查与解决方案

1. 修复ClientChannel生命周期问题

你给clientProvider和authControllerProvider都加了autoDispose,但gRPC通道需要保持单例复用,自动销毁会导致连接提前关闭。修改clientProvider去掉autoDispose:

final clientProvider = Provider<ClientChannel>((ref) {
  const host = '10.0.2.2';
  const port = 3000;
  final channel = ClientChannel(
    host,
    port: port,
    options: const ChannelOptions(
      credentials: host == '10.0.2.2'
          ? ChannelCredentials.insecure()
          : ChannelCredentials.secure(),
    ),
  );
  ref.onDispose(() {
    channel.shutdown();
  });
  return channel;
});

2. 动态传入CallOptions

当前创建AuthControllerClient时固定了CallOptions,后续token更新后客户端不会自动刷新配置。改为每次调用时动态传入:

  • 修改authControllerProvider:
final authControllerProvider =
    Provider<AuthControllerClient>((ref) {
  final channel = ref.watch(clientProvider);
  return AuthControllerClient(channel);
});
  • 调用时添加options:
final response = await authController.signIn(
  UserAuthRequest(
    email: email,
    password: password,
  ),
  options: _callOptionsWithToken(ref),
);

3. 模拟器网络调试

  • 执行adb reverse tcp:3000 tcp:3000,将模拟器3000端口转发到本地对应端口
  • 重启Android模拟器,清除应用缓存后重试
  • 检查本地防火墙是否允许3000端口的入站连接

4. TLS证书配置(针对GCP外部URL)

如果连接HTTPS的gRPC服务器,确认证书有效性。若使用自签名证书,需在ChannelOptions中指定根证书:

ChannelOptions(
  credentials: ChannelCredentials.secure(
    certificates: utf8.encode(rootCertPem), // 替换为你的根证书内容
  ),
)

内容的提问来源于stack exchange,提问作者sthm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 20:25:22