flutter_integrated_whb/lib/services/update_service.dart

169 lines
5.5 KiB
Dart
Raw Permalink Normal View History

// update_service.dart
import 'dart:async';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
/// 安装 MethodChannel 名称(与原生保持一致)
const String _kInstallChannel = 'app.install';
/// 更新事件类型
enum UpdateState { idle, starting, downloading, completed, installing, installed, failed, canceled }
class UpdateEvent {
final UpdateState state;
final String? message; // 可选的错误/提示消息
UpdateEvent(this.state, {this.message});
}
/// 单例服务类:管理下载、进度与安装
class UpdateService {
UpdateService._internal();
static final UpdateService _instance = UpdateService._internal();
factory UpdateService() => _instance;
// 进度通知器0.0 ~ 1.0
final ValueNotifier<double> progress = ValueNotifier<double>(0.0);
// 状态流控制器(广播)
final StreamController<UpdateEvent> _statusController =
StreamController<UpdateEvent>.broadcast();
Stream<UpdateEvent> get statusStream => _statusController.stream;
// Dio cancel token 用于取消下载
CancelToken? _cancelToken;
final MethodChannel _channel = const MethodChannel(_kInstallChannel);
/// 启动下载并在下载完成后尝试安装
/// apkUrl: 下载地址
/// apkFileName: 保存的文件名(可选)
Future<void> downloadAndInstall({
required String apkUrl,
String? apkFileName,
}) async {
// 防止重复下载
if (_cancelToken != null && !_cancelToken!.isCancelled) {
_statusController.add(UpdateEvent(UpdateState.failed, message: '已有下载进行中'));
return;
}
_statusController.add(UpdateEvent(UpdateState.starting));
progress.value = 0.0;
_cancelToken = CancelToken();
// 准备保存路径app 专属外部目录)
String savePath;
try {
final extDir = await getExternalStorageDirectory();
if (extDir == null) {
_statusController.add(UpdateEvent(UpdateState.failed, message: '无法获取存储目录'));
return;
}
apkFileName ??= 'app_update_${DateTime.now().millisecondsSinceEpoch}.apk';
savePath = '${extDir.path}/$apkFileName';
} catch (e) {
_statusController.add(UpdateEvent(UpdateState.failed, message: '获取存储路径失败: $e'));
return;
}
final dio = Dio();
try {
_statusController.add(UpdateEvent(UpdateState.downloading));
await dio.download(
apkUrl,
savePath,
cancelToken: _cancelToken,
onReceiveProgress: (received, total) {
if (total > 0) {
final p = received / total;
progress.value = p;
}
},
options: Options(
responseType: ResponseType.stream,
followRedirects: true,
// 不设置超时(可能大文件下载)
receiveTimeout: Duration(seconds: 0),
headers: {"Accept": "application/vnd.android.package-archive"},
),
);
// 下载完成
progress.value = 1.0;
_statusController.add(UpdateEvent(UpdateState.completed));
// 延迟让 UI 显示 100%
await Future.delayed(const Duration(milliseconds: 200));
// 调用原生安装
_statusController.add(UpdateEvent(UpdateState.installing));
try {
final Map<String, dynamic> args = {'path': savePath};
await _channel.invokeMethod('installApk', args);
// 成功发起安装(系统会弹出安装界面),我们不能保证用户安装成功,但这里当做已触发安装
_statusController.add(UpdateEvent(UpdateState.installed));
} on PlatformException catch (e) {
_statusController.add(UpdateEvent(UpdateState.failed, message: '安装失败: ${e.message}'));
} catch (e) {
_statusController.add(UpdateEvent(UpdateState.failed, message: '安装异常: $e'));
}
} on DioError catch (e) {
if (CancelToken.isCancel(e)) {
progress.value = 0.0;
_statusController.add(UpdateEvent(UpdateState.canceled));
} else {
progress.value = 0.0;
_statusController.add(UpdateEvent(UpdateState.failed, message: e.message));
}
} catch (e) {
progress.value = 0.0;
_statusController.add(UpdateEvent(UpdateState.failed, message: e.toString()));
} finally {
// 清理 token
_cancelToken = null;
}
}
/// 取消当前下载(若有)
void cancelDownload() {
if (_cancelToken != null && !_cancelToken!.isCancelled) {
_cancelToken!.cancel();
}
}
/// 仅触发安装(如果你已经手动下载好了文件),传入本地 apk 路径
Future<void> installApk(String apkPath) async {
try {
_statusController.add(UpdateEvent(UpdateState.installing));
final Map<String, dynamic> args = {'path': apkPath};
await _channel.invokeMethod('installApk', args);
_statusController.add(UpdateEvent(UpdateState.installed));
} on PlatformException catch (e) {
_statusController.add(UpdateEvent(UpdateState.failed, message: '安装失败: ${e.message}'));
} catch (e) {
_statusController.add(UpdateEvent(UpdateState.failed, message: '安装异常: $e'));
}
}
/// 释放资源(页面销毁或 app 退出时调用)
Future<void> dispose() async {
try {
_statusController.add(UpdateEvent(UpdateState.idle));
await _statusController.close();
} catch (_) {}
try {
progress.dispose();
} catch (_) {}
}
}