feat(cache): implement LRUCache and AsyncLRUCache for efficient data management

This commit is contained in:
ZhuJHua
2025-02-04 04:13:36 +08:00
parent b5be93fa56
commit 5154db3269
5 changed files with 112 additions and 92 deletions

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:moodiary/components/base/marquee.dart';
import 'package:moodiary/utils/lru.dart';
class AdaptiveText extends StatelessWidget {
final String text;
@@ -82,6 +83,8 @@ class EllipsisText extends StatelessWidget {
final String ellipsis;
final int? maxLines;
static final _cache = LRUCache<String, String>(maxSize: 1000);
const EllipsisText(
this.text, {
super.key,
@@ -90,10 +93,7 @@ class EllipsisText extends StatelessWidget {
this.maxLines,
});
double _calculateTextWidth(
String text,
TextScaler textScaler,
) {
double _calculateTextWidth(String text, TextScaler textScaler) {
final span = TextSpan(text: text.fixAutoLines(), style: style);
final tp = TextPainter(
text: span,
@@ -120,15 +120,31 @@ class EllipsisText extends StatelessWidget {
@override
Widget build(BuildContext context) {
final textScaler = MediaQuery.textScalerOf(context);
return LayoutBuilder(builder: (context, constraints) {
if (text.isEmpty) {
return const SizedBox.shrink();
}
final cacheKey =
'${text.hashCode}_${ellipsis}_${constraints.maxWidth}_$maxLines';
final cachedResult = _cache.get(cacheKey);
if (cachedResult != null) {
return Text(
cachedResult,
maxLines: maxLines,
overflow: TextOverflow.ellipsis,
style: style,
textScaler: textScaler,
);
}
if (!_createTextPainter(
text,
constraints.maxWidth,
textScaler,
).didExceedMaxLines) {
_cache.put(cacheKey, text.fixAutoLines());
return Text(
text.fixAutoLines(),
maxLines: maxLines,
@@ -143,8 +159,6 @@ class EllipsisText extends StatelessWidget {
double leftWidth = 0;
double rightWidth = 0;
String truncatedText = text;
int lastValidLeftIndex = 0;
int lastValidRightIndex = text.characters.length;
@@ -157,6 +171,7 @@ class EllipsisText extends StatelessWidget {
rightWidth;
final currentText =
'${text.runeSubstring(0, leftIndex)}$ellipsis${text.runeSubstring(rightIndex)}';
if (_createTextPainter(
currentText,
constraints.maxWidth,
@@ -176,10 +191,10 @@ class EllipsisText extends StatelessWidget {
}
}
final leftText = text.runeSubstring(0, lastValidLeftIndex);
final rightText = text.runeSubstring(lastValidRightIndex);
final truncatedText =
'${text.runeSubstring(0, lastValidLeftIndex)}$ellipsis${text.runeSubstring(lastValidRightIndex)}';
truncatedText = '$leftText$ellipsis$rightText';
_cache.put(cacheKey, truncatedText.fixAutoLines());
return Text(
truncatedText.fixAutoLines(),

72
lib/utils/lru.dart Normal file
View File

@@ -0,0 +1,72 @@
import 'dart:collection';
import 'package:synchronized/synchronized.dart';
class LRUCache<K, V> {
final int maxSize;
final LinkedHashMap<K, V> _map;
LRUCache({this.maxSize = 1000}) : _map = LinkedHashMap<K, V>();
V? get(K key) {
if (!_map.containsKey(key)) return null;
final value = _map.remove(key) as V;
_map[key] = value;
return value;
}
void put(K key, V value) {
if (_map.containsKey(key)) {
_map.remove(key);
} else if (_map.length >= maxSize) {
_map.remove(_map.keys.first);
}
_map[key] = value;
}
void clear() => _map.clear();
int size() => _map.length;
@override
String toString() => _map.toString();
}
class AsyncLRUCache<K, V> {
final int maxSize;
final LinkedHashMap<K, V> _map = LinkedHashMap<K, V>();
final Lock _lock = Lock();
AsyncLRUCache({this.maxSize = 1000});
Future<V?> get(K key) async {
return await _lock.synchronized(() async {
if (!_map.containsKey(key)) return null;
final value = _map.remove(key) as V;
_map[key] = value;
return value;
});
}
Future<void> put(K key, V value) async {
await _lock.synchronized(() async {
if (_map.containsKey(key)) {
_map.remove(key);
} else if (_map.length >= maxSize) {
_map.remove(_map.keys.first);
}
_map[key] = value;
});
}
Future<void> clear() async {
await _lock.synchronized(() => _map.clear());
}
Future<int> size() async {
return await _lock.synchronized(() => _map.length);
}
@override
String toString() => _map.toString();
}

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart';
@@ -16,6 +15,7 @@ import 'package:moodiary/src/rust/api/compress.dart';
import 'package:moodiary/src/rust/api/constants.dart' as r_type;
import 'package:moodiary/utils/file_util.dart';
import 'package:moodiary/utils/log_util.dart';
import 'package:moodiary/utils/lru.dart';
import 'package:moodiary/utils/notice_util.dart';
import 'package:path/path.dart';
import 'package:uuid/uuid.dart';
@@ -25,6 +25,9 @@ class MediaUtil {
static final _thumbnail = FcNativeVideoThumbnail();
static final _imageAspectRatioCache =
AsyncLRUCache<String, double>(maxSize: 1000);
static void useAndroidImagePicker() {
final ImagePickerPlatform imagePickerImplementation =
ImagePickerPlatform.instance;
@@ -186,36 +189,25 @@ class MediaUtil {
return completer.future;
}
static const int _maxCacheSize = 1000;
static final LinkedHashMap<String, double> _cache = LinkedHashMap();
static Future<double> getImageAspectRatio(ImageProvider imageProvider) async {
final key = _getImageKey(imageProvider);
if (_cache.containsKey(key)) {
return _cache[key]!;
final cachedAspectRatio = await _imageAspectRatioCache.get(key);
if (cachedAspectRatio != null) {
return cachedAspectRatio;
}
final completer = Completer<double>();
final imageStream = imageProvider.resolve(const ImageConfiguration());
imageStream.addListener(
ImageStreamListener((ImageInfo info, bool _) {
ImageStreamListener((ImageInfo info, bool _) async {
final aspectRatio = info.image.width / info.image.height;
_addToCache(key, aspectRatio);
await _imageAspectRatioCache.put(key, aspectRatio);
completer.complete(aspectRatio);
}),
);
return completer.future;
}
static void _addToCache(String key, double ratio) {
if (_cache.length >= _maxCacheSize) {
_cache.remove(_cache.keys.first);
}
_cache[key] = ratio;
}
static String _getImageKey(ImageProvider imageProvider) {
if (imageProvider is AssetImage) {
return 'asset_${imageProvider.assetName}';

View File

@@ -478,14 +478,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.2"
csv:
dependency: transitive
description:
name: csv
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
url: "https://pub.dev"
source: hosted
version: "6.0.0"
cupertino_icons:
dependency: "direct main"
description:
@@ -1330,14 +1322,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json2yaml:
dependency: transitive
description:
name: json2yaml
sha256: da94630fbc56079426fdd167ae58373286f603371075b69bf46d848d63ba3e51
url: "https://pub.dev"
source: hosted
version: "3.0.1"
json_annotation:
dependency: transitive
description:
@@ -1709,18 +1693,18 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4
sha256: c447a3c3e7be4addf129b8f9ab6a4bd5d166b78918223e223b61fddf4d07e254
url: "https://pub.dev"
source: hosted
version: "8.1.4"
version: "8.2.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b
sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.1.0"
path:
dependency: "direct main"
description:
@@ -2186,22 +2170,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.8"
sensors_plus:
dependency: "direct main"
description:
name: sensors_plus
sha256: "905282c917c6bb731c242f928665c2ea15445aa491249dea9d98d7c79dc8fd39"
url: "https://pub.dev"
source: hosted
version: "6.1.1"
sensors_plus_platform_interface:
dependency: transitive
description:
name: sensors_plus_platform_interface
sha256: "58815d2f5e46c0c41c40fb39375d3f127306f7742efe3b891c0b1c87e2b5cd5d"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
share_plus:
dependency: "direct main"
description:
@@ -2311,30 +2279,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
slang:
dependency: "direct main"
description:
name: slang
sha256: "2778b88f05ffc23fd0a37436f607bf2a541d0b6b922e69a8ea5bbd50c2427d18"
url: "https://pub.dev"
source: hosted
version: "4.4.1"
slang_build_runner:
dependency: "direct dev"
description:
name: slang_build_runner
sha256: "15b251e8aa591d96a16da3678abdca814a5d21486d14f98602c52b169f794d7b"
url: "https://pub.dev"
source: hosted
version: "4.4.2"
slang_flutter:
dependency: "direct main"
description:
name: slang_flutter
sha256: "819637a23348adbc4f4e8faee3f274d8908f9af31d57bf1e277cd730b14bacde"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
sliver_tools:
dependency: "direct main"
description:
@@ -2488,7 +2432,7 @@ packages:
source: hosted
version: "28.2.3"
synchronized:
dependency: transitive
dependency: "direct main"
description:
name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"

View File

@@ -27,7 +27,7 @@ dependencies:
image_picker: 1.1.2
device_info_plus: 11.2.2
photo_view: 0.15.0
package_info_plus: 8.1.4
package_info_plus: 8.2.0
uuid: 4.5.1
flutter_quill: 11.0.0-dev.21
share_plus: 10.1.4
@@ -101,16 +101,14 @@ dependencies:
sdk: flutter
moodiary_rust:
path: rust_builder
slang: 4.4.1
slang_flutter: 4.4.0
modal_bottom_sheet: 3.0.0
sheet: 1.0.0
tutorial_coach_mark: 1.2.12
adaptive_dialog: 2.4.0
flutter_inappwebview: 6.1.5
sensors_plus: 6.1.1
markdown: 7.3.0
flutter_highlight: 0.7.0
synchronized: 3.3.0+3
# //llama_cpp_dart: 0.0.8
@@ -119,7 +117,6 @@ dev_dependencies:
# sdk: flutter
build_runner: 2.4.14
flutter_launcher_icons: 0.14.3
slang_build_runner: 4.4.2
msix: 3.16.8
flutter_lints: 5.0.0