fix(mobile,Android): throttle detail progress notifications & wait on foregroundInfo (#907)
This commit is contained in:
parent
cfa04fadd1
commit
dcefd53bfe
2 changed files with 60 additions and 18 deletions
|
@ -50,6 +50,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||||
private var timeBackupStarted: Long = 0L
|
private var timeBackupStarted: Long = 0L
|
||||||
private var notificationBuilder: NotificationCompat.Builder? = null
|
private var notificationBuilder: NotificationCompat.Builder? = null
|
||||||
private var notificationDetailBuilder: NotificationCompat.Builder? = null
|
private var notificationDetailBuilder: NotificationCompat.Builder? = null
|
||||||
|
private var fgFuture: ListenableFuture<Void>? = null
|
||||||
|
|
||||||
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
|
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
|
||||||
|
|
||||||
|
@ -112,6 +113,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||||
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
|
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
|
||||||
backgroundChannel.invokeMethod("systemStop", null)
|
backgroundChannel.invokeMethod("systemStop", null)
|
||||||
}
|
}
|
||||||
|
waitOnSetForegroundAsync()
|
||||||
// cannot await/get(block) on resolvableFuture as its already cancelled (would throw CancellationException)
|
// cannot await/get(block) on resolvableFuture as its already cancelled (would throw CancellationException)
|
||||||
// instead, wait for 5 seconds until forcefully stopping backup work
|
// instead, wait for 5 seconds until forcefully stopping backup work
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
@ -119,6 +121,17 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||||
}, 5000)
|
}, 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?) {
|
private fun stopEngine(result: Result?) {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
@ -128,6 +141,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||||
engine?.destroy()
|
engine?.destroy()
|
||||||
engine = null
|
engine = null
|
||||||
clearBackgroundNotification()
|
clearBackgroundNotification()
|
||||||
|
waitOnSetForegroundAsync()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, r: MethodChannel.Result) {
|
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) {
|
private fun showInfo(notification: Notification, isDetail: Boolean = false) {
|
||||||
val id = if(isDetail) NOTIFICATION_DETAIL_ID else NOTIFICATION_ID
|
val id = if(isDetail) NOTIFICATION_DETAIL_ID else NOTIFICATION_ID
|
||||||
if (isIgnoringBatteryOptimizations) {
|
if (isIgnoringBatteryOptimizations && !isDetail) {
|
||||||
setForegroundAsync(ForegroundInfo(id, notification))
|
fgFuture = setForegroundAsync(ForegroundInfo(id, notification))
|
||||||
} else {
|
} else {
|
||||||
notificationManager.notify(id, notification)
|
notificationManager.notify(id, notification)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,9 @@ class BackgroundService {
|
||||||
bool _errorGracePeriodExceeded = true;
|
bool _errorGracePeriodExceeded = true;
|
||||||
int _uploadedAssetsCount = 0;
|
int _uploadedAssetsCount = 0;
|
||||||
int _assetsToUploadCount = 0;
|
int _assetsToUploadCount = 0;
|
||||||
int _lastDetailProgressUpdate = 0;
|
|
||||||
String _lastPrintedProgress = "";
|
String _lastPrintedProgress = "";
|
||||||
|
late final _Throttle _throttleNotificationUpdates =
|
||||||
|
_Throttle(_updateDetailProgress, const Duration(milliseconds: 400));
|
||||||
|
|
||||||
bool get isBackgroundInitialized {
|
bool get isBackgroundInitialized {
|
||||||
return _isBackgroundInitialized;
|
return _isBackgroundInitialized;
|
||||||
|
@ -447,21 +448,20 @@ class BackgroundService {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onProgress(int sent, int total) {
|
void _onProgress(int sent, int total) {
|
||||||
final int now = Timeline.now;
|
_throttleNotificationUpdates(sent, total);
|
||||||
// limit updates to 10 per second (or Android drops important notifications)
|
}
|
||||||
if (now > _lastDetailProgressUpdate + 100000) {
|
|
||||||
final String msg = _humanReadableBytesProgress(sent, total);
|
void _updateDetailProgress(int sent, int total) {
|
||||||
// only update if message actually differs (to stop many useless notification updates on large assets or slow connections)
|
final String msg = _humanReadableBytesProgress(sent, total);
|
||||||
if (msg != _lastPrintedProgress) {
|
// only update if message actually differs (to stop many useless notification updates on large assets or slow connections)
|
||||||
_lastDetailProgressUpdate = now;
|
if (msg != _lastPrintedProgress) {
|
||||||
_lastPrintedProgress = msg;
|
_lastPrintedProgress = msg;
|
||||||
_updateNotification(
|
_updateNotification(
|
||||||
progress: sent,
|
progress: sent,
|
||||||
max: total,
|
max: total,
|
||||||
isDetail: true,
|
isDetail: true,
|
||||||
content: msg,
|
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
|
/// entry point called by Kotlin/Java code; needs to be a top-level function
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void _nativeEntry() {
|
void _nativeEntry() {
|
||||||
|
|
Loading…
Reference in a new issue