startDownload method
开始或恢复下载
断点续传策略:
- 文件名包含 version+buildVersion,版本变化自动重新下载
- 检测本地文件大小,若超出预期则删除重来
- 服务端不支持 Range(返回200而非206)时,自动清理本地文件从头写入
- 大小不匹配或 HTTP 416 时自动重试
Implementation
Future<void> startDownload() async {
_ensureAndroid();
_ensureInitialized();
if (_updateInfo?.latestVersion == null) {
statusNotifier.value = DownloadStatus.error;
final error = StateError('No update is available to download.');
_emit(UpgraderEventType.downloadError, error.message);
_safeErrorCallback(error);
return;
}
if (statusNotifier.value == DownloadStatus.downloading) {
_emit(UpgraderEventType.log, '下载已在进行中,跳过重复请求');
return;
}
final session = ++_sessionSequence;
_activeDownloadSession = session;
statusNotifier.value = DownloadStatus.downloading;
progressNotifier.value = 0.0;
final latest = _updateInfo!.latestVersion!;
try {
final tempDir = await getTemporaryDirectory();
if (!_isActiveSession(session)) return;
final fileName = _buildSafeApkFileName(latest);
_savePath = '${tempDir.path}${Platform.pathSeparator}$fileName';
await _cleanOldApkFiles(tempDir, _savePath!);
if (!_isActiveSession(session)) return;
final file = File(_savePath!);
final existingLength = await file.exists() ? await file.length() : 0;
if (latest.apkSize > 0 && existingLength < latest.apkSize) {
final availableBytes = await TinyUpgraderPlatform.instance
.getAvailableStorageBytes(tempDir.path);
final requiredBytes =
(latest.apkSize - existingLength) +
latest.apkSize +
_minFreeSpaceMarginBytes;
if (availableBytes < requiredBytes) {
final error = InsufficientStorageException(
availableBytes: availableBytes,
requiredBytes: requiredBytes,
);
statusNotifier.value = DownloadStatus.error;
_emit(UpgraderEventType.downloadError, '存储空间不足,无法下载更新', {
'sessionId': session,
'availableBytes': availableBytes,
'requiredBytes': requiredBytes,
});
_safeErrorCallback(error);
return;
}
}
final headers = <String, String>{};
final downloadUrl = _resolveDownloadUrl(latest.downloadUrl);
if (_ossConfig != null && !_ossConfig!.isPublicRead) {
headers.addAll(
OssSigner.generateHeaders(
config: _ossConfig!,
downloadUrl: downloadUrl,
),
);
}
_emit(UpgraderEventType.downloadStart, '启动 Android 前台下载服务', {
'sessionId': session,
'fileName': fileName,
'downloadUrl': _redactUrl(downloadUrl),
'apkSize': latest.apkSize,
'version': latest.version,
'buildVersion': latest.buildVersion,
});
await TinyUpgraderPlatform.instance.startForegroundDownload(
ForegroundDownloadRequest(
sessionId: session,
url: downloadUrl,
savePath: _savePath!,
headers: headers,
expectedSize: latest.apkSize,
expectedHash: latest.apkHashCode,
hashAlgorithm: latest.apkHashAlgorithm.name,
maxNetworkRetryCount: _maxNetworkRetryCount,
maxValidationRetryCount: _maxValidationRetryCount,
minFreeSpaceMarginBytes: _minFreeSpaceMarginBytes,
),
);
} catch (error) {
if (!_isActiveSession(session)) return;
statusNotifier.value = DownloadStatus.error;
_emit(UpgraderEventType.downloadError, '无法启动 Android 前台下载服务', {
'sessionId': session,
'error': error.toString(),
});
_safeErrorCallback(error);
}
}