Flutter应用加载头像时崩溃,出现URI无主机错误求助
问题排查:Flutter应用加载头像时崩溃(No host specified in URI)
我开发了一款带用户账号的移动端应用,使用ui-avatars.com生成默认头像(示例地址:https://ui-avatars.com/api/?name=John+Doe&background=random)。现在应用多处展示这些头像时,部分图片加载失败且未显示默认图,通过Crashlytics捕获到如下崩溃信息:
Fatal Exception: FlutterError Invalid argument(s): No host specified in URI. Error thrown resolving an image codec. Fatal Exception: FlutterError 0 ??? 0x0 _HttpClient.openUrl (dart:_http) 1 ??? 0x0 IOClient.send + 63 (io_client.dart:63) 2 ??? 0x0 HttpFileService.get + 35 (file_service.dart:35) 3 ??? 0x0 WebHelper._download + 121 (web_helper.dart:121) 4 ??? 0x0 WebHelper._updateFile + 103 (web_helper.dart:103) 5 ??? 0x0 WebHelper._downloadOrAddToQueue + 71 (web_helper.dart:71)
头像URL存储在Firebase中,直接获取后通过FlutterFlow生成的代码展示图片,核心代码如下:
List<UsersRecord> adminUserUsersRecordList = snapshot.data!; return GestureDetector( onTap: () => FocusScope.of(context).requestFocus(_model.unfocusNode), child: Scaffold( key: scaffoldKey, backgroundColor: FlutterFlowTheme.of(context).white, body: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ Align( alignment: AlignmentDirectional(-1, -1), child: Padding( padding: EdgeInsetsDirectional.fromSTEB(16, 50, 0, 0), child: FlutterFlowIconButton( borderColor: Colors.transparent, borderRadius: 8, borderWidth: 1, buttonSize: 40, fillColor: FlutterFlowTheme.of(context).secondary, icon: Icon( Icons.chevron_left_rounded, color: FlutterFlowTheme.of(context).white, size: 20, ), onPressed: () async { context.safePop(); }, ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB(24, 16, 0, 8), child: Text( 'Liste des utilisateurs (${adminUserUsersRecordList.length.toString()})', style: FlutterFlowTheme.of(context).bodyMedium.override( fontFamily: 'Raleway', color: FlutterFlowTheme.of(context).secondary, fontSize: 20, fontWeight: FontWeight.bold, ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB(0, 8, 0, 40), child: Builder( builder: (context) { final user = adminUserUsersRecordList.toList(); return ListView.builder( padding: EdgeInsets.zero, primary: false, shrinkWrap: true, scrollDirection: Axis.vertical, itemCount: user.length, itemBuilder: (context, userIndex) { final userItem = user[userIndex]; return Padding( padding: EdgeInsetsDirectional.fromSTEB(24, 16, 24, 0), child: InkWell( splashColor: Colors.transparent, focusColor: Colors.transparent, hoverColor: Colors.transparent, highlightColor: Colors.transparent, onTap: () async { context.pushNamed( 'User', queryParameters: { 'user': serializeParam( userItem.reference, ParamType.DocumentReference, ), }.withoutNulls, ); }, child: Container( width: double.infinity, height: 70, decoration: BoxDecoration( boxShadow: [ BoxShadow( blurRadius: 80, color: Color(0x1A000000), offset: Offset(0, 15), spreadRadius: 0, ) ], ), child: Container( width: MediaQuery.sizeOf(context).width * 0.8, child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(8), bottomRight: Radius.circular(0), topLeft: Radius.circular(8), topRight: Radius.circular(0), ), child: CachedNetworkImage( fadeInDuration: Duration(milliseconds: 500), fadeOutDuration: Duration(milliseconds: 500), imageUrl: userItem.photoUrl, width: MediaQuery.sizeOf(context) .width * 0.18, height: 70, fit: BoxFit.cover, ), ), Align( alignment: AlignmentDirectional(1, 0), child: Container( width: MediaQuery.sizeOf(context) .width * 0.71, height: 155, decoration: BoxDecoration( color: FlutterFlowTheme.of(context) .white, borderRadius: BorderRadius.circular(8), ), child: Padding( padding: EdgeInsetsDirectional .fromSTEB(16, 0, 16, 0), child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment .center, crossAxisAlignment: CrossAxisAlignment .start, children: [ RichText( text: TextSpan( children: [ TextSpan( text: userItem .displayName, style: TextStyle(), ) ], style: FlutterFlowTheme .of(context) .bodyMedium .override( fontFamily: 'Raleway', color: FlutterFlowTheme.of( context) .secondary, fontSize: 16, fontWeight: FontWeight .bold, ), ), ), Padding( padding: EdgeInsetsDirectional .fromSTEB( 0, 4, 0, 0), child: Text( 'Inscrit le ${dateTimeFormat( 'd/M H:mm', userItem .createdTime, locale: FFLocalizations .of(context) .languageCode, )}', style: FlutterFlowTheme .of(context) .bodyMedium .override( fontFamily: 'Raleway', color: FlutterFlowTheme.of( context) .secondaryText, fontWeight: FontWeight .w500, ), ), ), ], ), InkWell( splashColor: Colors.transparent, focusColor: Colors.transparent, hoverColor: Colors.transparent, highlightColor: Colors.transparent, onTap: () async { await showModalBottomSheet( isScrollControlled: true, backgroundColor: Colors.transparent, context: context, builder: (context) { return GestureDetector( onTap: () => FocusScope .of(context) .requestFocus(_model .unfocusNode), child: Padding( padding: MediaQuery .viewInsetsOf( context), child: BottomSheetFriendsWidget( user: userItem .reference, ), ), ); }, ).then((value) => setState(() {})); }, child: Icon( Icons.more_vert_sharp, color: Colors.black, size: 24, ), ), ], ), ), ), ), ], ), ), ), ), ); }, ); }, ), ), ], ), ), ), );
应用界面如图所示:
问题原因分析
崩溃日志中的Invalid argument(s): No host specified in URI明确指向:传入CachedNetworkImage的imageUrl是无效URI,缺少主机地址。大概率是以下情况:
- 部分用户的
photoUrl字段为空字符串、null,或是格式错误的URL(仅含路径无域名) - Firebase中存储的头像URL被意外修改,或是生成默认头像时逻辑出错,导致URL不完整
修复方案
1. 为图片加载添加容错与兜底逻辑
修改CachedNetworkImage配置,增加URL有效性校验、占位图和错误兜底图:
// 先添加URL校验函数 bool _isValidUrl(String? url) { if (url == null || url.trim().isEmpty) return false; try { final uri = Uri.parse(url.trim()); return uri.host.isNotEmpty && uri.hasAbsolutePath; } catch (e) { return false; } } // 修改CachedNetworkImage部分 ClipRRect( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(8), bottomRight: Radius.circular(0), topLeft: Radius.circular(8), topRight: Radius.circular(0), ), child: CachedNetworkImage( fadeInDuration: Duration(milliseconds: 500), fadeOutDuration: Duration(milliseconds: 500), // 校验URL,无效则使用ui-avatars生成默认头像 imageUrl: _isValidUrl(userItem.photoUrl) ? userItem.photoUrl! : "https://ui-avatars.com/api/?name=${Uri.encodeComponent(userItem.displayName ?? "Utilisateur")}&background=random", width: MediaQuery.sizeOf(context).width * 0.18, height: 70, fit: BoxFit.cover, // 加载时的占位图 placeholder: (context, url) => Center(child: CircularProgressIndicator(strokeWidth: 2)), // 加载失败时的兜底图标 errorWidget: (context, url, error) => Icon(Icons.person_rounded, size: 70, color: FlutterFlowTheme.of(context).secondaryText), ), ),
2. 修复数据源头问题
- 检查用户注册/更新流程,确保生成默认头像URL的逻辑稳定,避免生成无效地址
- 批量清理Firebase中无效的
photoUrl字段,为空或格式错误的统一替换为正确的ui-avatars链接
3. 增强数据读取时的容错
在获取用户头像URL时直接做兜底处理,避免无效值流入UI层:
final String? rawAvatarUrl = userItem.photoUrl?.trim(); final String validAvatarUrl = _isValidUrl(rawAvatarUrl) ? rawAvatarUrl! : "https://ui-avatars.com/api/?name=${Uri.encodeComponent(userItem.displayName ?? "Utilisateur")}&background=random";
内容的提问来源于stack exchange,提问作者Dany Naumann
相关产品推荐
相关产品推荐

