288 lines
8.8 KiB
Dart
288 lines
8.8 KiB
Dart
import 'package:dio/dio.dart';
|
||
import 'package:package_info_plus/package_info_plus.dart';
|
||
import 'package:open_file/open_file.dart';
|
||
import 'package:path_provider/path_provider.dart';
|
||
import 'dart:io';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:permission_handler/permission_handler.dart';
|
||
import 'package:device_info_plus/device_info_plus.dart';
|
||
|
||
import '../apis/app.dart';
|
||
|
||
// 版本信息模型
|
||
class VersionInfo {
|
||
final String version;
|
||
final String downloadUrl;
|
||
final String description;
|
||
final bool forceUpdate;
|
||
final int versionCode;
|
||
final String? platform;
|
||
final int? fileSize;
|
||
final String? md5;
|
||
final String? minVersion;
|
||
final String? createdAt;
|
||
final String? updatedAt;
|
||
final int? status;
|
||
|
||
VersionInfo({
|
||
required this.version,
|
||
required this.downloadUrl,
|
||
required this.description,
|
||
required this.forceUpdate,
|
||
required this.versionCode,
|
||
this.platform,
|
||
this.fileSize,
|
||
this.md5,
|
||
this.minVersion,
|
||
this.createdAt,
|
||
this.updatedAt,
|
||
this.status,
|
||
});
|
||
|
||
factory VersionInfo.fromJson(Map<String, dynamic> json) {
|
||
return VersionInfo(
|
||
version: json['version'] ?? '',
|
||
downloadUrl: json['downloadUrl'] ?? '',
|
||
description: json['description'] ?? '',
|
||
forceUpdate: json['forceUpdate'] == 1,
|
||
versionCode: json['versionCode'] ?? 0,
|
||
platform: json['platform'],
|
||
fileSize: json['fileSize'],
|
||
md5: json['md5'],
|
||
minVersion: json['minVersion'],
|
||
createdAt: json['createdAt'],
|
||
updatedAt: json['updatedAt'],
|
||
status: json['status'],
|
||
);
|
||
}
|
||
}
|
||
|
||
class UpdateService {
|
||
final Dio _dio = Dio();
|
||
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
|
||
|
||
// 检查版本更新
|
||
Future<VersionInfo?> checkUpdate() async {
|
||
try {
|
||
// 获取当前应用版本信息
|
||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||
print('当前应用版本信息: ${packageInfo.version}');
|
||
String currentVersion = packageInfo.version;
|
||
String buildNumber = packageInfo.buildNumber;
|
||
|
||
// 调用后端API检查版本
|
||
final response = await userApi.getVersionUpdate(currentVersion);
|
||
|
||
if (response['code'] == 200) {
|
||
final data = response['data'];
|
||
// 转换接口数据为 VersionInfo 对象
|
||
final versionInfo = VersionInfo(
|
||
version: data['version'] ?? '',
|
||
downloadUrl: data['downloadUrl'] ?? '',
|
||
description: data['description'] ?? '',
|
||
forceUpdate: data['forceUpdate'] == 1, // 转换 0/1 为 boolean
|
||
versionCode: data['versionCode'] ?? 0,
|
||
fileSize: data['fileSize'],
|
||
platform: data['platform'],
|
||
md5: data['md5'],
|
||
minVersion: data['minVersion'],
|
||
createdAt: data['createdAt'],
|
||
updatedAt: data['updatedAt'],
|
||
status: data['status'],
|
||
);
|
||
|
||
// 比较版本号,判断是否需要更新
|
||
if (isNeedUpdate(currentVersion, versionInfo.version)) {
|
||
return versionInfo;
|
||
}
|
||
}
|
||
return null;
|
||
} catch (e) {
|
||
print('检查更新失败: $e');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
Future<PackageInfo> getLatestPackageInfo() async {
|
||
// 使用系统API尝试刷新包信息
|
||
try {
|
||
// 使用延迟和多次读取来确保获取最新信息
|
||
await Future.delayed(const Duration(milliseconds: 300));
|
||
final info1 = await PackageInfo.fromPlatform();
|
||
|
||
await Future.delayed(const Duration(milliseconds: 200));
|
||
final info2 = await PackageInfo.fromPlatform();
|
||
|
||
// 比较两次读取的结果,如果不同则取最新的一个(版本号更大的)
|
||
if (info1.version != info2.version) {
|
||
if (isNeedUpdate(info1.version, info2.version)) {
|
||
return info2;
|
||
} else {
|
||
return info1;
|
||
}
|
||
}
|
||
|
||
return info2; // 返回最后读取的结果
|
||
} catch (e) {
|
||
print('获取最新包信息失败: $e');
|
||
return PackageInfo.fromPlatform();
|
||
}
|
||
}
|
||
|
||
// 检查和请求安装权限
|
||
Future<bool> _checkInstallPermission() async {
|
||
// 仅在Android平台需要检查
|
||
if (!Platform.isAndroid) return true;
|
||
|
||
// 获取Android版本
|
||
final androidInfo = await _deviceInfo.androidInfo;
|
||
// Android 8.0 (API 26) 及以上版本需要请求权限
|
||
if (androidInfo.version.sdkInt >= 26) {
|
||
if (await Permission.requestInstallPackages.status != PermissionStatus.granted) {
|
||
// 请求权限
|
||
final status = await Permission.requestInstallPackages.request();
|
||
return status == PermissionStatus.granted;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 8.0以下版本不需要动态请求该权限
|
||
return true;
|
||
}
|
||
|
||
// 下载并安装更新
|
||
Future<(bool, String)> downloadAndInstall(String url, Function(double) onProgress) async {
|
||
try {
|
||
// 先检查安装权限
|
||
final hasPermission = await _checkInstallPermission();
|
||
if (!hasPermission) {
|
||
debugPrint('没有安装未知来源应用的权限');
|
||
return (false, '请授予安装权限后重试');
|
||
}
|
||
|
||
// 验证URL
|
||
String downloadUrl = url;
|
||
if (downloadUrl.isEmpty) {
|
||
debugPrint('下载地址为空');
|
||
return (false, '下载地址无效');
|
||
}
|
||
|
||
// 处理URL前缀问题,确保是完整的URL
|
||
if (!downloadUrl.startsWith('http://') && !downloadUrl.startsWith('https://')) {
|
||
downloadUrl = 'http://$downloadUrl';
|
||
}
|
||
|
||
debugPrint('开始下载APK: $downloadUrl');
|
||
|
||
// 使用普通下载方式
|
||
return await _fallbackDownload(downloadUrl, onProgress);
|
||
} catch (e) {
|
||
debugPrint('下载或安装过程中发生异常: $e');
|
||
return (false, '更新过程中出错: ${e.toString()}');
|
||
}
|
||
}
|
||
|
||
// 普通下载方式
|
||
Future<(bool, String)> _fallbackDownload(String url, Function(double) onProgress) async {
|
||
try {
|
||
// 获取应用目录
|
||
final dir = await getApplicationDocumentsDirectory();
|
||
final savePath = '${dir.path}/app-update.apk';
|
||
|
||
// 检查文件是否已存在,如果存在则删除
|
||
final file = File(savePath);
|
||
if (await file.exists()) {
|
||
await file.delete();
|
||
debugPrint('删除旧的APK文件');
|
||
}
|
||
|
||
// 下载文件
|
||
await _dio.download(
|
||
url,
|
||
savePath,
|
||
onReceiveProgress: (received, total) {
|
||
if (total != -1) {
|
||
final progress = received / total;
|
||
onProgress(progress);
|
||
}
|
||
},
|
||
options: Options(
|
||
receiveTimeout: const Duration(minutes: 5),
|
||
sendTimeout: const Duration(minutes: 5),
|
||
followRedirects: true,
|
||
validateStatus: (status) {
|
||
return status! < 500;
|
||
}
|
||
),
|
||
);
|
||
|
||
debugPrint('APK下载完成: $savePath');
|
||
|
||
// 验证文件是否存在
|
||
if (!await File(savePath).exists()) {
|
||
debugPrint('下载完成但文件不存在');
|
||
return (false, '下载文件不存在');
|
||
}
|
||
|
||
// 检查文件大小
|
||
final fileSize = await File(savePath).length();
|
||
if (fileSize < 1000) { // 如果文件小于1KB,很可能是错误的
|
||
debugPrint('下载的文件过小,可能是错误内容: $fileSize 字节');
|
||
return (false, '下载的文件无效');
|
||
}
|
||
|
||
// 安装APK(仅Android)
|
||
if (Platform.isAndroid) {
|
||
try {
|
||
debugPrint('开始安装APK');
|
||
final result = await OpenFile.open(savePath);
|
||
debugPrint('安装结果: ${result.type}, ${result.message}');
|
||
|
||
if (result.type != ResultType.done) {
|
||
return (false, '安装失败: ${result.message}');
|
||
}
|
||
|
||
return (true, '安装包已打开');
|
||
} catch (e) {
|
||
debugPrint('打开安装包时出错: $e');
|
||
return (false, '打开安装包失败: ${e.toString()}');
|
||
}
|
||
} else {
|
||
// iOS跳转App Store
|
||
return (false, '请在App Store中更新应用');
|
||
}
|
||
} catch (e) {
|
||
debugPrint('下载失败: $e');
|
||
return (false, '下载失败: ${e.toString()}');
|
||
}
|
||
}
|
||
|
||
// 比较版本号
|
||
bool isNeedUpdate(String currentVersion, String serverVersion) {
|
||
try {
|
||
List<int> current = currentVersion.split('.')
|
||
.map((e) => int.parse(e.trim()))
|
||
.toList();
|
||
List<int> server = serverVersion.split('.')
|
||
.map((e) => int.parse(e.trim()))
|
||
.toList();
|
||
|
||
// 确保两个列表长度相同
|
||
while (current.length < server.length) {
|
||
current.add(0);
|
||
}
|
||
while (server.length < current.length) {
|
||
server.add(0);
|
||
}
|
||
|
||
for (int i = 0; i < current.length; i++) {
|
||
if (server[i] > current[i]) return true;
|
||
if (server[i] < current[i]) return false;
|
||
}
|
||
return false;
|
||
} catch (e) {
|
||
print('版本号比较错误: $e');
|
||
return false;
|
||
}
|
||
}
|
||
} |