yx_app_upgrade_flutter/lib/app_upgrade_plugin_method_c...

841 lines
28 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart';
import 'app_upgrade_plugin_platform_interface.dart';
import 'core/http_config.dart';
import 'core/permission_helper.dart';
import 'models/install_strategy.dart';
import 'models/upgrade_info.dart';
/// An implementation of [AppUpgradePluginPlatform] that uses method channels.
class MethodChannelAppUpgradePlugin extends AppUpgradePluginPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('app_upgrade_plugin');
late Dio _dio;
HttpConfig _httpConfig = const HttpConfig();
MethodChannelAppUpgradePlugin() {
// 默认配置:根据环境自动决定
if (!const bool.fromEnvironment('dart.vm.product')) {
// 开发环境:自动使用开发配置(绕过证书)
_httpConfig = HttpConfig.development;
} else {
// 生产环境:使用生产配置(严格验证)
_httpConfig = HttpConfig.production;
}
// 初始化Dio
_initializeDio(_httpConfig);
}
/// 初始化Dio实例
void _initializeDio(HttpConfig config) {
_httpConfig = config;
// 配置 Dio 实例
_dio = Dio(BaseOptions(
connectTimeout: Duration(seconds: config.connectTimeout),
receiveTimeout: Duration(seconds: config.receiveTimeout),
sendTimeout: Duration(seconds: config.connectTimeout),
headers: {
'User-Agent': 'AppUpgradePlugin/1.0',
'Accept': 'application/json',
'Content-Type': 'application/json',
...?config.headers,
},
// 允许所有状态码,我们自己处理
validateStatus: (status) => true,
));
// 配置HTTPS支持
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
final client = HttpClient();
if (config.ignoreCertificate) {
client.badCertificateCallback = (cert, host, port) => true;
}
return client;
};
// 清除旧的拦截器
_dio.interceptors.clear();
// 添加拦截器
if (config.enableLog) {
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
debugPrint('==== HTTP请求 ====');
debugPrint('${options.method} ${options.uri}');
debugPrint('请求头: ${options.headers}');
if (options.data != null) {
debugPrint('请求体: ${options.data}');
}
handler.next(options);
},
onResponse: (response, handler) {
debugPrint('==== HTTP响应 ====');
debugPrint('状态码: ${response.statusCode}');
debugPrint('URL: ${response.requestOptions.uri}');
handler.next(response);
},
onError: (DioException e, handler) {
_logDioError(e);
handler.next(e);
},
));
}
}
@override
void configureHttp(HttpConfig config) {
_initializeDio(config);
}
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
@override
Future<UpgradeInfo?> checkUpdate(String url, {Map<String, dynamic>? params}) async {
try {
final packageInfo = await PackageInfo.fromPlatform();
final currentVersionName = packageInfo.version;
final currentBuildNumber = int.parse(packageInfo.buildNumber);
// 准备请求参数
final requestParams = {
'version': currentVersionName,
'buildNumber': currentBuildNumber,
'platform': Platform.isAndroid ? 'android' : 'ios',
...?params,
};
debugPrint('==== 检查更新 ====');
debugPrint('当前版本: $currentVersionName (Build: $currentBuildNumber)');
debugPrint('请求URL: $url');
Response? response;
// 根据配置或参数决定请求方式
final usePost = _httpConfig.defaultMethod.toUpperCase() == 'POST' ||
(params != null && params.containsKey('_method') && params['_method'] == 'POST');
if (usePost) {
// POST请求
debugPrint('使用POST请求');
response = await _dio.post(
url,
data: requestParams,
);
} else {
// GET请求
debugPrint('使用GET请求');
response = await _dio.get(
url,
queryParameters: requestParams,
);
}
debugPrint('响应状态: ${response.statusCode}');
if (response.statusCode == 200) {
// 处理响应数据
dynamic responseData = response.data;
// 如果响应是字符串尝试解析为JSON
if (responseData is String) {
if (responseData.isEmpty) {
throw Exception('服务器返回空内容');
}
try {
responseData = json.decode(responseData);
} catch (_) {
throw Exception('响应数据格式错误');
}
}
debugPrint('响应数据: $responseData');
// 解析更新信息
final upgradeInfo = UpgradeInfo.fromJson(
responseData,
currentBuildNumber: currentBuildNumber,
currentVersionName: currentVersionName,
);
// 比较版本
if (upgradeInfo.hasUpdate) {
debugPrint('✅ 发现新版本: ${upgradeInfo.versionName}');
return upgradeInfo;
} else {
debugPrint('✅ 当前已是最新版本');
return null;
}
} else {
throw Exception('服务器响应错误: ${response.statusCode}');
}
} on DioException catch (e) {
if (e.type == DioExceptionType.connectionError && e.error is SocketException) {
final socketError = e.error as SocketException;
if (socketError.message.contains('Failed host lookup')) {
debugPrint('❌ DNS解析失败请检查网络连接或域名配置');
}
}
_handleNetworkError(e);
return null;
} catch (e) {
debugPrint('❌ 检查更新失败: $e');
return null;
}
}
@override
Future<int?> getAndroidSdkVersion() async {
if (!Platform.isAndroid) return null;
return await methodChannel.invokeMethod<int>('getAndroidSdkVersion');
}
@override
Future<bool> openInstallPermissionSettings() async {
if (!Platform.isAndroid) return false;
try {
final result = await methodChannel.invokeMethod<bool>('openInstallPermissionSettings');
return result ?? false;
} catch (e) {
debugPrint('Failed to open install permission settings: $e');
return false;
}
}
@override
Future<Map<String, dynamic>?> getDeviceInfo() async {
if (!Platform.isAndroid) return null;
try {
final result = await methodChannel.invokeMethod<Map<dynamic, dynamic>>('getDeviceInfo');
return result?.cast<String, dynamic>();
} catch (e) {
debugPrint('Failed to get device info: $e');
return null;
}
}
@override
Future<List<String>> getInstalledMarkets() async {
if (!Platform.isAndroid) return [];
try {
final result = await methodChannel.invokeMethod<List<dynamic>>('getInstalledMarkets');
if (result == null) return [];
return result.map((item) => item.toString()).toList();
} catch (e) {
debugPrint('Failed to get installed markets: $e');
return [];
}
}
@override
Future<Map<String, String>> getAppInfo() async {
final packageInfo = await PackageInfo.fromPlatform();
return {
'appName': packageInfo.appName,
'packageName': packageInfo.packageName,
'version': packageInfo.version,
'buildNumber': packageInfo.buildNumber,
};
}
@override
Future<String?> downloadApk(String url, {Function(DownloadProgress)? onProgress, String? savePath}) async {
if (!Platform.isAndroid) {
throw PlatformException(code: 'PLATFORM_NOT_SUPPORTED', message: 'downloadApk only supports Android platform');
}
debugPrint('开始下载APK: $url');
// 先测试URL连接性
final errorMessage = await testDownloadUrlWithError(url);
if (errorMessage != null) {
debugPrint('错误: $errorMessage');
throw Exception(errorMessage);
}
try {
// 获取保存路径
savePath ??= await getDownloadPath();
if (savePath == null) {
debugPrint('错误: 无法获取下载路径');
throw Exception('无法获取下载路径');
}
debugPrint('下载路径: $savePath');
// 创建文件名
final fileName = 'app_${DateTime.now().millisecondsSinceEpoch}.apk';
final filePath = '$savePath/$fileName';
debugPrint('文件将保存到: $filePath');
// 配置下载选项
final options = Options(
responseType: ResponseType.bytes,
followRedirects: true,
validateStatus: (status) {
return status != null && status < 500;
},
headers: {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
},
);
// 下载文件
final response = await _dio.download(
url,
filePath,
onReceiveProgress: (received, total) {
if (total != -1) {
final progress = (received / total * 100).toStringAsFixed(0);
debugPrint('下载进度: $progress% ($received/$total)');
}
if (onProgress != null) {
onProgress(DownloadProgress(received: received, total: total));
}
},
options: options,
deleteOnError: true,
);
debugPrint('下载完成,响应状态码: ${response.statusCode}');
// 检查文件是否存在
final file = File(filePath);
if (await file.exists()) {
final fileSize = await file.length();
debugPrint('文件保存成功,大小: $fileSize 字节');
return filePath;
} else {
debugPrint('错误: 文件保存失败');
return null;
}
} on DioException catch (e) {
debugPrint('Dio下载异常:');
debugPrint(' 类型: ${e.type}');
debugPrint(' 消息: ${e.message}');
debugPrint(' 错误: ${e.error}');
if (e.response != null) {
debugPrint(' 响应状态码: ${e.response?.statusCode}');
debugPrint(' 响应数据: ${e.response?.data}');
}
return null;
} catch (e, stackTrace) {
debugPrint('下载APK失败: $e');
debugPrint('堆栈跟踪: $stackTrace');
return null;
}
}
@override
Future<bool> installApk(String filePath) async {
if (!Platform.isAndroid) {
debugPrint('installApk: 非Android平台');
return false;
}
try {
debugPrint('开始安装APK: $filePath');
// Check if file exists
final file = File(filePath);
if (!await file.exists()) {
debugPrint('安装失败: APK文件不存在 - $filePath');
return false;
}
final result = await methodChannel.invokeMethod<bool>('installApk', {'filePath': filePath});
debugPrint('安装APK结果: $result');
return result ?? false;
} catch (e) {
debugPrint('安装APK异常: $e');
// Provide more specific error messages
if (e.toString().contains('PERMISSION_DENIED')) {
debugPrint('安装失败: 缺少安装未知应用权限');
} else if (e.toString().contains('FILE_NOT_FOUND')) {
debugPrint('安装失败: APK文件未找到');
} else if (e.toString().contains('FILEPROVIDER_ERROR')) {
debugPrint('安装失败: FileProvider配置错误');
} else if (e.toString().contains('INTENT_ERROR')) {
debugPrint('安装失败: 无法启动安装程序');
}
return false;
}
}
@override
Future<bool> installApkWithSystemFlow(String filePath) async {
if (!Platform.isAndroid) {
debugPrint('installApkWithSystemFlow: 非Android平台');
return false;
}
try {
debugPrint('开始使用系统流程安装APK: $filePath');
// Check if file exists
final file = File(filePath);
if (!await file.exists()) {
debugPrint('安装失败: APK文件不存在 - $filePath');
return false;
}
final result = await methodChannel.invokeMethod<bool>('installApkWithSystemFlow', {'filePath': filePath});
debugPrint('系统流程安装APK结果: $result');
return result ?? false;
} catch (e) {
debugPrint('系统流程安装APK异常: $e');
return false;
}
}
@override
Future<bool> installApkWithConfig(String filePath, InstallConfig config) async {
if (!Platform.isAndroid) {
debugPrint('installApkWithConfig: 非Android平台');
return false;
}
try {
debugPrint('开始使用配置策略安装APK: $filePath, 策略: ${config.strategy}');
// Check if file exists
final file = File(filePath);
if (!await file.exists()) {
debugPrint('安装失败: APK文件不存在 - $filePath');
return false;
}
switch (config.strategy) {
case InstallStrategy.systemFlow:
return await _installWithSystemFlow(filePath);
case InstallStrategy.preCheckPermission:
return await _installWithPreCheck(filePath, config);
case InstallStrategy.smart:
return await _installWithSmartStrategy(filePath, config);
}
} catch (e) {
debugPrint('配置策略安装APK异常: $e');
return false;
}
}
Future<bool> _installWithSystemFlow(String filePath) async {
final result = await methodChannel.invokeMethod<bool>('installApkWithSystemFlow', {'filePath': filePath});
return result ?? false;
}
Future<bool> _installWithPreCheck(String filePath, InstallConfig config) async {
// 先检查权限
final permissionStatus = await PermissionHelper.checkInstallPermission();
if (permissionStatus != InstallPermissionStatus.granted) {
debugPrint('安装权限未授予,状态: $permissionStatus');
if (config.autoOpenSettings) {
// 自动跳转到权限设置
final opened = await openInstallPermissionSettings();
if (opened) {
debugPrint('已跳转到权限设置页面');
// 等待用户操作
await Future.delayed(const Duration(seconds: 2));
// 重新检查权限
final newStatus = await PermissionHelper.checkInstallPermission();
if (newStatus != InstallPermissionStatus.granted) {
debugPrint('用户未授予权限');
return false;
}
} else {
debugPrint('无法跳转到权限设置页面');
return false;
}
} else {
return false;
}
}
// 有权限时使用传统安装方式
final result = await methodChannel.invokeMethod<bool>('installApk', {'filePath': filePath});
return result ?? false;
}
Future<bool> _installWithSmartStrategy(String filePath, InstallConfig config) async {
// 智能策略:先检查权限状态
final permissionStatus = await PermissionHelper.checkInstallPermission();
if (permissionStatus == InstallPermissionStatus.granted) {
// 有权限时使用预检查方式
debugPrint('智能策略: 检测到已有权限,使用预检查方式');
return await _installWithPreCheck(filePath, config);
} else {
// 无权限时使用系统流程
debugPrint('智能策略: 检测到无权限,使用系统流程');
return await _installWithSystemFlow(filePath);
}
}
@override
Future<bool> goToAppStore(String url, {required BuildContext context}) async {
try {
final uri = Uri.parse(url);
// 对于其他协议(如 https://),先检查是否可以打开
final bool flag = await canLaunchUrl(uri);
if (flag) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
return true;
} else {
// 对于 market:// 协议canLaunchUrl 可能不准确(即使应用已上架也可能返回 false
// 所以直接尝试打开,如果失败再返回 false
if (uri.scheme == 'market') {
try {
await launchUrl(uri, mode: LaunchMode.externalApplication);
return true;
} catch (e) {
debugPrint('无法打开应用市场: $url, 错误: $e');
}
}
}
debugPrint('无法打开URL: $url');
return false;
} catch (e) {
debugPrint('跳转应用商店失败: $e');
return false;
}
}
@override
Future<String?> getDownloadPath({bool checkPermission = true}) async {
if (Platform.isAndroid && checkPermission) {
// 检查存储权限状态
final sdkVersion = await _getAndroidSdkVersion();
// Android 9 及以下需要 WRITE_EXTERNAL_STORAGE 权限来写入公共目录
if (sdkVersion <= 28) {
final permission = await Permission.storage.status;
if (!permission.isGranted) {
debugPrint('存储权限未授予,使用应用私有目录');
// 权限未授予,直接使用应用私有目录
return await _getAppPrivateDownloadPath();
}
}
}
try {
// 首先尝试使用原生方法获取下载路径(公共 Download 目录)
final nativePath = await methodChannel.invokeMethod<String>('getDownloadPath');
if (nativePath != null && nativePath.isNotEmpty) {
// 验证路径是否可写(对于 Android 9 及以下)
if (Platform.isAndroid) {
final sdkVersion = await _getAndroidSdkVersion();
if (sdkVersion <= 28) {
try {
if (await _canWriteToDirectory(nativePath)) {
debugPrint('使用原生下载路径: $nativePath');
return nativePath;
} else {
debugPrint('无法写入公共下载目录,使用应用私有目录');
return await _getAppPrivateDownloadPath();
}
} catch (e) {
debugPrint('检查下载路径权限失败: $e,使用应用私有目录');
return await _getAppPrivateDownloadPath();
}
} else {
debugPrint('使用原生下载路径: $nativePath');
return nativePath;
}
} else {
debugPrint('使用原生下载路径: $nativePath');
return nativePath;
}
}
} catch (e) {
debugPrint('获取原生下载路径失败: $e');
}
// 备用方案:使用 path_provider
try {
Directory? directory;
if (Platform.isAndroid) {
// Android: 优先使用外部存储下载目录
directory = await getExternalStorageDirectory();
if (directory != null) {
// 创建 Download 子目录
final downloadDir = Directory('${directory.path}/Download');
if (!await downloadDir.exists()) {
try {
await downloadDir.create(recursive: true);
} catch (e) {
debugPrint('无法创建外部存储下载目录: $e,使用应用私有目录');
return await _getAppPrivateDownloadPath();
}
}
debugPrint('使用外部存储下载路径: ${downloadDir.path}');
return downloadDir.path;
}
}
// 如果外部存储不可用,使用应用文档目录
return await _getAppPrivateDownloadPath();
} catch (e) {
debugPrint('获取备用下载路径失败: $e');
return await _getAppPrivateDownloadPath();
}
}
/// 获取应用私有下载路径(不需要权限)
/// 使用外部存储的应用私有目录Android/data/包名/files/),因为它在 file_paths.xml 中已配置
/// 这个目录在 Android 10+ 不需要 WRITE_EXTERNAL_STORAGE 权限
Future<String> _getAppPrivateDownloadPath() async {
try {
// 使用外部存储的应用私有目录(/storage/emulated/0/Android/data/包名/files/
// 这个目录在 Android 10+ 不需要 WRITE_EXTERNAL_STORAGE 权限
// 在 Android 9 及以下,如果无法写入会自动抛出异常,会被下面的 catch 捕获
// 并且在 file_paths.xml 中通过 external-files-path 配置FileProvider 可以访问
final externalDir = await getExternalStorageDirectory();
if (externalDir != null) {
final downloadDir = Directory('${externalDir.path}/downloads');
if (!await downloadDir.exists()) {
await downloadDir.create(recursive: true);
}
debugPrint('使用外部存储应用私有下载路径: ${downloadDir.path}');
return downloadDir.path;
}
} catch (e) {
debugPrint('无法使用外部存储应用私有目录: $e');
}
// 备用方案:使用内部 files 目录(/data/data/包名/files/
// 这个目录在 file_paths.xml 中通过 files-path 配置FileProvider 可以访问
try {
// 获取应用数据目录的父目录,然后访问 files 子目录
final appDataDir = await getApplicationSupportDirectory();
final parentDir = Directory(appDataDir.path).parent;
final filesPath = '${parentDir.path}/files';
final downloadDir = Directory('$filesPath/downloads');
if (!await downloadDir.exists()) {
await downloadDir.create(recursive: true);
}
debugPrint('使用内部 files 下载路径: ${downloadDir.path}');
return downloadDir.path;
} catch (e) {
debugPrint('无法使用内部 files 目录: $e');
// 最后的备用方案:使用 cache 目录(/data/data/包名/cache/
// 这个目录在 file_paths.xml 中通过 cache-path 配置FileProvider 可以访问
try {
final cacheDir = await getTemporaryDirectory();
final downloadDir = Directory('${cacheDir.path}/downloads');
if (!await downloadDir.exists()) {
await downloadDir.create(recursive: true);
}
debugPrint('使用 cache 下载路径: ${downloadDir.path}');
return downloadDir.path;
} catch (e) {
debugPrint('无法使用 cache 目录: $e');
// 如果所有方案都失败,抛出异常
throw Exception('无法获取可用的下载路径');
}
}
}
/// 检查是否可以写入目录
Future<bool> _canWriteToDirectory(String path) async {
try {
final dir = Directory(path);
if (!await dir.exists()) {
await dir.create(recursive: true);
}
// 尝试创建一个测试文件
final testFile = File('$path/.test_write_${DateTime.now().millisecondsSinceEpoch}');
await testFile.writeAsString('test');
await testFile.delete();
return true;
} catch (e) {
debugPrint('无法写入目录 $path: $e');
return false;
}
}
/// 获取 Android SDK 版本
Future<int> _getAndroidSdkVersion() async {
if (!Platform.isAndroid) return 0;
try {
final sdkVersion = await methodChannel.invokeMethod<int>('getAndroidSdkVersion');
return sdkVersion ?? 0;
} catch (e) {
debugPrint('获取 Android SDK 版本失败: $e');
return 0;
}
}
@override
Future<bool> checkApkExists(String version, String? md5) async {
if (!Platform.isAndroid) return false;
final result = await methodChannel.invokeMethod<bool>('checkApkExists', {
'version': version,
'md5': md5,
});
return result ?? false;
}
/// 测试下载URL的连接性并返回错误信息
Future<String?> testDownloadUrlWithError(String url) async {
try {
debugPrint('测试下载URL连接: $url');
final response = await _dio.head(
url,
options: Options(
receiveTimeout: const Duration(seconds: 10),
sendTimeout: const Duration(seconds: 10),
validateStatus: (status) => true,
headers: {
'User-Agent': 'AppUpgradePlugin/1.0',
},
),
);
debugPrint('URL测试响应状态码: ${response.statusCode}');
debugPrint('响应头: ${response.headers.map}');
if (response.statusCode == 200 || response.statusCode == 206) {
final contentLength = response.headers.value('content-length');
if (contentLength != null) {
final size = int.tryParse(contentLength) ?? 0;
debugPrint('文件大小: ${(size / 1024 / 1024).toStringAsFixed(2)} MB');
}
return null; // 无错误
} else if (response.statusCode == 404) {
debugPrint('错误: 文件不存在 (404)');
return '文件不存在 (404),请检查下载地址';
} else if (response.statusCode == 403) {
debugPrint('错误: 访问被拒绝 (403)');
return '访问被拒绝 (403),请检查下载权限';
} else if (response.statusCode == 401) {
debugPrint('错误: 需要认证 (401)');
return '需要认证 (401),请检查下载权限';
} else {
debugPrint('错误: 未知状态 (${response.statusCode})');
return '无法连接到下载服务器 (${response.statusCode})';
}
} on DioException catch (e) {
debugPrint('测试URL连接失败: $e');
if (e.type == DioExceptionType.connectionTimeout) {
return '连接超时,请检查网络连接';
} else if (e.type == DioExceptionType.receiveTimeout) {
return '接收超时,请检查网络连接';
} else if (e.type == DioExceptionType.connectionError) {
return '无法连接到下载服务器,请检查网络';
} else {
return '无法连接到下载URL: ${e.message}';
}
} catch (e) {
debugPrint('测试URL连接失败: $e');
return '无法连接到下载URL: $e';
}
}
/// 记录Dio错误详情
void _logDioError(DioException e) {
debugPrint('========== Dio错误详情 ==========');
debugPrint('错误类型: ${e.type}');
debugPrint('错误消息: ${e.message}');
if (e.error != null) {
debugPrint('原始错误: ${e.error}');
if (e.error is SocketException) {
final socketError = e.error as SocketException;
debugPrint('Socket错误代码: ${socketError.osError?.errorCode}');
debugPrint('Socket错误消息: ${socketError.osError?.message}');
}
}
if (e.response != null) {
debugPrint('响应状态码: ${e.response?.statusCode}');
debugPrint('响应消息: ${e.response?.statusMessage}');
}
debugPrint('请求URL: ${e.requestOptions.uri}');
debugPrint('请求方法: ${e.requestOptions.method}');
debugPrint('================================');
}
/// 处理网络错误,提供友好的错误消息
void _handleNetworkError(DioException e) {
String errorMessage = '网络请求失败';
switch (e.type) {
case DioExceptionType.connectionTimeout:
errorMessage = '连接超时,请检查网络';
break;
case DioExceptionType.sendTimeout:
errorMessage = '发送超时,请检查网络';
break;
case DioExceptionType.receiveTimeout:
errorMessage = '接收超时,请检查网络';
break;
case DioExceptionType.badResponse:
errorMessage = '服务器响应错误 (${e.response?.statusCode})';
break;
case DioExceptionType.cancel:
errorMessage = '请求已取消';
break;
case DioExceptionType.connectionError:
if (e.error is SocketException) {
final socketError = e.error as SocketException;
if (socketError.message.contains('Failed host lookup')) {
errorMessage = 'DNS解析失败请检查域名或网络设置';
} else if (socketError.message.contains('Connection refused')) {
errorMessage = '连接被拒绝,请检查服务器状态';
} else if (socketError.message.contains('Network is unreachable')) {
errorMessage = '网络不可达,请检查网络连接';
} else {
errorMessage = '网络连接错误: ${socketError.message}';
}
} else {
errorMessage = '连接错误: ${e.message}';
}
break;
case DioExceptionType.badCertificate:
errorMessage = '证书验证失败';
break;
case DioExceptionType.unknown:
errorMessage = '未知错误: ${e.message}';
break;
}
debugPrint('网络错误: $errorMessage');
// 如果需要可以在这里抛出自定义异常或显示Toast
// throw NetworkException(errorMessage);
}
}