svg资源释放
This commit is contained in:
parent
829914c58c
commit
92bbbcb0eb
@ -46,6 +46,7 @@ import 'services/auth/user_profile_manager.dart';
|
||||
import 'ui_kit/theme/socialchat_theme.dart';
|
||||
import 'ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
|
||||
import 'ui_kit/widgets/room/effect/vapp_svga_layer_widget.dart';
|
||||
import 'ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
||||
|
||||
bool _isCrashlyticsReady = false;
|
||||
|
||||
@ -108,6 +109,7 @@ void _configureImageCache() {
|
||||
|
||||
void _releaseVisualMemoryCaches({bool clearLiveImages = false}) {
|
||||
SCGiftVapSvgaManager().clearMemoryCache();
|
||||
SCSvgaAssetWidget.clearMemoryCache();
|
||||
final imageCache = PaintingBinding.instance.imageCache;
|
||||
imageCache.clear();
|
||||
if (clearLiveImages) {
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_svga/flutter_svga.dart';
|
||||
@ -566,6 +564,7 @@ class _GiftWallSvgaEffectOverlay extends StatefulWidget {
|
||||
class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final SVGAAnimationController _controller;
|
||||
MovieEntity? _retainedMovieEntity;
|
||||
bool _loaded = false;
|
||||
bool _finished = false;
|
||||
|
||||
@ -585,9 +584,15 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||
|
||||
Future<void> _loadAndPlay() async {
|
||||
try {
|
||||
final movieEntity = await _loadMovieEntity(widget.sourceUrl);
|
||||
if (!mounted || _finished) return;
|
||||
final movieEntity = await SCGiftVapSvgaManager().acquireSvgaEntity(
|
||||
widget.sourceUrl,
|
||||
);
|
||||
if (!mounted || _finished) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(movieEntity);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_retainedMovieEntity = movieEntity;
|
||||
_loaded = true;
|
||||
_controller.videoItem = movieEntity;
|
||||
});
|
||||
@ -599,28 +604,6 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||
}
|
||||
}
|
||||
|
||||
Future<MovieEntity> _loadMovieEntity(String sourceUrl) async {
|
||||
final cache = SCGiftVapSvgaManager().videoItemCache;
|
||||
final cached = cache[sourceUrl];
|
||||
if (cached != null) return cached;
|
||||
|
||||
final pathType = SCPathUtils.getPathType(sourceUrl);
|
||||
late final MovieEntity movieEntity;
|
||||
if (pathType == PathType.network) {
|
||||
movieEntity = await SVGAParser.shared.decodeFromURL(sourceUrl);
|
||||
} else if (pathType == PathType.asset) {
|
||||
movieEntity = await SVGAParser.shared.decodeFromAssets(sourceUrl);
|
||||
} else if (pathType == PathType.file) {
|
||||
final bytes = await File(sourceUrl).readAsBytes();
|
||||
movieEntity = await SVGAParser.shared.decodeFromBuffer(bytes);
|
||||
} else {
|
||||
throw Exception("Unsupported SVGA source: $sourceUrl");
|
||||
}
|
||||
movieEntity.autorelease = false;
|
||||
cache[sourceUrl] = movieEntity;
|
||||
return movieEntity;
|
||||
}
|
||||
|
||||
void _finish() {
|
||||
if (_finished) return;
|
||||
_finished = true;
|
||||
@ -631,6 +614,8 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||
void dispose() {
|
||||
_controller.removeStatusListener(_handleStatusChanged);
|
||||
_controller.dispose();
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(_retainedMovieEntity);
|
||||
_retainedMovieEntity = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,8 @@ class SCGiftVapSvgaManager {
|
||||
static const int _maxConsecutiveEntryTasksBeforeGift = 2;
|
||||
static const Duration _taskWatchdogTimeout = Duration(seconds: 20);
|
||||
static const Duration _entryTaskTtl = Duration(seconds: 6);
|
||||
Map<String, MovieEntity> videoItemCache = {};
|
||||
final Map<String, MovieEntity> _videoItemCache = <String, MovieEntity>{};
|
||||
final Map<MovieEntity, int> _svgaEntityUseCounts = <MovieEntity, int>{};
|
||||
static SCGiftVapSvgaManager? _inst;
|
||||
static const int _maxPreloadConcurrency = 1;
|
||||
static const int _maxPreloadQueueLength = 12;
|
||||
@ -79,6 +80,44 @@ class SCGiftVapSvgaManager {
|
||||
return SCPathUtils.getFileExtension(path).toLowerCase() == ".svga";
|
||||
}
|
||||
|
||||
Future<MovieEntity> acquireSvgaEntity(String path) async {
|
||||
final entity = await _loadSvgaEntity(path);
|
||||
_retainSvgaEntity(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
void releaseSvgaEntity(MovieEntity? entity) {
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
final count = _svgaEntityUseCounts[entity] ?? 0;
|
||||
if (count <= 1) {
|
||||
_svgaEntityUseCounts.remove(entity);
|
||||
if (!_videoItemCache.containsValue(entity) &&
|
||||
!_isSvgaEntityInUse(entity)) {
|
||||
_disposeSvgaEntity(entity);
|
||||
} else {
|
||||
_trimSvgaCache();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_svgaEntityUseCounts[entity] = count - 1;
|
||||
}
|
||||
|
||||
void evictSvgaEntity(String path, {bool includeCurrent = false}) {
|
||||
final entity = _videoItemCache.remove(path);
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
if (includeCurrent || !_isSvgaEntityInUse(entity)) {
|
||||
_disposeSvgaEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void _retainSvgaEntity(MovieEntity entity) {
|
||||
_svgaEntityUseCounts[entity] = (_svgaEntityUseCounts[entity] ?? 0) + 1;
|
||||
}
|
||||
|
||||
static int resolveEffectPriority({
|
||||
int priority = 0,
|
||||
int type = giftEffectType,
|
||||
@ -116,7 +155,7 @@ class SCGiftVapSvgaManager {
|
||||
|
||||
bool _isPreloadedOrLoading(String path) {
|
||||
if (_needsSvgaController(path)) {
|
||||
return videoItemCache.containsKey(path) ||
|
||||
return _videoItemCache.containsKey(path) ||
|
||||
_svgaLoadTasks.containsKey(path);
|
||||
}
|
||||
final pathType = SCPathUtils.getPathType(path);
|
||||
@ -256,19 +295,19 @@ class SCGiftVapSvgaManager {
|
||||
}
|
||||
|
||||
MovieEntity? _touchCachedSvgaEntity(String path) {
|
||||
final cached = videoItemCache.remove(path);
|
||||
final cached = _videoItemCache.remove(path);
|
||||
if (cached != null) {
|
||||
videoItemCache[path] = cached;
|
||||
_videoItemCache[path] = cached;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
void _cacheSvgaEntity(String path, MovieEntity entity) {
|
||||
final previous = videoItemCache.remove(path);
|
||||
final previous = _videoItemCache.remove(path);
|
||||
if (previous != null && !identical(previous, entity)) {
|
||||
_disposeSvgaEntityIfUnused(previous);
|
||||
}
|
||||
videoItemCache[path] = entity;
|
||||
_videoItemCache[path] = entity;
|
||||
_trimSvgaCache();
|
||||
}
|
||||
|
||||
@ -287,17 +326,17 @@ class SCGiftVapSvgaManager {
|
||||
}
|
||||
|
||||
void _trimSvgaCache() {
|
||||
while (videoItemCache.length > _maxSvgaCacheEntries) {
|
||||
final removableEntry = videoItemCache.entries.firstWhere(
|
||||
while (_videoItemCache.length > _maxSvgaCacheEntries) {
|
||||
final removableEntry = _videoItemCache.entries.firstWhere(
|
||||
(entry) => !_isSvgaEntityInUse(entry.value),
|
||||
orElse: () => videoItemCache.entries.first,
|
||||
orElse: () => _videoItemCache.entries.first,
|
||||
);
|
||||
if (_isSvgaEntityInUse(removableEntry.value)) {
|
||||
videoItemCache.remove(removableEntry.key);
|
||||
videoItemCache[removableEntry.key] = removableEntry.value;
|
||||
_videoItemCache.remove(removableEntry.key);
|
||||
_videoItemCache[removableEntry.key] = removableEntry.value;
|
||||
break;
|
||||
}
|
||||
videoItemCache.remove(removableEntry.key);
|
||||
_videoItemCache.remove(removableEntry.key);
|
||||
_disposeSvgaEntity(removableEntry.value);
|
||||
}
|
||||
}
|
||||
@ -752,20 +791,21 @@ class SCGiftVapSvgaManager {
|
||||
_preloadQueue.clear();
|
||||
_queuedPreloadPaths.clear();
|
||||
_activePreloadCount = 0;
|
||||
final entries = videoItemCache.entries.toList(growable: false);
|
||||
final entries = _videoItemCache.entries.toList(growable: false);
|
||||
for (final entry in entries) {
|
||||
final entity = entry.value;
|
||||
if (!includeCurrent && _isSvgaEntityInUse(entity)) {
|
||||
continue;
|
||||
}
|
||||
videoItemCache.remove(entry.key);
|
||||
_videoItemCache.remove(entry.key);
|
||||
_disposeSvgaEntity(entity);
|
||||
}
|
||||
_playablePathCache.clear();
|
||||
}
|
||||
|
||||
bool _isSvgaEntityInUse(MovieEntity entity) {
|
||||
return identical(_rsc?.videoItem, entity);
|
||||
return identical(_rsc?.videoItem, entity) ||
|
||||
(_svgaEntityUseCounts[entity] ?? 0) > 0;
|
||||
}
|
||||
|
||||
void _disposeSvgaEntityIfUnused(MovieEntity entity) {
|
||||
@ -777,6 +817,7 @@ class SCGiftVapSvgaManager {
|
||||
|
||||
void _disposeSvgaEntity(MovieEntity entity) {
|
||||
try {
|
||||
_svgaEntityUseCounts.remove(entity);
|
||||
entity.dispose();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@ -48,6 +48,8 @@ class SVGAHeadwearWidget extends StatefulWidget {
|
||||
class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
SVGAAnimationController? _animationController;
|
||||
MovieEntity? _retainedMovieEntity;
|
||||
String? _retainedResource;
|
||||
bool _isLoading = true;
|
||||
bool _isNetworkResource = false;
|
||||
bool _hasError = false;
|
||||
@ -82,41 +84,53 @@ class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
||||
});
|
||||
|
||||
try {
|
||||
// 检查缓存
|
||||
MovieEntity? videoItem;
|
||||
if (widget.useCache) {
|
||||
videoItem = SCGiftVapSvgaManager().videoItemCache[widget.resource];
|
||||
}
|
||||
final videoItem =
|
||||
_retainedResource == widget.resource && _retainedMovieEntity != null
|
||||
? _retainedMovieEntity!
|
||||
: await SCGiftVapSvgaManager().acquireSvgaEntity(
|
||||
widget.resource,
|
||||
);
|
||||
final acquiredNewEntity = !identical(videoItem, _retainedMovieEntity);
|
||||
if (!mounted) {
|
||||
if (acquiredNewEntity) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(videoItem);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有缓存,则进行解析
|
||||
if (videoItem == null) {
|
||||
videoItem =
|
||||
setState(() {
|
||||
_releaseRetainedMovieEntity(except: videoItem);
|
||||
_retainedMovieEntity = videoItem;
|
||||
_retainedResource = widget.resource;
|
||||
_animationController?.videoItem = videoItem;
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
final videoItem =
|
||||
_isNetworkResource
|
||||
? await SVGAParser.shared.decodeFromURL(widget.resource)
|
||||
: await SVGAParser.shared.decodeFromAssets(widget.resource);
|
||||
videoItem.autorelease =false;
|
||||
// 存入缓存
|
||||
if (widget.useCache) {
|
||||
SCGiftVapSvgaManager().videoItemCache[widget.resource] = videoItem;
|
||||
if (!mounted) {
|
||||
videoItem.dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_animationController?.videoItem = videoItem;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 根据循环次数设置播放方式
|
||||
if (widget.loops == 0) {
|
||||
_animationController?.repeat();
|
||||
} else {
|
||||
_animationController?.repeat(count: widget.loops);
|
||||
}
|
||||
// 根据循环次数设置播放方式
|
||||
if (widget.loops == 0) {
|
||||
_animationController?.repeat();
|
||||
} else {
|
||||
_animationController?.repeat(count: widget.loops);
|
||||
}
|
||||
|
||||
if (widget.onFinishLoading != null) {
|
||||
widget.onFinishLoading!();
|
||||
}
|
||||
if (widget.onFinishLoading != null) {
|
||||
widget.onFinishLoading!();
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
@ -157,14 +171,16 @@ class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
||||
_animationController?.stop();
|
||||
if (widget.clearsAfterStop) {
|
||||
_animationController?.videoItem = null;
|
||||
_releaseRetainedMovieEntity();
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载动画
|
||||
void reload() {
|
||||
if (widget.useCache) {
|
||||
// 清除缓存项
|
||||
SCGiftVapSvgaManager().videoItemCache.remove(widget.resource);
|
||||
_animationController?.videoItem = null;
|
||||
_releaseRetainedMovieEntity();
|
||||
SCGiftVapSvgaManager().evictSvgaEntity(widget.resource);
|
||||
}
|
||||
_loadAnimation();
|
||||
}
|
||||
@ -173,9 +189,20 @@ class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
||||
void dispose() {
|
||||
stop();
|
||||
_animationController?.dispose();
|
||||
_releaseRetainedMovieEntity();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _releaseRetainedMovieEntity({MovieEntity? except}) {
|
||||
final entity = _retainedMovieEntity;
|
||||
if (entity == null || identical(entity, except)) {
|
||||
return;
|
||||
}
|
||||
_retainedMovieEntity = null;
|
||||
_retainedResource = null;
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(entity);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading && !widget.autoPlay) {
|
||||
|
||||
@ -270,6 +270,8 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
||||
bool _isLoading = false;
|
||||
bool _hasError = false;
|
||||
String? _currentResource;
|
||||
MovieEntity? _retainedMovieEntity;
|
||||
String? _retainedResource;
|
||||
|
||||
// 用于存储SVGAImage组件的key,以便刷新占位符
|
||||
GlobalKey _svgaKey = GlobalKey();
|
||||
@ -351,45 +353,79 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
||||
_hasError = false;
|
||||
});
|
||||
|
||||
MovieEntity? acquiredMovieEntity;
|
||||
try {
|
||||
final isNetworkResource = resource.startsWith('http');
|
||||
MovieEntity? videoItem;
|
||||
|
||||
if (widget.useCache) {
|
||||
videoItem = SCGiftVapSvgaManager().videoItemCache[resource];
|
||||
}
|
||||
final videoItem =
|
||||
_retainedResource == resource && _retainedMovieEntity != null
|
||||
? _retainedMovieEntity!
|
||||
: await SCGiftVapSvgaManager().acquireSvgaEntity(resource);
|
||||
final acquiredNewEntity = !identical(videoItem, _retainedMovieEntity);
|
||||
if (acquiredNewEntity) {
|
||||
acquiredMovieEntity = videoItem;
|
||||
}
|
||||
if (!mounted) {
|
||||
if (acquiredNewEntity) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(videoItem);
|
||||
acquiredMovieEntity = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (videoItem == null) {
|
||||
videoItem =
|
||||
// 应用动态数据(文本、图片替换)
|
||||
await _applyDynamicData(videoItem, dynamicData);
|
||||
if (!mounted) {
|
||||
if (acquiredNewEntity) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(videoItem);
|
||||
acquiredMovieEntity = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_releaseRetainedMovieEntity(except: videoItem);
|
||||
_retainedMovieEntity = videoItem;
|
||||
_retainedResource = resource;
|
||||
_animationController?.videoItem = videoItem;
|
||||
_isLoading = false;
|
||||
});
|
||||
acquiredMovieEntity = null;
|
||||
} else {
|
||||
final isNetworkResource = resource.startsWith('http');
|
||||
final videoItem =
|
||||
isNetworkResource
|
||||
? await SVGAParser.shared.decodeFromURL(resource)
|
||||
: await SVGAParser.shared.decodeFromAssets(resource);
|
||||
videoItem.autorelease = false;
|
||||
|
||||
if (widget.useCache) {
|
||||
SCGiftVapSvgaManager().videoItemCache[resource] = videoItem;
|
||||
if (!mounted) {
|
||||
videoItem.dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
// 应用动态数据(文本、图片替换)
|
||||
await _applyDynamicData(videoItem, dynamicData);
|
||||
if (!mounted) {
|
||||
videoItem.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_animationController?.videoItem = videoItem;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 开始播放动画
|
||||
_startAnimation();
|
||||
// 开始播放动画
|
||||
_startAnimation();
|
||||
|
||||
if (widget.onFinishLoading != null) {
|
||||
widget.onFinishLoading!();
|
||||
}
|
||||
if (widget.onFinishLoading != null) {
|
||||
widget.onFinishLoading!();
|
||||
}
|
||||
} catch (e) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(acquiredMovieEntity);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_animationController?.videoItem = null;
|
||||
_releaseRetainedMovieEntity();
|
||||
_isLoading = false;
|
||||
_hasError = true;
|
||||
});
|
||||
@ -543,6 +579,7 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
||||
_animationController?.stop();
|
||||
if (widget.clearsAfterStop) {
|
||||
_animationController?.videoItem = null;
|
||||
_releaseRetainedMovieEntity();
|
||||
setState(() {
|
||||
_currentResource = null;
|
||||
});
|
||||
@ -553,7 +590,9 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
||||
void reload() {
|
||||
if (_currentResource != null) {
|
||||
if (widget.useCache) {
|
||||
SCGiftVapSvgaManager().videoItemCache.remove(_currentResource);
|
||||
_animationController?.videoItem = null;
|
||||
_releaseRetainedMovieEntity();
|
||||
SCGiftVapSvgaManager().evictSvgaEntity(_currentResource!);
|
||||
}
|
||||
|
||||
_loadAnimation(
|
||||
@ -568,11 +607,22 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
||||
void dispose() {
|
||||
stop();
|
||||
_animationController?.dispose();
|
||||
_releaseRetainedMovieEntity();
|
||||
// 从队列管理器中移除当前播放器
|
||||
_queueManager.clearCurrentPlayer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _releaseRetainedMovieEntity({MovieEntity? except}) {
|
||||
final entity = _retainedMovieEntity;
|
||||
if (entity == null || identical(entity, except)) {
|
||||
return;
|
||||
}
|
||||
_retainedMovieEntity = null;
|
||||
_retainedResource = null;
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(entity);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 显示加载状态
|
||||
|
||||
@ -52,6 +52,8 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final SVGAAnimationController _controller;
|
||||
String? _loadedResource;
|
||||
MovieEntity? _retainedMovieEntity;
|
||||
String? _retainedResource;
|
||||
bool _hasError = false;
|
||||
|
||||
@override
|
||||
@ -78,9 +80,10 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
||||
final resource = widget.resource.trim();
|
||||
if (resource.isEmpty || !SCNetworkSvgaWidget.isSvga(resource)) {
|
||||
setState(() {
|
||||
_controller.videoItem = null;
|
||||
_releaseRetainedMovieEntity();
|
||||
_hasError = true;
|
||||
_loadedResource = null;
|
||||
_controller.videoItem = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -89,37 +92,53 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
||||
_hasError = false;
|
||||
});
|
||||
|
||||
MovieEntity? acquiredMovieEntity;
|
||||
try {
|
||||
final cache = SCGiftVapSvgaManager().videoItemCache;
|
||||
var movieEntity = cache[resource];
|
||||
if (movieEntity == null) {
|
||||
movieEntity =
|
||||
resource.startsWith("http")
|
||||
? await SVGAParser.shared.decodeFromURL(resource)
|
||||
: await SVGAParser.shared.decodeFromAssets(resource);
|
||||
movieEntity.autorelease = false;
|
||||
cache[resource] = movieEntity;
|
||||
final movieEntity =
|
||||
_retainedResource == resource && _retainedMovieEntity != null
|
||||
? _retainedMovieEntity!
|
||||
: await SCGiftVapSvgaManager().acquireSvgaEntity(resource);
|
||||
final acquiredNewEntity = !identical(movieEntity, _retainedMovieEntity);
|
||||
if (acquiredNewEntity) {
|
||||
acquiredMovieEntity = movieEntity;
|
||||
}
|
||||
if (!mounted || widget.resource.trim() != resource) {
|
||||
if (acquiredNewEntity) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(movieEntity);
|
||||
acquiredMovieEntity = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (widget.movieConfigurer != null) {
|
||||
movieEntity.dynamicItem.reset();
|
||||
await widget.movieConfigurer!(movieEntity);
|
||||
}
|
||||
if (!mounted || widget.resource.trim() != resource) {
|
||||
if (acquiredNewEntity) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(movieEntity);
|
||||
acquiredMovieEntity = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_releaseRetainedMovieEntity(except: movieEntity);
|
||||
_retainedMovieEntity = movieEntity;
|
||||
_retainedResource = resource;
|
||||
_loadedResource = resource;
|
||||
_controller.videoItem = movieEntity;
|
||||
});
|
||||
acquiredMovieEntity = null;
|
||||
_syncPlayback(restartIfActive: true);
|
||||
} catch (error) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(acquiredMovieEntity);
|
||||
if (!mounted || widget.resource.trim() != resource) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_controller.videoItem = null;
|
||||
_releaseRetainedMovieEntity();
|
||||
_hasError = true;
|
||||
_loadedResource = null;
|
||||
_controller.videoItem = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -145,9 +164,20 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_releaseRetainedMovieEntity();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _releaseRetainedMovieEntity({MovieEntity? except}) {
|
||||
final entity = _retainedMovieEntity;
|
||||
if (entity == null || identical(entity, except)) {
|
||||
return;
|
||||
}
|
||||
_retainedMovieEntity = null;
|
||||
_retainedResource = null;
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(entity);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final videoItem = _controller.videoItem;
|
||||
|
||||
@ -8,9 +8,11 @@ typedef SCSvgaMovieConfigurer =
|
||||
FutureOr<void> Function(MovieEntity movieEntity);
|
||||
|
||||
class SCSvgaAssetWidget extends StatefulWidget {
|
||||
static const int _maxCacheEntries = 8;
|
||||
static final Map<String, MovieEntity> _cache = <String, MovieEntity>{};
|
||||
static final Map<String, Future<MovieEntity>> _loadingTasks =
|
||||
<String, Future<MovieEntity>>{};
|
||||
static final Map<MovieEntity, int> _activeUseCounts = <MovieEntity, int>{};
|
||||
|
||||
const SCSvgaAssetWidget({
|
||||
super.key,
|
||||
@ -49,8 +51,26 @@ class SCSvgaAssetWidget extends StatefulWidget {
|
||||
return _obtainMovieEntity(assetPath);
|
||||
}
|
||||
|
||||
static void clearMemoryCache({bool includeActive = false}) {
|
||||
final entries = _cache.entries.toList(growable: false);
|
||||
for (final entry in entries) {
|
||||
final entity = entry.value;
|
||||
if (!includeActive && _isActive(entity)) {
|
||||
continue;
|
||||
}
|
||||
_cache.remove(entry.key);
|
||||
_disposeMovieEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<MovieEntity> _acquireMovieEntity(String assetPath) async {
|
||||
final entity = await _obtainMovieEntity(assetPath);
|
||||
_retainMovieEntity(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
static Future<MovieEntity> _obtainMovieEntity(String assetPath) async {
|
||||
final cached = _cache[assetPath];
|
||||
final cached = _touchCachedMovieEntity(assetPath);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
@ -71,7 +91,7 @@ class SCSvgaAssetWidget extends StatefulWidget {
|
||||
);
|
||||
}
|
||||
entity.autorelease = false;
|
||||
_cache[assetPath] = entity;
|
||||
_cacheMovieEntity(assetPath, entity);
|
||||
return entity;
|
||||
}();
|
||||
|
||||
@ -83,6 +103,77 @@ class SCSvgaAssetWidget extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
static MovieEntity? _touchCachedMovieEntity(String assetPath) {
|
||||
final cached = _cache.remove(assetPath);
|
||||
if (cached != null) {
|
||||
_cache[assetPath] = cached;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
static void _cacheMovieEntity(String assetPath, MovieEntity entity) {
|
||||
final previous = _cache.remove(assetPath);
|
||||
if (previous != null && !identical(previous, entity)) {
|
||||
_disposeMovieEntityIfInactive(previous);
|
||||
}
|
||||
_cache[assetPath] = entity;
|
||||
_trimCache();
|
||||
}
|
||||
|
||||
static void _trimCache() {
|
||||
while (_cache.length > _maxCacheEntries) {
|
||||
final removableEntry = _cache.entries.firstWhere(
|
||||
(entry) => !_isActive(entry.value),
|
||||
orElse: () => _cache.entries.first,
|
||||
);
|
||||
if (_isActive(removableEntry.value)) {
|
||||
_cache.remove(removableEntry.key);
|
||||
_cache[removableEntry.key] = removableEntry.value;
|
||||
break;
|
||||
}
|
||||
_cache.remove(removableEntry.key);
|
||||
_disposeMovieEntity(removableEntry.value);
|
||||
}
|
||||
}
|
||||
|
||||
static void _retainMovieEntity(MovieEntity entity) {
|
||||
_activeUseCounts[entity] = (_activeUseCounts[entity] ?? 0) + 1;
|
||||
}
|
||||
|
||||
static void _releaseMovieEntity(MovieEntity? entity) {
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
final count = _activeUseCounts[entity] ?? 0;
|
||||
if (count <= 1) {
|
||||
_activeUseCounts.remove(entity);
|
||||
if (!_cache.containsValue(entity)) {
|
||||
_disposeMovieEntity(entity);
|
||||
} else {
|
||||
_trimCache();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_activeUseCounts[entity] = count - 1;
|
||||
}
|
||||
|
||||
static bool _isActive(MovieEntity entity) =>
|
||||
(_activeUseCounts[entity] ?? 0) > 0;
|
||||
|
||||
static void _disposeMovieEntityIfInactive(MovieEntity entity) {
|
||||
if (_isActive(entity)) {
|
||||
return;
|
||||
}
|
||||
_disposeMovieEntity(entity);
|
||||
}
|
||||
|
||||
static void _disposeMovieEntity(MovieEntity entity) {
|
||||
try {
|
||||
_activeUseCounts.remove(entity);
|
||||
entity.dispose();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@override
|
||||
State<SCSvgaAssetWidget> createState() => _SCSvgaAssetWidgetState();
|
||||
}
|
||||
@ -91,6 +182,8 @@ class _SCSvgaAssetWidgetState extends State<SCSvgaAssetWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final SVGAAnimationController _controller;
|
||||
String? _loadedAssetPath;
|
||||
MovieEntity? _retainedMovieEntity;
|
||||
String? _retainedAssetPath;
|
||||
bool _hasError = false;
|
||||
|
||||
@override
|
||||
@ -131,25 +224,44 @@ class _SCSvgaAssetWidgetState extends State<SCSvgaAssetWidget>
|
||||
setState(() {
|
||||
_hasError = false;
|
||||
});
|
||||
MovieEntity? acquiredMovieEntity;
|
||||
try {
|
||||
final movieEntity = await SCSvgaAssetWidget._obtainMovieEntity(assetPath);
|
||||
final movieEntity =
|
||||
_retainedAssetPath == assetPath && _retainedMovieEntity != null
|
||||
? _retainedMovieEntity!
|
||||
: await SCSvgaAssetWidget._acquireMovieEntity(assetPath);
|
||||
final acquiredNewEntity = !identical(movieEntity, _retainedMovieEntity);
|
||||
if (acquiredNewEntity) {
|
||||
acquiredMovieEntity = movieEntity;
|
||||
}
|
||||
if (widget.movieConfigurer != null) {
|
||||
movieEntity.dynamicItem.reset();
|
||||
await widget.movieConfigurer!(movieEntity);
|
||||
}
|
||||
if (!mounted || widget.assetPath != assetPath) {
|
||||
if (acquiredNewEntity) {
|
||||
SCSvgaAssetWidget._releaseMovieEntity(movieEntity);
|
||||
acquiredMovieEntity = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_releaseRetainedMovieEntity(except: movieEntity);
|
||||
_retainedMovieEntity = movieEntity;
|
||||
_retainedAssetPath = assetPath;
|
||||
_loadedAssetPath = assetPath;
|
||||
_controller.videoItem = movieEntity;
|
||||
});
|
||||
acquiredMovieEntity = null;
|
||||
_syncPlayback(restartIfActive: true);
|
||||
} catch (error) {
|
||||
SCSvgaAssetWidget._releaseMovieEntity(acquiredMovieEntity);
|
||||
if (!mounted || widget.assetPath != assetPath) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_controller.videoItem = null;
|
||||
_releaseRetainedMovieEntity();
|
||||
_hasError = true;
|
||||
});
|
||||
}
|
||||
@ -185,9 +297,20 @@ class _SCSvgaAssetWidgetState extends State<SCSvgaAssetWidget>
|
||||
void dispose() {
|
||||
_controller.removeStatusListener(_handleAnimationStatusChanged);
|
||||
_controller.dispose();
|
||||
_releaseRetainedMovieEntity();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _releaseRetainedMovieEntity({MovieEntity? except}) {
|
||||
final entity = _retainedMovieEntity;
|
||||
if (entity == null || identical(entity, except)) {
|
||||
return;
|
||||
}
|
||||
_retainedMovieEntity = null;
|
||||
_retainedAssetPath = null;
|
||||
SCSvgaAssetWidget._releaseMovieEntity(entity);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_hasError) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user