初始化
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../debug_log.dart';
|
||||
|
||||
class ImageCacheDisk {
|
||||
static Future<Uint8List?> get(String path) async {
|
||||
var pathUrl = Uri.tryParse(path);
|
||||
String fileName = "";
|
||||
if (pathUrl?.path != null) {
|
||||
fileName = pathUrl?.path.replaceAll("/", "") ?? "";
|
||||
}
|
||||
if (fileName.isNotEmpty) {
|
||||
String filePath = "${await findSavePath()}/$fileName";
|
||||
var imageFile = File(filePath);
|
||||
if (imageFile.existsSync()) {
|
||||
return imageFile.readAsBytes();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static void save(String path, List<int> fileData) async {
|
||||
try {
|
||||
if (fileData.length < 2048) {
|
||||
return;
|
||||
}
|
||||
var pathUrl = Uri.tryParse(path);
|
||||
String fileName = "";
|
||||
if (pathUrl?.path != null) {
|
||||
fileName = pathUrl?.path.replaceAll("/", "") ?? "";
|
||||
}
|
||||
if (fileName.isNotEmpty) {
|
||||
String filePath = "${await findSavePath()}/$fileName";
|
||||
var imageFile = File(filePath);
|
||||
if (imageFile.existsSync()) {
|
||||
imageFile.deleteSync();
|
||||
}
|
||||
imageFile.writeAsBytes(fileData, mode: FileMode.write);
|
||||
}
|
||||
} catch (e) {
|
||||
debugLog("图片存储失败");
|
||||
debugLog(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> findSavePath() async {
|
||||
final directory = Platform.isAndroid ? await getTemporaryDirectory() : await getTemporaryDirectory();
|
||||
|
||||
String saveDir = '${directory.path}/cacheImage';
|
||||
Directory root = Directory(saveDir);
|
||||
if (!root.existsSync()) {
|
||||
debugLog(saveDir);
|
||||
await root.create();
|
||||
}
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
static Future emptyCache() async {
|
||||
try {
|
||||
String saveDir = await findSavePath();
|
||||
Directory root = Directory(saveDir);
|
||||
|
||||
if (root.existsSync()) {
|
||||
await root.delete(recursive: true);
|
||||
|
||||
//showToast( "磁盘图片缓存清理成功");
|
||||
// showToast( "磁盘图片缓存清理成功");
|
||||
}
|
||||
} catch (e) {
|
||||
debugLog(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// ignore_for_file: unrelated_type_equality_checks, constant_identifier_names
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
|
||||
import '../../debug_log.dart';
|
||||
import 'image_cache_disk.dart';
|
||||
|
||||
class ImageCrypto {
|
||||
static Future<Uint8List?> loadAndDecrypt(String path) async {
|
||||
BaseOptions options = BaseOptions(
|
||||
connectTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 60),
|
||||
responseType: ResponseType.bytes,
|
||||
);
|
||||
var url = path;
|
||||
Response response;
|
||||
try {
|
||||
Uint8List? imageBytes = await ImageCacheDisk.get(url);
|
||||
if (imageBytes == null) {
|
||||
response = await Dio(options).get(url);
|
||||
imageBytes = Uint8List.fromList(response.data);
|
||||
ImageCacheDisk.save(url, response.data);
|
||||
}
|
||||
var decodeData = decryptImage(imageBytes);
|
||||
if (!(url.contains('.gif') || url.contains('.GIF'))) {
|
||||
decodeData = await compressList(decodeData);
|
||||
}
|
||||
return decodeData;
|
||||
} on DioException catch (error) {
|
||||
debugLog("图片加载失败:$error");
|
||||
if (error.type == DioException.receiveTimeout) {
|
||||
try {
|
||||
response = await Dio(options).get(url);
|
||||
var imageBytes = Uint8List.fromList(response.data);
|
||||
ImageCacheDisk.save(url, response.data);
|
||||
var decodeData = decryptImage(imageBytes);
|
||||
return decodeData;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// debugLog("dio image error url: $url -> $e");
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static final List<Uint8List> _featuresList = [
|
||||
Uint8List.fromList([0xff, 0xd8, 0xff]), //jpg,jpeg
|
||||
Uint8List.fromList([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), //png
|
||||
Uint8List.fromList([0x47, 0x49, 0x46]), //gif
|
||||
];
|
||||
|
||||
/// 加密魔数头
|
||||
static const encryptMagicNumber = [0x88, 0xA8, 0x30, 0xCB, 0x10, 0x76];
|
||||
|
||||
/// 加密密钥
|
||||
static const ENCRYPT_KEY = 0xA3;
|
||||
static const int _encryptedLen = 100; // //加密图片的数据长度
|
||||
static final Uint8List _decryptKey = Uint8List.fromList('2019ysapp7527'.codeUnits); //加密key
|
||||
|
||||
static Uint8List? decryptImage(Uint8List? imgBytes) {
|
||||
if (imgBytes?.isNotEmpty != true) {
|
||||
return imgBytes;
|
||||
}
|
||||
var isAll = false;
|
||||
for (var i = 0; i < encryptMagicNumber.length; i++) {
|
||||
if (encryptMagicNumber[i] != imgBytes![i]) {
|
||||
continue;
|
||||
}
|
||||
isAll = true;
|
||||
}
|
||||
// if (isAll) {
|
||||
// imgBytes = xorBaseAllLength(imgBytes!);
|
||||
// } else {
|
||||
if (_isEncryptedImage(imgBytes!)) {
|
||||
imgBytes = xorBaseLength(imgBytes, _decryptKey, _encryptedLen);
|
||||
}
|
||||
// }
|
||||
return imgBytes;
|
||||
}
|
||||
|
||||
static bool _isEncryptedImage(Uint8List imgBytes) {
|
||||
bool isDecrypted = false;
|
||||
int featuresLen = _featuresList.length;
|
||||
for (int i = 0; i < featuresLen; i++) {
|
||||
isDecrypted = false;
|
||||
for (int j = 0; j < _featuresList[i].length; j++) {
|
||||
if (_featuresList[i][j] != imgBytes[j]) {
|
||||
isDecrypted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isDecrypted) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isDecrypted;
|
||||
}
|
||||
|
||||
static Uint8List xorBaseAllLength(Uint8List src) {
|
||||
var index = -1;
|
||||
int maxInt = double.maxFinite.toInt();
|
||||
var dest = Uint8List.fromList(
|
||||
src
|
||||
.map((it) {
|
||||
index++;
|
||||
if (index < encryptMagicNumber.length && it == encryptMagicNumber[index]) {
|
||||
return maxInt;
|
||||
}
|
||||
return it ^ ENCRYPT_KEY;
|
||||
})
|
||||
.where((element) => element != maxInt)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
static Uint8List xorBaseLength(Uint8List src, Uint8List key, int length) {
|
||||
int srcLen = src.length;
|
||||
int keyLen = key.length;
|
||||
if (length > srcLen || length <= 0) {
|
||||
length = srcLen;
|
||||
}
|
||||
for (var i = 0; i < length; i += keyLen) {
|
||||
for (var j = 0; j < keyLen && i + j < length; j++) {
|
||||
src[i + j] ^= key[j];
|
||||
}
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
static Uint8List xor(Uint8List src, Uint8List key) {
|
||||
return xorBaseLength(src, key, src.length);
|
||||
}
|
||||
|
||||
static Future<Uint8List?> compressList(Uint8List? list) async {
|
||||
if (list?.isNotEmpty == true) {
|
||||
final result = await FlutterImageCompress.compressWithList(
|
||||
list!,
|
||||
quality: 92,
|
||||
format: CompressFormat.webp,
|
||||
);
|
||||
//debugPrint("压缩前大小-----${list.length}");
|
||||
//debugPrint("压缩后大小-----${result.length}");
|
||||
return result;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import '../../debug_log.dart';
|
||||
import 'image_crypto.dart';
|
||||
|
||||
typedef ImageCallback = void Function(String, Uint8List?);
|
||||
|
||||
class ImageManager {
|
||||
// 工厂模式
|
||||
factory ImageManager() => _getInstance();
|
||||
|
||||
static ImageManager get instance => _getInstance();
|
||||
static ImageManager? _instance;
|
||||
|
||||
static int _taskCount = 0;
|
||||
static final List<String> _taskQueue = [];
|
||||
static final _taskCallbackMap = <String, List<ImageCallback>>{};
|
||||
static final List<String> _taskNetOpQueue = [];
|
||||
static int get maxTaskCount => 10;
|
||||
|
||||
int maxCacheSize = 3000;
|
||||
|
||||
ImageManager._internal();
|
||||
|
||||
static ImageManager _getInstance() {
|
||||
_instance ??= ImageManager._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
// 正在加载中的图片队列
|
||||
//final Map<String, dynamic> _pendingImages = {};
|
||||
// 缓存队列
|
||||
final Map<String, Uint8List?> _cache = {};
|
||||
final List<String> _cacheKey = [];
|
||||
|
||||
// 缓存数量上限(1000)
|
||||
// final int _maximumSize = _kDefaultSize;
|
||||
// 缓存容量上限 (100 MB)
|
||||
|
||||
// 清除所有缓存
|
||||
void clearBySize({required int maxSize}) {
|
||||
if (_cacheKey.length > maxSize) {
|
||||
List<String> removeKey = [];
|
||||
for (int i = 0; i < _cacheKey.length - maxSize; i++) {
|
||||
removeKey.add(_cacheKey[i]);
|
||||
}
|
||||
for (int i = 0; i < removeKey.length; i++) {
|
||||
_cacheKey.remove(removeKey[i]);
|
||||
_cache.remove(removeKey[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清除指定key对应的图片缓存
|
||||
bool evict(String key) {
|
||||
if (_cache[key] != null) {
|
||||
_cache.remove(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<Uint8List?> loadImage(String? url) async {
|
||||
if (url?.isNotEmpty != true) {
|
||||
return null;
|
||||
}
|
||||
var imageBase = _cache[url];
|
||||
if (imageBase != null) {
|
||||
return imageBase;
|
||||
}
|
||||
imageBase = await ImageCrypto.loadAndDecrypt(url!);
|
||||
if (imageBase != null && imageBase.length > 2048) {
|
||||
_cache[url] = imageBase;
|
||||
_cacheKey.add(url);
|
||||
clearBySize(maxSize: maxCacheSize);
|
||||
}
|
||||
return imageBase;
|
||||
}
|
||||
|
||||
void loadImageInQueue(String url, {ImageCallback? callback}) async {
|
||||
if (url.isEmpty) {
|
||||
if (callback != null) {
|
||||
callback(url, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Uint8List? imageBase = _cache[url];
|
||||
if (imageBase != null) {
|
||||
if (callback != null) {
|
||||
callback(url, imageBase);
|
||||
}
|
||||
} else {
|
||||
if (_taskCount > maxTaskCount) {
|
||||
_taskQueue.remove(url);
|
||||
_taskQueue.add(url);
|
||||
if (callback != null) {
|
||||
if (_taskCallbackMap[url] == null) {
|
||||
_taskCallbackMap[url] = [callback];
|
||||
} else {
|
||||
_taskCallbackMap[url]?.remove(callback);
|
||||
_taskCallbackMap[url]?.add(callback);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_taskNetOpQueue.contains(url)) {
|
||||
if (_taskCallbackMap[url] == null) {
|
||||
_taskCallbackMap[url] == [callback];
|
||||
} else {
|
||||
if (callback != null) {
|
||||
_taskCallbackMap[url]?.remove(callback);
|
||||
_taskCallbackMap[url]?.add(callback);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 开始任务
|
||||
_taskCount++;
|
||||
_taskNetOpQueue.add(url);
|
||||
try {
|
||||
imageBase = await ImageCrypto.loadAndDecrypt(url);
|
||||
if (imageBase != null) {
|
||||
_cache[url] = imageBase;
|
||||
_cacheKey.add(url);
|
||||
clearBySize(maxSize: maxCacheSize);
|
||||
}
|
||||
} catch (e) {
|
||||
debugLog(e);
|
||||
}
|
||||
if (callback != null) {
|
||||
callback(url, imageBase);
|
||||
}
|
||||
_taskCallbackMap[url]?.forEach((element) {
|
||||
element(url, imageBase);
|
||||
});
|
||||
_taskQueue.remove(url);
|
||||
_taskCallbackMap.remove(url);
|
||||
_taskNetOpQueue.remove(url);
|
||||
_taskCount--;
|
||||
for (int i = _taskCount; i <= maxTaskCount; i++) {
|
||||
if (_taskQueue.isNotEmpty) {
|
||||
String taskUrl = _taskQueue.first;
|
||||
_taskQueue.remove(taskUrl);
|
||||
try {
|
||||
ImageManager.instance.loadImageInQueue(taskUrl);
|
||||
} catch (e) {
|
||||
debugLog(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user