flutter學(xué)習(xí)第 12 節(jié):網(wǎng)絡(luò)請求與 JSON 解析

在現(xiàn)代移動應(yīng)用開發(fā)中,幾乎所有應(yīng)用都需要與后端服務(wù)器進(jìn)行數(shù)據(jù)交互,獲取遠(yuǎn)程數(shù)據(jù)并展示給用戶。Flutter 提供了多種方式來處理網(wǎng)絡(luò)請求和數(shù)據(jù)解析,本節(jié)課將詳細(xì)介紹如何在 Flutter 中進(jìn)行網(wǎng)絡(luò)請求、處理響應(yīng)數(shù)據(jù)以及解析 JSON 格式的數(shù)據(jù)。

一、HTTP 庫選擇:dio 安裝與基本使用

Flutter 官方提供了 http 包用于網(wǎng)絡(luò)請求,但在實(shí)際開發(fā)中,dio 庫因其更強(qiáng)大的功能和更簡潔的 API 而被廣泛使用。dio 是一個強(qiáng)大的 Dart HTTP 客戶端,支持?jǐn)r截器、FormData、請求取消、超時設(shè)置等高級功能。

1. 安裝 dio

pubspec.yaml 文件中添加依賴:

dependencies:
  flutter:
    sdk: flutter
  dio: ^5.9.0  # 使用最新版本

運(yùn)行 flutter pub get 安裝依賴。

2. dio 基本使用

首先導(dǎo)入 dio 包:

import 'package:dio/dio.dart';

創(chuàng)建 dio 實(shí)例:

// 創(chuàng)建默認(rèn)實(shí)例
Dio dio = Dio();

// 也可以通過 BaseOptions 配置實(shí)例
BaseOptions options = BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: const Duration(milliseconds: 5000),
  receiveTimeout: const Duration(milliseconds: 3000),
  headers: {
    'Content-Type': 'application/json',
  },
);

Dio dio = Dio(options);

二、發(fā)起 GET/POST 請求與參數(shù)處理

1. 發(fā)起 GET 請求

GET 請求通常用于從服務(wù)器獲取數(shù)據(jù):

// 簡單的 GET 請求
Future<void> fetchData() async {
  try {
    Response response = await dio.get('/users');
    print('Response data: ${response.data}');
    print('Status code: ${response.statusCode}');
  } catch (e) {
    print('Error: $e');
  }
}

// 帶查詢參數(shù)的 GET 請求
Future<void> fetchUserData() async {
  try {
    // 方式一:直接在 URL 中添加參數(shù)
    Response response1 = await dio.get('/users?userId=123&name=John');
    
    // 方式二:使用 queryParameters
    Response response2 = await dio.get(
      '/users',
      queryParameters: {
        'userId': 123,
        'name': 'John',
      },
    );
    
    print('Response data: ${response2.data}');
  } catch (e) {
    print('Error: $e');
  }
}

2. 發(fā)起 POST 請求

POST 請求通常用于向服務(wù)器提交數(shù)據(jù):

// 提交 JSON 數(shù)據(jù)
Future<void> submitData() async {
  try {
    Response response = await dio.post(
      '/users',
      data: {'name': 'John Doe', 'email': 'john@example.com', 'age': 30},
    );
    print('Response data: ${response.data}');
  } catch (e) {
    print('Error: $e');
  }
}

// 提交 FormData(表單數(shù)據(jù))
Future<void> uploadForm() async {
  try {
    FormData formData = FormData.fromMap({
      'name': 'John Doe',
      'avatar': await MultipartFile.fromFile(
        '/path/to/avatar.jpg',
        filename: 'avatar.jpg',
      ),
      'hobbies': ['reading', 'sports'],
    });

    Response response = await dio.post('/user/profile', data: formData);
    print('Response data: ${response.data}');
  } catch (e) {
    print('Error: $e');
  }
}

3. 自定義請求頭

可以為單個請求設(shè)置自定義請求頭:

Future<void> fetchWithHeaders() async {
  try {
    Response response = await dio.get(
      '/protected/data',
      options: Options(
        headers: {
          'Authorization': 'Bearer your_token_here',
          'Custom-Header': 'custom_value',
        },
      ),
    );
    print('Response data: ${response.data}');
  } catch (e) {
    print('Error: $e');
  }
}

4. 處理請求超時

可以為單個請求設(shè)置超時時間:

Future<void> fetchWithTimeout() async {
  try {
    Response response = await dio.get(
      '/slow/endpoint',
      options: Options(
        sendTimeout: const Duration(seconds: 2),
        receiveTimeout: const Duration(seconds: 5),
      ),
    );
    print('Response data: ${response.data}');
  } on DioException catch (e) {
    if (e.type == DioExceptionType.connectionTimeout) {
      print('Connection timeout');
    } else if (e.type == DioExceptionType.receiveTimeout) {
      print('Receive timeout');
    } else {
      print('Other error: $e');
    }
  }
}


三、JSON 數(shù)據(jù)解析

服務(wù)器返回的數(shù)據(jù)通常是 JSON 格式,F(xiàn)lutter 提供了多種方式來解析 JSON 數(shù)據(jù)。

1. 手動解析 JSON

Dart 內(nèi)置了 dart:convert 庫,可以手動解析 JSON 數(shù)據(jù):

import 'dart:convert';

// 假設(shè)服務(wù)器返回的 JSON 數(shù)據(jù)如下:
// {
//   "id": 1,
//   "name": "John Doe",
//   "email": "john@example.com",
//   "age": 30,
//   "hobbies": ["reading", "sports"]
// }

// 創(chuàng)建模型類
class User {
  final int id;
  final String name;
  final String email;
  final int age;
  final List<String> hobbies;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.age,
    required this.hobbies,
  });

  // 從 JSON 映射創(chuàng)建 User 實(shí)例
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
      age: json['age'],
      hobbies: List<String>.from(json['hobbies']),
    );
  }

  // 轉(zhuǎn)換為 JSON 映射
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'age': age,
      'hobbies': hobbies,
    };
  }
}

// 解析 JSON 數(shù)據(jù)
Future<void> parseUserJson() async {
  try {
    Response response = await dio.get('/users/1');

    // 將 JSON 字符串轉(zhuǎn)換為 Map
    Map<String, dynamic> jsonData = response.data;

    // 轉(zhuǎn)換為 User 對象
    User user = User.fromJson(jsonData);

    print('User name: ${user.name}');
    print('User email: ${user.email}');
  } catch (e) {
    print('Error: $e');
  }
}

// 解析 JSON 數(shù)組
Future<void> parseUsersJson() async {
  try {
    Response response = await dio.get('/users');

    // 將 JSON 數(shù)組轉(zhuǎn)換為 List
    List<dynamic> jsonList = response.data;

    // 轉(zhuǎn)換為 User 對象列表
    List<User> users = jsonList.map((json) => User.fromJson(json)).toList();

    print('Number of users: ${users.length}');
    print('First user: ${users[0].name}');
  } catch (e) {
    print('Error: $e');
  }
}

手動解析的優(yōu)點(diǎn)是簡單直接,不需要額外依賴,但缺點(diǎn)是當(dāng) JSON 結(jié)構(gòu)復(fù)雜或字段較多時,編寫解析代碼繁瑣且容易出錯。

2. 使用 json_serializable 自動生成代碼

json_serializable 是一個自動化的源代碼生成器,可以為 JSON 序列化和反序列化生成代碼,減少手動編寫解析代碼的工作量。

安裝依賴

pubspec.yaml 中添加依賴:

dependencies:
  # ... 其他依賴
  json_annotation: ^4.8.1  # 注解包

dev_dependencies:
  # ... 其他開發(fā)依賴
  build_runner: ^2.4.4     # 構(gòu)建工具
  json_serializable: ^6.7.1 # 代碼生成器

運(yùn)行 flutter pub get 安裝依賴。

創(chuàng)建模型類

import 'package:json_annotation/json_annotation.dart';

// 生成的代碼將在 user.g.dart 文件中
part 'user.g.dart';

@JsonSerializable()
class User {
  final int id;
  final String name;
  
  // 使用 @JsonKey 注解指定 JSON 字段名與類屬性名不同的情況
  @JsonKey(name: 'email_address')
  final String email;
  
  final int age;
  
  // 忽略該字段,不參與序列化和反序列化
  @JsonKey(ignore: true)
  final String? token;
  
  final List<String> hobbies;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.age,
    this.token,
    required this.hobbies,
  });

  // 從 JSON 映射創(chuàng)建 User 實(shí)例
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  // 轉(zhuǎn)換為 JSON 映射
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

生成代碼

在項(xiàng)目根目錄運(yùn)行以下命令生成序列化代碼:

flutter pub run build_runner build

如果需要在開發(fā)過程中自動生成代碼(當(dāng)模型類變化時),可以使用 watch 模式:

flutter pub run build_runner watch

運(yùn)行成功后,會生成 user.g.dart 文件,包含自動生成的序列化和反序列化代碼。

使用自動生成的代碼

Future<void> useGeneratedCode() async {
  try {
    Response response = await dio.get('/users/1');
    
    // 使用自動生成的 fromJson 方法解析
    User user = User.fromJson(response.data);
    
    print('User name: ${user.name}');
    print('User email: ${user.email}');
    
    // 序列化示例
    Map<String, dynamic> userJson = user.toJson();
    print('Serialized user: $userJson');
  } catch (e) {
    print('Error: $e');
  }
}

json_serializable 的優(yōu)點(diǎn)是減少手動編寫解析代碼的工作量,提高代碼的可靠性和可維護(hù)性,特別適合處理復(fù)雜的 JSON 結(jié)構(gòu)。



四、網(wǎng)絡(luò)狀態(tài)處理

在實(shí)際應(yīng)用中,網(wǎng)絡(luò)請求通常有幾種狀態(tài):加載中、成功、失敗。我們需要根據(jù)不同的狀態(tài)展示不同的 UI。

1. 創(chuàng)建網(wǎng)絡(luò)狀態(tài)管理類

enum NetworkStatus { initial, loading, success, error }

class NetworkResult<T> {
  final NetworkStatus status;
  final T? data;
  final String? errorMessage;

  NetworkResult.initial()
      : status = NetworkStatus.initial,
        data = null,
        errorMessage = null;

  NetworkResult.loading()
      : status = NetworkStatus.loading,
        data = null,
        errorMessage = null;

  NetworkResult.success(this.data)
      : status = NetworkStatus.success,
        errorMessage = null;

  NetworkResult.error(this.errorMessage)
      : status = NetworkStatus.error,
        data = null;
}

2. 基于狀態(tài)展示不同 UI

class DataScreen extends StatefulWidget {
  const DataScreen({super.key});

  @override
  State<DataScreen> createState() => _DataScreenState();
}

class _DataScreenState extends State<DataScreen> {
  final Dio _dio = Dio();
  NetworkResult<List<User>> _result = NetworkResult.initial();

  @override
  void initState() {
    super.initState();
    fetchUsers();
  }

  Future<void> fetchUsers() async {
    setState(() {
      _result = NetworkResult.loading();
    });

    try {
      Response response = await _dio.get('https://api.example.com/users');
      List<User> users = (response.data as List)
          .map((json) => User.fromJson(json))
          .toList();
      
      setState(() {
        _result = NetworkResult.success(users);
      });
    } catch (e) {
      setState(() {
        _result = NetworkResult.error(e.toString());
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('User List')),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    switch (_result.status) {
      case NetworkStatus.initial:
        return const Center(child: Text('Tap to load data'));
      case NetworkStatus.loading:
        return const Center(child: CircularProgressIndicator());
      case NetworkStatus.success:
        return _buildUserList(_result.data!);
      case NetworkStatus.error:
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Error: ${_result.errorMessage}'),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: fetchUsers,
                child: const Text('Retry'),
              ),
            ],
          ),
        );
    }
  }

  Widget _buildUserList(List<User> users) {
    return ListView.builder(
      itemCount: users.length,
      itemBuilder: (context, index) {
        User user = users[index];
        return ListTile(
          title: Text(user.name),
          subtitle: Text(user.email),
          trailing: Text('Age: ${user.age}'),
        );
      },
    );
  }
}


五、dio 攔截器

dio 提供了攔截器功能,可以在請求發(fā)送前或響應(yīng)返回后進(jìn)行一些統(tǒng)一處理,如添加認(rèn)證 token、處理錯誤等。

1. 請求攔截器

// 添加請求攔截器
dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      // 在請求發(fā)送前做一些處理
      print('Request: ${options.method} ${options.uri}');
      
      // 添加認(rèn)證 token
      options.headers['Authorization'] = 'Bearer your_token_here';
      
      // 繼續(xù)發(fā)送請求
      return handler.next(options);
      
      // 如果想終止請求,可以調(diào)用 handler.reject()
      // return handler.reject(DioException(requestOptions: options, type: DioExceptionType.cancel));
    },
  ),
);

2. 響應(yīng)攔截器

// 添加響應(yīng)攔截器
dio.interceptors.add(
  InterceptorsWrapper(
    onResponse: (response, handler) {
      // 在響應(yīng)返回后做一些處理
      print('Response: ${response.statusCode} ${response.data}');
      
      // 繼續(xù)處理響應(yīng)
      return handler.next(response);
    },
  ),
);

3. 錯誤攔截器

// 添加錯誤攔截器
dio.interceptors.add(
  InterceptorsWrapper(
    onError: (DioException e, handler) {
      // 處理錯誤
      print('Error: ${e.message}');
      
      // 統(tǒng)一處理 401 未授權(quán)錯誤
      if (e.response?.statusCode == 401) {
        // 可以在這里跳轉(zhuǎn)到登錄頁面
        print('Unauthorized, redirecting to login');
      }
      
      // 繼續(xù)處理錯誤
      return handler.next(e);
      
      // 如果想掩蓋錯誤,可以返回一個成功的響應(yīng)
      // return handler.resolve(Response(requestOptions: e.requestOptions, data: {}));
    },
  ),
);

4. 日志攔截器

dio 提供了一個內(nèi)置的日志攔截器,方便調(diào)試:

import 'package:dio/io.dart';

dio.interceptors.add(LogInterceptor(
  request: true,  // 打印請求信息
  requestHeader: true,  // 打印請求頭
  requestBody: true,  // 打印請求體
  responseHeader: true,  // 打印響應(yīng)頭
  responseBody: true,  // 打印響應(yīng)體
  error: true,  // 打印錯誤信息
  logPrint: (object) {
    print('Dio Log: $object');
  },
));


六、實(shí)例:請求開源 API 展示新聞列表

下面我們將實(shí)現(xiàn)一個完整的示例,使用公開的新聞 API 獲取新聞列表并展示。

1. 創(chuàng)建新聞模型類

import 'package:json_annotation/json_annotation.dart';

part 'news.g.dart';

@JsonSerializable()
class NewsArticle {
  @JsonKey(name: 'source')
  final NewsSource source;
  
  @JsonKey(name: 'author')
  final String? author;
  
  @JsonKey(name: 'title')
  final String title;
  
  @JsonKey(name: 'description')
  final String? description;
  
  @JsonKey(name: 'url')
  final String url;
  
  @JsonKey(name: 'urlToImage')
  final String? urlToImage;
  
  @JsonKey(name: 'publishedAt')
  final String publishedAt;
  
  @JsonKey(name: 'content')
  final String? content;

  NewsArticle({
    required this.source,
    this.author,
    required this.title,
    this.description,
    required this.url,
    this.urlToImage,
    required this.publishedAt,
    this.content,
  });

  factory NewsArticle.fromJson(Map<String, dynamic> json) =>
      _$NewsArticleFromJson(json);

  Map<String, dynamic> toJson() => _$NewsArticleToJson(this);
}

@JsonSerializable()
class NewsSource {
  @JsonKey(name: 'id')
  final String? id;
  
  @JsonKey(name: 'name')
  final String name;

  NewsSource({
    this.id,
    required this.name,
  });

  factory NewsSource.fromJson(Map<String, dynamic> json) =>
      _$NewsSourceFromJson(json);

  Map<String, dynamic> toJson() => _$NewsSourceToJson(this);
}

@JsonSerializable()
class NewsResponse {
  @JsonKey(name: 'status')
  final String status;
  
  @JsonKey(name: 'totalResults')
  final int totalResults;
  
  @JsonKey(name: 'articles')
  final List<NewsArticle> articles;

  NewsResponse({
    required this.status,
    required this.totalResults,
    required this.articles,
  });

  factory NewsResponse.fromJson(Map<String, dynamic> json) =>
      _$NewsResponseFromJson(json);

  Map<String, dynamic> toJson() => _$NewsResponseToJson(this);
}

運(yùn)行代碼生成命令:

flutter pub run build_runner build

2. 創(chuàng)建新聞服務(wù)類

import 'package:dio/dio.dart';

class NewsService {
  final Dio _dio = Dio();
  final String _apiKey = 'your_news_api_key'; // 替換為你的 API Key
  final String _baseUrl = 'https://newsapi.org/v2';

  NewsService() {
    // 配置 dio
    _dio.options.baseUrl = _baseUrl;
    _dio.options.connectTimeout = const Duration(seconds: 5);
    _dio.options.receiveTimeout = const Duration(seconds: 3);

    // 添加日志攔截器
    _dio.interceptors.add(LogInterceptor(responseBody: true));
  }

  // 獲取頭條新聞
  Future<NewsResponse> getTopHeadlines({String country = 'us'}) async {
    try {
      Response response = await _dio.get(
        '/top-headlines',
        queryParameters: {
          'country': country,
          'apiKey': _apiKey,
        },
      );
      return NewsResponse.fromJson(response.data);
    } on DioException catch (e) {
      print('News API error: ${e.message}');
      throw Exception('Failed to fetch news: ${e.message}');
    }
  }

  // 搜索新聞
  Future<NewsResponse> searchNews(String query) async {
    try {
      Response response = await _dio.get(
        '/everything',
        queryParameters: {
          'q': query,
          'apiKey': _apiKey,
        },
      );
      return NewsResponse.fromJson(response.data);
    } on DioException catch (e) {
      print('News API error: ${e.message}');
      throw Exception('Failed to search news: ${e.message}');
    }
  }
}

注意:需要在 News API 網(wǎng)站注冊獲取 API Key。

3. 實(shí)現(xiàn)新聞列表頁面

class NewsListScreen extends StatefulWidget {
  const NewsListScreen({super.key});

  @override
  State<NewsListScreen> createState() => _NewsListScreenState();
}

class _NewsListScreenState extends State<NewsListScreen> {
  final NewsService _newsService = NewsService();
  NetworkResult<List<NewsArticle>> _newsResult = NetworkResult.initial();
  final TextEditingController _searchController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _fetchTopHeadlines();
  }

  Future<void> _fetchTopHeadlines() async {
    setState(() {
      _newsResult = NetworkResult.loading();
    });

    try {
      NewsResponse response = await _newsService.getTopHeadlines();
      setState(() {
        _newsResult = NetworkResult.success(response.articles);
      });
    } catch (e) {
      setState(() {
        _newsResult = NetworkResult.error(e.toString());
      });
    }
  }

  Future<void> _searchNews() async {
    String query = _searchController.text.trim();
    if (query.isEmpty) return;

    setState(() {
      _newsResult = NetworkResult.loading();
    });

    try {
      NewsResponse response = await _newsService.searchNews(query);
      setState(() {
        _newsResult = NetworkResult.success(response.articles);
      });
    } catch (e) {
      setState(() {
        _newsResult = NetworkResult.error(e.toString());
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Latest News'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              controller: _searchController,
              decoration: InputDecoration(
                hintText: 'Search news...',
                suffixIcon: IconButton(
                  icon: const Icon(Icons.search),
                  onPressed: _searchNews,
                ),
                border: const OutlineInputBorder(),
              ),
              onSubmitted: (value) => _searchNews(),
            ),
          ),
          Expanded(
            child: _buildNewsContent(),
          ),
        ],
      ),
    );
  }

  Widget _buildNewsContent() {
    switch (_newsResult.status) {
      case NetworkStatus.initial:
        return const Center(child: Text('Loading news...'));
      case NetworkStatus.loading:
        return const Center(child: CircularProgressIndicator());
      case NetworkStatus.success:
        return _buildNewsList(_newsResult.data!);
      case NetworkStatus.error:
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Error: ${_newsResult.errorMessage}'),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _fetchTopHeadlines,
                child: const Text('Retry'),
              ),
            ],
          ),
        );
    }
  }

  Widget _buildNewsList(List<NewsArticle> articles) {
    if (articles.isEmpty) {
      return const Center(child: Text('No news found'));
    }

    return ListView.builder(
      itemCount: articles.length,
      itemBuilder: (context, index) {
        NewsArticle article = articles[index];
        return Card(
          margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
          child: Column(
            children: [
              if (article.urlToImage != null)
                Image.network(
                  article.urlToImage!,
                  height: 180,
                  width: double.infinity,
                  fit: BoxFit.cover,
                  errorBuilder: (context, error, stackTrace) {
                    return Container(
                      height: 180,
                      color: Colors.grey[200],
                      child: const Center(child: Icon(Icons.image_not_supported)),
                    );
                  },
                ),
              Padding(
                padding: const EdgeInsets.all(12.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      article.source.name,
                      style: TextStyle(
                        color: Colors.grey[600],
                        fontSize: 12,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      article.title,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 8),
                    if (article.description != null)
                      Text(
                        article.description!,
                        style: const TextStyle(fontSize: 14),
                        maxLines: 3,
                        overflow: TextOverflow.ellipsis,
                      ),
                    const SizedBox(height: 8),
                    Text(
                      _formatDate(article.publishedAt),
                      style: TextStyle(
                        color: Colors.grey[600],
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  String _formatDate(String dateString) {
    DateTime date = DateTime.parse(dateString);
    return DateFormat.yMMMd().add_jm().format(date);
  }

注意:需要添加 intl 依賴來格式化日期,在 pubspec.yaml 中添加 intl: ^0.18.1 并運(yùn)行 flutter pub get。

4. 配置網(wǎng)絡(luò)權(quán)限

對于 Android,需要在 android/app/src/main/AndroidManifest.xml 中添加網(wǎng)絡(luò)權(quán)限:

<uses-permission android:name="android.permission.INTERNET" />

對于 iOS,需要在 ios/Runner/Info.plist 中添加:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>


七、網(wǎng)絡(luò)請求最佳實(shí)踐

  1. 封裝網(wǎng)絡(luò)層:將網(wǎng)絡(luò)請求邏輯封裝在專門的服務(wù)類中,與 UI 層分離,提高代碼復(fù)用性和可維護(hù)性。
  2. 統(tǒng)一錯誤處理:使用攔截器統(tǒng)一處理網(wǎng)絡(luò)錯誤,如超時、無網(wǎng)絡(luò)、認(rèn)證失敗等。
  3. 合理管理請求狀態(tài):清晰展示加載中、成功、失敗等狀態(tài),提供良好的用戶體驗(yàn)。
  4. 數(shù)據(jù)緩存:對于不經(jīng)常變化的數(shù)據(jù),可以實(shí)現(xiàn)本地緩存,減少網(wǎng)絡(luò)請求,提高應(yīng)用性能。
  5. 請求取消:在頁面銷毀時取消未完成的網(wǎng)絡(luò)請求,避免內(nèi)存泄漏和不必要的資源消耗。
  6. 圖片處理:使用 cached_network_image 等庫處理網(wǎng)絡(luò)圖片,實(shí)現(xiàn)緩存和占位圖功能。
  7. 避免在 UI 線程處理復(fù)雜任務(wù):確保網(wǎng)絡(luò)請求在異步線程執(zhí)行,避免阻塞 UI。
  8. 添加日志:在開發(fā)環(huán)境添加詳細(xì)的網(wǎng)絡(luò)日志,方便調(diào)試;在生產(chǎn)環(huán)境關(guān)閉或簡化日志。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容