You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
6.9 KiB
213 lines
6.9 KiB
import 'dart:io'; |
|
import 'package:dio/dio.dart'; |
|
import 'package:flutter/material.dart'; |
|
import 'package:get/get.dart'; |
|
import 'package:path_provider/path_provider.dart'; |
|
import 'package:open_file/open_file.dart'; |
|
import 'package:package_info_plus/package_info_plus.dart'; |
|
import 'package:permission_handler/permission_handler.dart'; |
|
|
|
class UpgraderService extends GetxService { |
|
final Dio _dio = Dio(); |
|
final String _versionUrl = |
|
'http://xhota.anxincloud.cn/problem/version.json'; // 您的版本信息 JSON 地址 |
|
|
|
var isUpdateAvailable = false.obs; |
|
var updateInfo = {}.obs; |
|
var downloadProgress = '0'.obs; |
|
var totalSize = '0'.obs; |
|
var isDownloading = false.obs; |
|
|
|
@override |
|
void onInit() { |
|
super.onInit(); |
|
checkForUpdate(); |
|
} |
|
|
|
// 检查更新 |
|
Future<void> checkForUpdate() async { |
|
try { |
|
// 获取当前应用版本信息 |
|
PackageInfo packageInfo = await PackageInfo.fromPlatform(); |
|
String currentVersion = packageInfo.version; |
|
|
|
final response = await _dio.get(_versionUrl); |
|
if (response.statusCode == 200) { |
|
final latestVersion = response.data['version']; |
|
if (latestVersion.compareTo(currentVersion) > 0) { |
|
isUpdateAvailable.value = true; |
|
updateInfo.value = response.data; |
|
showUpdateDialog(); |
|
} |
|
} |
|
} catch (e) { |
|
Get.log('检查更新失败: $e'); |
|
} |
|
} |
|
|
|
// 显示更新提示对话框 |
|
void showUpdateDialog() { |
|
Get.dialog( |
|
AlertDialog( |
|
title: Text('发现新版本'), |
|
content: Text(updateInfo['description'] ?? '有新的更新可用。'), |
|
actions: [ |
|
TextButton(onPressed: () => Get.back(), child: Text('稍后')), |
|
TextButton( |
|
onPressed: () { |
|
Get.back(); |
|
startDownload(); |
|
}, |
|
child: Text('立即更新'), |
|
), |
|
], |
|
), |
|
barrierDismissible: false, |
|
); |
|
} |
|
|
|
// 开始下载 |
|
Future<void> startDownload() async { |
|
isDownloading.value = true; |
|
showDownloadProgressDialog(); |
|
|
|
try { |
|
Directory? dir = await getExternalStorageDirectory(); |
|
|
|
if (dir == null) { |
|
Get.back(); // 关闭进度对话框 |
|
Get.snackbar('错误', '无法获取外部存储目录。'); |
|
print('错误: getExternalStorageDirectory() 返回 null'); |
|
isDownloading.value = false; |
|
return; // 提前退出 |
|
} |
|
|
|
String savePath = '${dir.path}/app-release.apk'; |
|
print('--- 调试信息 ---'); |
|
print('目标下载路径: $savePath'); |
|
print('--- 调试信息 ---'); |
|
|
|
// 确保父目录存在 |
|
final file = File(savePath); |
|
if (!await file.parent.exists()) { |
|
await file.parent.create(recursive: true); |
|
print('--- 调试信息 ---'); |
|
print('已尝试创建父目录: ${file.parent.path}'); |
|
print('--- 调试信息 ---'); |
|
} |
|
|
|
await _dio.download( |
|
updateInfo['url'], |
|
savePath, |
|
onReceiveProgress: (received, total) { |
|
if (total != -1) { |
|
downloadProgress.value = (received / 1024 / 1024).toStringAsFixed( |
|
2, |
|
); |
|
totalSize.value = (total / 1024 / 1024).toStringAsFixed(2); |
|
// 在进度回调中也打印路径,确保一直有效 |
|
// print('下载进度 - 目标路径: $savePath'); |
|
} |
|
}, |
|
options: Options( |
|
receiveTimeout: const Duration(seconds: 30), // 增加超时时间 |
|
sendTimeout: const Duration(seconds: 30), |
|
), |
|
); |
|
|
|
isDownloading.value = false; |
|
Get.back(); // 关闭下载进度对话框 |
|
|
|
// 再次验证文件是否存在 |
|
if (await file.exists()) { |
|
final fileSize = await file.length(); |
|
Get.log('文件验证成功!路径: $savePath'); |
|
Get.log('文件大小: ${(fileSize / 1024 / 1024).toStringAsFixed(2)} MB'); |
|
installApk(savePath); |
|
} else { |
|
Get.snackbar('下载失败', '文件下载完毕但未找到,请联系管理员。'); |
|
Get.log('错误:文件下载完毕但未找到!'); |
|
} |
|
} on DioException catch (e) { |
|
// 使用 DioException 捕获更具体的 Dio 错误 |
|
isDownloading.value = false; |
|
Get.back(); |
|
String errorMessage = '下载失败: 网络或服务器问题。'; |
|
if (e.type == DioExceptionType.badResponse) { |
|
errorMessage = '下载失败: 服务器响应错误 (${e.response?.statusCode})'; |
|
Get.log('Dio响应错误: ${e.response?.data}'); |
|
} else if (e.type == DioExceptionType.connectionError) { |
|
errorMessage = '下载失败: 连接错误,请检查网络。'; |
|
} else if (e.type == DioExceptionType.unknown) { |
|
// 可能是文件系统错误、权限错误等 |
|
errorMessage = '下载失败: 未知错误。请检查存储权限或设备空间。'; |
|
Get.log('Dio未知错误: $e'); |
|
} |
|
Get.snackbar('下载失败', errorMessage); |
|
Get.log('下载失败 (DioException): $e'); |
|
} catch (e) { |
|
isDownloading.value = false; |
|
Get.back(); |
|
Get.snackbar('下载失败', '发生未知错误。'); |
|
Get.log('下载失败 (通用异常): $e'); |
|
} |
|
} |
|
|
|
// 显示下载进度对话框 |
|
void showDownloadProgressDialog() { |
|
Get.dialog( |
|
AlertDialog( |
|
title: Text('正在下载更新...'), |
|
content: Obx( |
|
() => Column( |
|
mainAxisSize: MainAxisSize.min, |
|
children: [ |
|
LinearProgressIndicator( |
|
value: totalSize.value == '0' |
|
? null |
|
: double.parse(downloadProgress.value) / |
|
double.parse(totalSize.value), |
|
), |
|
SizedBox(height: 16), |
|
Text('${downloadProgress.value} MB / ${totalSize.value} MB'), |
|
], |
|
), |
|
), |
|
), |
|
barrierDismissible: false, |
|
); |
|
} |
|
|
|
// 安装 APK |
|
Future<void> installApk(String path) async { |
|
// 检查安装权限 |
|
var status = await Permission.requestInstallPackages.status; |
|
if (status.isGranted) { |
|
// 权限已授予,直接安装 |
|
_openApkFile(path); |
|
} else { |
|
// 权限未授予,发起请求 |
|
// 这会打开系统设置页面,让用户手动授权 |
|
status = await Permission.requestInstallPackages.request(); |
|
if (status.isGranted) { |
|
_openApkFile(path); |
|
} else { |
|
// 如果用户拒绝了权限,给出提示 |
|
Get.snackbar( |
|
'权限被拒绝', |
|
'需要授权才能安装更新。请在系统设置中为本应用开启“安装未知应用”的权限。', |
|
duration: Duration(seconds: 5), |
|
); |
|
} |
|
} |
|
} |
|
|
|
// 抽离出打开文件的逻辑 |
|
Future<void> _openApkFile(String path) async { |
|
final result = await OpenFile.open(path); |
|
print('OpenFile result: ${result.type}, message: ${result.message}'); |
|
if (result.type != ResultType.done) { |
|
Get.snackbar('安装失败', '无法启动安装程序: ${result.message}'); |
|
} |
|
} |
|
}
|
|
|