cached_request.dart 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter/material.dart'; // 引入 Flutter 核心包
  4. import 'package:http/http.dart' as http;
  5. import 'package:image_puzzle/models/api_helper.dart';
  6. import 'package:image_puzzle/utils/utils.dart';
  7. import 'package:logging/logging.dart';
  8. final Logger _log = Logger('cached_request.dart');
  9. typedef TransformFunction = Future<dynamic> Function(dynamic json);
  10. // 混入 WidgetsBindingObserver 以监听应用生命周期
  11. class CachedRequest with WidgetsBindingObserver {
  12. static final Map<String, CachedRequest> _cache = {};
  13. final String url;
  14. TransformFunction? transformFunction;
  15. // 仅使用 .broadcast(),但 onListen 只会触发一次
  16. final StreamController _streamController = StreamController.broadcast();
  17. dynamic _transformed;
  18. // --- 【新增】Getter:允许外部同步访问最新的缓存数据 ---
  19. dynamic get cachedData => _transformed;
  20. CachedRequest._internal(this.url, this.transformFunction) {
  21. _log.info('New cached request: $url');
  22. // 注册生命周期监听器
  23. WidgetsBinding.instance.addObserver(this);
  24. _init();
  25. }
  26. factory CachedRequest.fromUrl(String url, {TransformFunction? transformFunction}) {
  27. // 确保单例模式下,只注册一次监听器
  28. if (!_cache.containsKey(url)) {
  29. _cache[url] = CachedRequest._internal(url, transformFunction);
  30. }
  31. return _cache[url]!;
  32. }
  33. // --- 关键修改:生命周期监听 ---
  34. @override
  35. void didChangeAppLifecycleState(AppLifecycleState state) {
  36. if (state == AppLifecycleState.resumed) {
  37. _log.info('App Resumed from background. Forcing refresh for $url');
  38. // 应用程序从后台恢复到前台时,强制刷新数据
  39. refresh();
  40. }
  41. }
  42. // 由于 CachedRequest 是一个单例,它不会被销毁,除非应用完全关闭。
  43. // 但是,为了严谨性,如果添加了 Dispose 逻辑,应记得移除 Observer。
  44. // 注意:在 Flutter Provider 或 InheritedWidget 依赖的单例中,通常不需要手动调用 dispose。
  45. // 如果需要清理:
  46. /*
  47. void dispose() {
  48. WidgetsBinding.instance.removeObserver(this);
  49. _streamController.close();
  50. // ... clean up
  51. }
  52. */
  53. // ---------------------------------
  54. _init() {
  55. // FIX: 首次初始化时,立即尝试加载缓存(如果有)
  56. _cacheLoad();
  57. _remoteLoad();
  58. // _streamController.onListen = () {
  59. // if (_transformed != null) {
  60. // // 如果有缓存数据,立即发送给新的监听者
  61. // _streamController.add(_transformed);
  62. // }
  63. // };
  64. _streamController.onListen = () {
  65. _log.info('Stream listener added for $url. Current data available? ${_transformed != null}');
  66. if (_transformed != null) {
  67. // 确保新订阅者立即获得缓存数据 (用于热重载或首次进入)
  68. // 使用 Future.microtask 确保在 onListen 结束后再触发 add,避免同步递归
  69. Future.microtask(() => _streamController.add(_transformed));
  70. }
  71. };
  72. }
  73. Future<void> refresh() async {
  74. await _remoteLoad();
  75. }
  76. Future<void> reload() async {
  77. await _cacheLoad();
  78. }
  79. _remoteLoad() async {
  80. try {
  81. final response = await http.get(Uri.parse(url));
  82. if (response.statusCode != 200) {
  83. throw Exception('Invalid status code: ${response.statusCode} when fetching: $url');
  84. }
  85. _log.info('${response.statusCode}, $url');
  86. final data = jsonDecode(response.body);
  87. _emit(data);
  88. await saveString(cachePath, response.body);
  89. } catch (error) {
  90. _streamController.addError(error);
  91. _log.severe('Remote load failed for $url: $error');
  92. }
  93. }
  94. _emit(dynamic data) async {
  95. _log.info('Emiting data..... ');
  96. try {
  97. if (transformFunction != null) {
  98. data = await transformFunction?.call(data);
  99. }
  100. // 仅当有监听者时才尝试添加数据
  101. if (_streamController.hasListener) {
  102. _streamController.add(data);
  103. }
  104. _transformed = data;
  105. } catch (error) {
  106. _streamController.addError(error);
  107. _log.severe('Data transformation or emission failed: $error');
  108. }
  109. }
  110. _cacheLoad() async {
  111. try {
  112. final file = await localFile(cachePath);
  113. if (await file.exists()) {
  114. _log.info('File $file exists, try loading from cache..');
  115. final data = await loadJson(cachePath);
  116. _emit(data);
  117. } else {
  118. if (url == ApiHelper.latestUri) {
  119. _log.info('Loading builtin latest asset data...');
  120. final data = await loadJSONFromAsset('assets/builtin/latest.json');
  121. _emit(data);
  122. }
  123. if (url == ApiHelper.collectionUri) {
  124. _log.info('Loading builtin collection asset data...');
  125. final data = await loadJSONFromAsset('assets/builtin/collection.json');
  126. _emit(data);
  127. }
  128. }
  129. } catch (error) {
  130. // 缓存加载失败不应该阻止远程加载
  131. _streamController.addError(error);
  132. }
  133. }
  134. String get hash => md5Hash(url);
  135. String get cachePath => 'api_cache/$hash';
  136. Stream get stream => _streamController.stream;
  137. @override
  138. String toString() {
  139. return 'CachedRequest(url=$url)';
  140. }
  141. }