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

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}');
}
}
}