fix(mobile,Android): throttle detail progress notifications & wait on foregroundInfo (#907)

This commit is contained in:
Fynn Petersen-Frey 2022-11-01 03:02:06 +01:00 committed by GitHub
parent cfa04fadd1
commit dcefd53bfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 18 deletions

View file

@ -50,6 +50,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
private var timeBackupStarted: Long = 0L
private var notificationBuilder: NotificationCompat.Builder? = null
private var notificationDetailBuilder: NotificationCompat.Builder? = null
private var fgFuture: ListenableFuture<Void>? = null
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
@ -112,6 +113,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
backgroundChannel.invokeMethod("systemStop", null)
}
waitOnSetForegroundAsync()
// cannot await/get(block) on resolvableFuture as its already cancelled (would throw CancellationException)
// instead, wait for 5 seconds until forcefully stopping backup work
Handler(Looper.getMainLooper()).postDelayed({
@ -119,6 +121,17 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
}, 5000)
}
private fun waitOnSetForegroundAsync() {
val fgFuture = this.fgFuture
if (fgFuture != null && !fgFuture.isCancelled() && !fgFuture.isDone()) {
try {
fgFuture.get(500, TimeUnit.MILLISECONDS)
}
catch (e: Exception) {
// ignored, there is nothing to be done
}
}
}
private fun stopEngine(result: Result?) {
if (result != null) {
@ -128,6 +141,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
engine?.destroy()
engine = null
clearBackgroundNotification()
waitOnSetForegroundAsync()
}
override fun onMethodCall(call: MethodCall, r: MethodChannel.Result) {
@ -207,8 +221,8 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
private fun showInfo(notification: Notification, isDetail: Boolean = false) {
val id = if(isDetail) NOTIFICATION_DETAIL_ID else NOTIFICATION_ID
if (isIgnoringBatteryOptimizations) {
setForegroundAsync(ForegroundInfo(id, notification))
if (isIgnoringBatteryOptimizations && !isDetail) {
fgFuture = setForegroundAsync(ForegroundInfo(id, notification))
} else {
notificationManager.notify(id, notification)
}

View file

@ -43,8 +43,9 @@ class BackgroundService {
bool _errorGracePeriodExceeded = true;
int _uploadedAssetsCount = 0;
int _assetsToUploadCount = 0;
int _lastDetailProgressUpdate = 0;
String _lastPrintedProgress = "";
late final _Throttle _throttleNotificationUpdates =
_Throttle(_updateDetailProgress, const Duration(milliseconds: 400));
bool get isBackgroundInitialized {
return _isBackgroundInitialized;
@ -447,21 +448,20 @@ class BackgroundService {
}
void _onProgress(int sent, int total) {
final int now = Timeline.now;
// limit updates to 10 per second (or Android drops important notifications)
if (now > _lastDetailProgressUpdate + 100000) {
final String msg = _humanReadableBytesProgress(sent, total);
// only update if message actually differs (to stop many useless notification updates on large assets or slow connections)
if (msg != _lastPrintedProgress) {
_lastDetailProgressUpdate = now;
_lastPrintedProgress = msg;
_updateNotification(
progress: sent,
max: total,
isDetail: true,
content: msg,
);
}
_throttleNotificationUpdates(sent, total);
}
void _updateDetailProgress(int sent, int total) {
final String msg = _humanReadableBytesProgress(sent, total);
// only update if message actually differs (to stop many useless notification updates on large assets or slow connections)
if (msg != _lastPrintedProgress) {
_lastPrintedProgress = msg;
_updateNotification(
progress: sent,
max: total,
isDetail: true,
content: msg,
);
}
}
@ -532,6 +532,34 @@ class BackgroundService {
}
}
class _Throttle {
_Throttle(this._fun, Duration interval) : _interval = interval.inMicroseconds;
final void Function(int, int) _fun;
final int _interval;
int _invokedAt = 0;
Timer? _timer;
int _progress = 0;
int _total = 0;
void call(int progress, int total) {
final time = Timeline.now;
_progress = progress;
_total = total;
if (time > _invokedAt + _interval) {
_timer?.cancel();
_onTimeElapsed();
} else {
_timer ??= Timer(Duration(microseconds: _interval), _onTimeElapsed);
}
}
void _onTimeElapsed() {
_invokedAt = Timeline.now;
_fun(_progress, _total);
_timer = null;
}
}
/// entry point called by Kotlin/Java code; needs to be a top-level function
@pragma('vm:entry-point')
void _nativeEntry() {