Fix cached reminder recovery and retain pre-reconcile idle diagnostics
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
const appVersion = '0.7.8';
|
||||
const appBuildNumber = 32;
|
||||
const appBuildNumber = 33;
|
||||
const appFilingNumber = '蜀ICP备2025169276号-5A';
|
||||
|
||||
@@ -49,6 +49,16 @@ class _ReminderToolsState extends State<ReminderTools> {
|
||||
});
|
||||
|
||||
Future<void> _check() => _run(() async {
|
||||
Map<String, Object?> before;
|
||||
try {
|
||||
before = await widget.controller.notifications.diagnose();
|
||||
} catch (error) {
|
||||
before = {
|
||||
'errorCode': error is PlatformException
|
||||
? error.code
|
||||
: 'diagnosis_unavailable',
|
||||
};
|
||||
}
|
||||
await widget.controller.refreshServices();
|
||||
final diagnosis = await widget.controller.notifications.diagnose();
|
||||
if (!mounted) return;
|
||||
@@ -67,6 +77,15 @@ class _ReminderToolsState extends State<ReminderTools> {
|
||||
'courseCount': c.snapshot.courses.length,
|
||||
'scheduledCount': c.reminderResult.scheduledCount,
|
||||
'reminderStatus': c.reminderResult.status.name,
|
||||
'beforeCheck': {
|
||||
for (final key in [
|
||||
'notifications',
|
||||
'exactAlarm',
|
||||
'pendingCount',
|
||||
'errorCode',
|
||||
])
|
||||
key: _scalar(before[key]),
|
||||
},
|
||||
for (final key in [
|
||||
'notifications',
|
||||
'appNotificationsEnabled',
|
||||
@@ -91,8 +110,18 @@ class _ReminderToolsState extends State<ReminderTools> {
|
||||
'batteryOptimizationsIgnored',
|
||||
'powerSaveMode',
|
||||
'interruptionFilter',
|
||||
'deviceIdle',
|
||||
'standbyBucket',
|
||||
'lastExitReason',
|
||||
'lastExitAtEpochMillis',
|
||||
'wasForceStoppedBeforeStart',
|
||||
'lastVisibleCourseNotificationAt',
|
||||
])
|
||||
key: _scalar(system[key]),
|
||||
'startupAlarmAudit': _safeStartupAudit(system['startupAlarmAudit']),
|
||||
'lastProblemAlarmAudit': _safeStartupAudit(
|
||||
system['lastProblemAlarmAudit'],
|
||||
),
|
||||
},
|
||||
};
|
||||
setState(() {
|
||||
@@ -104,6 +133,22 @@ class _ReminderToolsState extends State<ReminderTools> {
|
||||
Object? _scalar(Object? value) =>
|
||||
value is String || value is bool || value is num ? value : null;
|
||||
|
||||
Map<String, Object?>? _safeStartupAudit(Object? raw) {
|
||||
if (raw is! Map) return null;
|
||||
return {
|
||||
for (final key in [
|
||||
'capturedAtEpochMillis',
|
||||
'cachedCourseCount',
|
||||
'existingHandleCount',
|
||||
'missingHandleCount',
|
||||
'overdueCachedCount',
|
||||
'partial',
|
||||
'errorCode',
|
||||
])
|
||||
key: _scalar(raw[key]),
|
||||
};
|
||||
}
|
||||
|
||||
String _errorCode(Object? value) {
|
||||
if (value is String && RegExp(r'^[a-zA-Z0-9_]{1,64}$').hasMatch(value)) {
|
||||
return value;
|
||||
@@ -129,6 +174,11 @@ class _ReminderToolsState extends State<ReminderTools> {
|
||||
final system = rawSystem is Map ? rawSystem : const {};
|
||||
final pending = diagnosis['pendingCount'];
|
||||
final sdk = system['sdk'];
|
||||
final audit =
|
||||
_safeStartupAudit(system['lastProblemAlarmAudit']) ??
|
||||
_safeStartupAudit(system['startupAlarmAudit']);
|
||||
final missing = audit?['missingHandleCount'];
|
||||
final overdue = audit?['overdueCachedCount'];
|
||||
final channelsSupported = sdk is! int || sdk >= 26;
|
||||
return [
|
||||
'通知权限:${_permission(diagnosis['notifications'])}',
|
||||
@@ -140,6 +190,14 @@ class _ReminderToolsState extends State<ReminderTools> {
|
||||
'课前提醒类别尚未建立,请重新打开应用后检查。',
|
||||
'精确提醒:${_permission(diagnosis['exactAlarm'])}',
|
||||
if (pending is int) '待发送提醒:$pending 条',
|
||||
if (missing is num && missing > 0)
|
||||
'发现 $missing 条提醒的系统触发信息缺失,已保留恢复前的诊断记录。',
|
||||
if (overdue is num && overdue > 0) '发现 $overdue 条缓存提醒已超过预计时间,请复制诊断以便排查。',
|
||||
if (system['standbyBucket'] is num &&
|
||||
(system['standbyBucket'] as num) >= 40)
|
||||
'系统待机限制:当前应用处于低活跃或受限分组,可能延后提醒。',
|
||||
if (system['wasForceStoppedBeforeStart'] == true)
|
||||
'系统记录本次启动前应用曾处于停止状态,先前的定时触发可能已被系统取消。',
|
||||
if (diagnosis['notifications'] == false) '系统通知已关闭,请在通知设置中开启。',
|
||||
if (diagnosis['channelImportance'] == 0) '提醒通道已关闭,请在通知设置中开启。',
|
||||
if (diagnosis['channelSound'] == false) '提醒通道处于静音,请在通知设置中检查声音。',
|
||||
|
||||
@@ -34,6 +34,7 @@ class ReminderRequest {
|
||||
abstract class ReminderGateway {
|
||||
Future<ReminderPermissions> permissions({required bool request});
|
||||
Future<Set<int>> pendingIds();
|
||||
Future<Set<int>> missingSystemIds(Set<int> ids) async => {};
|
||||
Future<Set<int>> ownedIds() => pendingIds();
|
||||
Future<String> sendTestReminder({required bool scheduled}) async =>
|
||||
'当前环境不支持通知测试';
|
||||
@@ -124,6 +125,8 @@ class LocalNotificationService extends NotificationService {
|
||||
);
|
||||
}
|
||||
final pending = await gateway.pendingIds();
|
||||
// Include overdue entries so warm-resume diagnostics survive their removal.
|
||||
final missing = await gateway.missingSystemIds(pending);
|
||||
for (final old in (await gateway.ownedIds()).difference(
|
||||
desired.keys.toSet(),
|
||||
)) {
|
||||
@@ -135,6 +138,7 @@ class LocalNotificationService extends NotificationService {
|
||||
final fingerprint =
|
||||
'${request.fireAt.millisecondsSinceEpoch}/${request.title}/${request.body}/${request.exact}';
|
||||
if (pending.contains(entry.key) &&
|
||||
!missing.contains(entry.key) &&
|
||||
_fingerprints[entry.key] == fingerprint) {
|
||||
continue;
|
||||
}
|
||||
@@ -297,6 +301,20 @@ class PluginReminderGateway extends ReminderGateway {
|
||||
return owned;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Set<int>> missingSystemIds(Set<int> ids) async {
|
||||
if (ids.isEmpty) return {};
|
||||
try {
|
||||
final missing = await _system
|
||||
.invokeListMethod<int>('findMissingAlarmHandles', ids.toList())
|
||||
.timeout(const Duration(seconds: 2));
|
||||
return missing?.where(ids.contains).toSet() ?? {};
|
||||
} catch (_) {
|
||||
// Availability of this extra check must not block normal scheduling.
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> sendTestReminder({required bool scheduled}) async {
|
||||
final access = await permissions(request: false);
|
||||
|
||||
Reference in New Issue
Block a user