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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 13:05:57