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
相关产品推荐
相关产品推荐

