csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
5.6 KiB
168 lines
5.6 KiB
using System;
|
|
using System.Diagnostics;
|
|
using Android.OS;
|
|
using Avalonia.Controls.Documents;
|
|
using Avalonia.Threading;
|
|
using Java.Lang;
|
|
using App = Android.App.Application;
|
|
using Object = Java.Lang.Object;
|
|
|
|
namespace Avalonia.Android
|
|
{
|
|
internal sealed class AndroidDispatcherImpl : IDispatcherImplWithExplicitBackgroundProcessing,
|
|
IDispatcherImplWithPendingInput
|
|
{
|
|
[ThreadStatic] private static bool? s_isUIThread;
|
|
private readonly Looper _mainLooper;
|
|
private readonly Handler _handler;
|
|
private readonly Runnable _signaler;
|
|
private readonly Runnable _timerSignaler;
|
|
private readonly Runnable _wakeupSignaler;
|
|
private readonly MessageQueue _queue;
|
|
private readonly object _lock = new();
|
|
private bool _signaled;
|
|
private bool _backgroundProcessingRequested;
|
|
|
|
|
|
public AndroidDispatcherImpl()
|
|
{
|
|
_mainLooper = App.Context.MainLooper ??
|
|
throw new InvalidOperationException(
|
|
"Application.Context.MainLooper was not expected to be null.");
|
|
if (!CurrentThreadIsLoopThread)
|
|
throw new InvalidOperationException("This class should be instanciated from the UI thread");
|
|
_handler = new Handler(_mainLooper);
|
|
_signaler = new Runnable(OnSignaled);
|
|
_timerSignaler = new Runnable(OnTimer);
|
|
_wakeupSignaler = new Runnable(() => { });
|
|
_queue = Looper.MyQueue();
|
|
Looper.MyQueue().AddIdleHandler(new IdleHandler(this));
|
|
CanQueryPendingInput = OperatingSystem.IsAndroidVersionAtLeast(23);
|
|
}
|
|
|
|
public event Action? Timer;
|
|
private void OnTimer() => Timer?.Invoke();
|
|
|
|
public event Action? Signaled;
|
|
private void OnSignaled()
|
|
{
|
|
lock (_lock)
|
|
_signaled = false;
|
|
Signaled?.Invoke();
|
|
}
|
|
|
|
public bool CurrentThreadIsLoopThread
|
|
{
|
|
get
|
|
{
|
|
if (s_isUIThread.HasValue)
|
|
return s_isUIThread.Value;
|
|
var uiThread = OperatingSystem.IsAndroidVersionAtLeast(23)
|
|
? _mainLooper.IsCurrentThread
|
|
: _mainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
|
|
|
|
s_isUIThread = uiThread;
|
|
return uiThread;
|
|
}
|
|
}
|
|
|
|
public void Signal()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if(_signaled)
|
|
return;
|
|
_signaled = true;
|
|
_handler.Post(_signaler);
|
|
}
|
|
}
|
|
|
|
readonly Stopwatch _clock = Stopwatch.StartNew();
|
|
public long Now => _clock.ElapsedMilliseconds;
|
|
|
|
public void UpdateTimer(long? dueTimeInMs)
|
|
{
|
|
_handler.RemoveCallbacks(_timerSignaler);
|
|
if (dueTimeInMs.HasValue)
|
|
{
|
|
var delay = dueTimeInMs.Value - Now;
|
|
if (delay > 0)
|
|
_handler.PostDelayed(_timerSignaler, delay);
|
|
else
|
|
_handler.Post(_timerSignaler);
|
|
}
|
|
}
|
|
|
|
class IdleHandler : Object, MessageQueue.IIdleHandler
|
|
{
|
|
private readonly AndroidDispatcherImpl _parent;
|
|
|
|
public IdleHandler(AndroidDispatcherImpl parent)
|
|
{
|
|
_parent = parent;
|
|
}
|
|
|
|
public bool QueueIdle()
|
|
{
|
|
_parent.OnIdle();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public event Action? ReadyForBackgroundProcessing;
|
|
|
|
public void RequestBackgroundProcessing()
|
|
{
|
|
_backgroundProcessingRequested = true;
|
|
}
|
|
|
|
void OnIdle()
|
|
{
|
|
tailCall:
|
|
if (_backgroundProcessingRequested)
|
|
{
|
|
_backgroundProcessingRequested = false;
|
|
ReadyForBackgroundProcessing?.Invoke();
|
|
}
|
|
|
|
if (_backgroundProcessingRequested)
|
|
{
|
|
// Dispatcher requested background processing again, however if the queue is empty and we
|
|
// just return here, Android's Looper will go to sleep and won't call us again and we'll have
|
|
// "background" jobs not being processed
|
|
// So we need to examine the queue state to prevent that scenario
|
|
|
|
lock (_lock)
|
|
{
|
|
// There are higher priority jobs enqueued, we'll be called again
|
|
if (_signaled)
|
|
return;
|
|
}
|
|
|
|
if (CanQueryPendingInput)
|
|
{
|
|
if (!HasPendingInput)
|
|
// There are no events in the queue, so if we just return here, Looper will go to sleep,
|
|
// so just run our logic again
|
|
goto tailCall;
|
|
// Nothing to do otherwise, we'll be called again after higher priority events get processed
|
|
}
|
|
else
|
|
{
|
|
// On this API level we can't check if there is pending input,
|
|
// so we explicitly wake up the Looper to make sure that it will call idle hooks again
|
|
// before going to sleep
|
|
_handler.Post(_wakeupSignaler);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool CanQueryPendingInput { get; }
|
|
|
|
// See check in ctor
|
|
#pragma warning disable CA1416
|
|
public bool HasPendingInput => !_queue.IsIdle;
|
|
#pragma warning restore CA1416
|
|
}
|
|
}
|
|
|