Files
huangguo_android/lib/tools_base/cache/image_cache_manager.dart
T
2026-09-15 15:44:13 +07:00

120 lines
4.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:http/http.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import '../../config/address.dart';
import '../../extension/extensions.dart';
import '../debug_log.dart';
import '../image/image_data_handle/image_crypto.dart';
import '../net/load_apk/dio_cli.dart';
class ImageCacheManager extends CacheManager {
static const image_key = "customCache";
// 工厂模式
factory ImageCacheManager() => _getInstance();
static ImageCacheManager get instance => _getInstance();
static ImageCacheManager? _instance;
static ImageCacheManager _getInstance() {
_instance ??= ImageCacheManager._internal();
return _instance!;
}
ImageCacheManager._internal()
: super(Config(image_key, maxNrOfCacheObjects: 400, stalePeriod: const Duration(days: 7), fileService: CustomFileRespons()));
Future<String> getFilePath() async {
var directory = await getTemporaryDirectory();
return path.join(directory.path, image_key);
}
}
class CustomFileRespons extends HttpFileService {
DioCli client = DioCli(
options: BaseOptions(
connectTimeout: const Duration(milliseconds: 30000),
receiveTimeout: const Duration(milliseconds: 30000),
sendTimeout: const Duration(milliseconds: 30000),
validateStatus: (int? status) {
if (status != null) return status < 600;
return false;
}));
@override
Future<FileServiceResponse> get(String url, {Map<String, String>? headers = const {}}) async {
if (!url.startsWith("http") && !url.startsWith("https")) {
url = path.join(Address.baseImagePath ?? '', url);
}
if (kDebugMode) {
debugLog("image_request", "get()...begin...ulr:$url...");
}
headers?['cache-control'] = 'max-age=31104000';
final resp = await client.getBytes(url, headers: headers);
final statusCode = resp.data?.statusCode ?? HttpStatus.badRequest;
// 失败 / 非 2xx:直接抛。避免错误响应(空内容、CDN 错误页)被落盘缓存成坏图,
// 让 CachedNetworkImage 走 errorWidget;下次能重新请求,而不是一直命中坏缓存。
if (resp.err != null || statusCode < 200 || statusCode >= 300) {
if (kDebugMode) {
debugLog("image_request", "get()...failed...$url...code:($statusCode): ${resp.err}");
}
throw HttpException("image fetch failed ($statusCode): ${resp.err}", uri: Uri.tryParse(url));
}
// 解密(Dio 不同版本 bytes 返回 List<int> 或 Uint8List)。解密失败 / 空字节同样抛出,不缓存坏图。
Uint8List bytes;
try {
final raw = resp.data?.data;
final decrypted = raw == null ? null : ImageCrypto.decryptImage(raw is Uint8List ? raw : Uint8List.fromList(raw));
if (decrypted == null || decrypted.isEmpty) {
throw const FormatException("empty image bytes");
}
//部分 CDN 返回的加密 JPEG 缺末尾 EOI(FF D9)Skia 严格 decoder 会拒;补 EOI 让标准 decoder 也能解
bytes = _repairJpegEoiIfNeeded(decrypted);
} catch (e) {
if (kDebugMode) debugLog("ImageCacheManager", "decrypt failed: $url -> $e");
throw HttpException("image decrypt failed: $e", uri: Uri.tryParse(url));
}
final respHeaders = <String, String>{};
resp.data?.headers.forEach((key, arrayValue) {
if (arrayValue.notEmpty()) respHeaders[key] = arrayValue[0];
});
if (kDebugMode) {
debugLog("image_request", "get()...success...$url...code:($statusCode): contentLength:${bytes.length}");
}
return HttpGetResponse(StreamedResponse(
Stream.value(bytes),
statusCode,
contentLength: bytes.length, // 补 EOI 后用真实长度
headers: respHeaders,
));
}
}
//判断是 jpeg 头但无 FF D9 结尾时补 EOI;其他格式 / 已完整 jpeg 原样返回
Uint8List _repairJpegEoiIfNeeded(Uint8List bytes) {
if (bytes.length < 4) return bytes;
final isJpeg = bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF;
if (!isJpeg) return bytes;
final hasEoi = bytes[bytes.length - 2] == 0xFF && bytes[bytes.length - 1] == 0xD9;
if (hasEoi) return bytes;
final fixed = Uint8List(bytes.length + 2)
..setRange(0, bytes.length, bytes)
..[bytes.length] = 0xFF
..[bytes.length + 1] = 0xD9;
return fixed;
}