Skip to content

Commit

Permalink
[android] move IsDispatchRequiredImplementation() to Java (#27936)
Browse files Browse the repository at this point in the history
* [android] move `IsDispatchRequiredImplementation()` to Java

Context: #8919
Context: https://github.com/jonathanpeppers/MAUI_CollectionView/tree/net9.0

Profiling the above sample, I saw a lot of time in:

    46.21ms (0.89%)	0.00ns (<0.01%)	Microsoft!Microsoft.Maui.Dispatching.Dispatcher.IsDispatchRequiredImplementation()

`Microsoft.Maui.Dispatching.Dispatcher.IsDispatchRequired` looks like
it is called *a lot* by .NET MAUI, so it's likely worth improving here.

Almost all of the time is spent on the check:

    _looper != Looper.MyLooper();

`MyLooper()` does interop to Java, and then bookkeeping to create a C#
instance of the `Looper`.

You can see this time spent inside `IsDispatchRequiredImplementation()` as:

    20.18ms (44%) Mono.Android!Java.Lang.Object.GetObject(intptr,Android.Runtime.JniHandleOwnership)

If we instead move the call to Java, C# can instead call a new
`PlatformDispatcher` type that extends `Handler`:

    _dispatcher.IsDispatchRequired;

So now C# calls a Java method that simply returns a `bool`, and no C#
`Looper` instance has to be created or managed. This completely
removes the ~20ms or so we see spent in `Java.Lang.Object.GetObject()`.

This will also help performance tiny amount at startup, as we don't
call `MyLooper()` or `MainLooper` any longer in C#, when creating the
`Dispatcher`.

* Update src/Core/AndroidNative/maui/src/main/java/com/microsoft/maui/PlatformDispatcher.java
  • Loading branch information
jonathanpeppers authored Feb 25, 2025
1 parent 2fdc54a commit e63f9e1
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.microsoft.maui;

import android.os.Handler;
import android.os.Looper;

/**
* Java-side support for Dispatcher.Android.cs
*/
public class PlatformDispatcher extends Handler {
private PlatformDispatcher(Looper looper) {
super(looper);
}

public static PlatformDispatcher create() {
Looper looper = Looper.myLooper();
if (looper == null || looper != Looper.getMainLooper())
return null;
return new PlatformDispatcher(looper);
}

public boolean isDispatchRequired() {
return Looper.myLooper() != getLooper();
}
}
23 changes: 11 additions & 12 deletions src/Core/src/Dispatching/Dispatcher.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,26 @@ namespace Microsoft.Maui.Dispatching
/// <inheritdoc/>
public partial class Dispatcher : IDispatcher
{
readonly Looper _looper;
readonly Handler _handler;
// NOTE: PlatformDispatcher extends Handler
readonly PlatformDispatcher _dispatcher;

internal Dispatcher(Looper looper)
internal Dispatcher(PlatformDispatcher dispatcher)
{
_looper = looper ?? throw new ArgumentNullException(nameof(looper));
_handler = new Handler(_looper);
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
}

bool IsDispatchRequiredImplementation() =>
_looper != Looper.MyLooper();
_dispatcher.IsDispatchRequired;

bool DispatchImplementation(Action action) =>
_handler.Post(() => action());
_dispatcher.Post(() => action());

bool DispatchDelayedImplementation(TimeSpan delay, Action action) =>
_handler.PostDelayed(() => action(), (long)delay.TotalMilliseconds);
_dispatcher.PostDelayed(() => action(), (long)delay.TotalMilliseconds);

DispatcherTimer CreateTimerImplementation()
{
return new DispatcherTimer(_handler);
return new DispatcherTimer(_dispatcher);
}
}

Expand Down Expand Up @@ -149,11 +148,11 @@ public partial class DispatcherProvider
{
static Dispatcher? GetForCurrentThreadImplementation()
{
var q = Looper.MyLooper();
if (q == null || q != Looper.MainLooper)
var dispatcher = PlatformDispatcher.Create();
if (dispatcher is null)
return null;

return new Dispatcher(q);
return new Dispatcher(dispatcher);
}
}
}

0 comments on commit e63f9e1

Please sign in to comment.