Skip to content

Commit 31696fc

Browse files
jonathanpeppersPureWeen
authored andcommitted
[android] improve performance of ImageHandler.PlatformArrange() (#23665)
Context: https://github.com/davidortinau/AllTheLists Profiling @davidortinau's app, I noticed the "Check-ins" sample felt the slowest on Android. One thing I noticed while scrolling: 98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect) 67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop() In this case, `PlatformArrange()` is called a lot for every `<Image/>`: if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop) { var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame); var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top); PlatformView.ClipBounds = clipRect; } `ImageView.ScaleType` is a class, and so and some bookkeeping is done to lookup *the same* C# instance for a Java object. We can make this a bit better by writing a new Java method: public static boolean isImageViewCenterCrop(@nonnull ImageView imageView) { return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP; } Next, let's make a `PlatformView.ToPixels()` extension method that can avoid calling `View.Context` for the same reason. Lastly, we can make a `PlatformInterop.SetClipBounds()` method to avoid creating a `Android.Graphics.Rect` object in C#. With these changes, I can only see the topmost `PlatformArrange()` method now: 2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect) This should improve the layout performance of all .NET MAUI `<Image/>` on Android. I also "banned" `GetScaleType()` in `eng/BannedSymbols.txt`.
1 parent ae6d2c5 commit 31696fc

File tree

7 files changed

+33
-5
lines changed

7 files changed

+33
-5
lines changed

eng/BannedSymbols.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
M:Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddSingleton`2(Microsoft.Extensions.DependencyInjection.IServiceCollection);Use a Factory method to create the service instead
22
M:Android.Content.Res.ColorStateList.#ctor(System.Int32[][],System.Int32[]);Use Microsoft.Maui.PlatformInterop.Get*ColorStateList() Java methods instead
3+
M:Android.Widget.ImageView.GetScaleType();Use PlatformInterop.IsImageViewCenterCrop instead (or add a new method)
34
P:Microsoft.Maui.MauiWinUIApplication.Services;Use the IPlatformApplication.Current.Services instead
45
P:Microsoft.Maui.MauiWinUIApplication.Application;Use the IPlatformApplication.Current.Application instead
56
P:Microsoft.UI.Xaml.Window.AppWindow;This API doesn't have null safety. Use GetAppWindow() and make sure to account for the possibility that GetAppWindow() might be null.

src/Compatibility/Core/src/Android/FastRenderers/ImageElementManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ static void OnLayoutChange(object sender, global::Android.Views.View.LayoutChang
2626
{
2727
if (sender is IVisualElementRenderer renderer && renderer.View is ImageView imageView)
2828
#pragma warning disable CS0618 // Obsolete
29-
AViewCompat.SetClipBounds(imageView, imageView.GetScaleType() == AScaleType.CenterCrop ? new ARect(0, 0, e.Right - e.Left, e.Bottom - e.Top) : null);
29+
AViewCompat.SetClipBounds(imageView, PlatformInterop.IsImageViewCenterCrop(imageView) ? new ARect(0, 0, e.Right - e.Left, e.Bottom - e.Top) : null);
3030
#pragma warning restore CS0618 // Obsolete
3131

3232
}

src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<RootNamespace>Microsoft.Maui.DeviceTests</RootNamespace>
88
<AssemblyName>Microsoft.Maui.Controls.DeviceTests</AssemblyName>
99
<NoWarn>$(NoWarn),CA1416</NoWarn>
10+
<IsTestProject>true</IsTestProject>
1011
<!-- Disable multi-RID builds to workaround a parallel build issue -->
1112
<RuntimeIdentifier Condition="$(TargetFramework.Contains('-maccatalyst'))">maccatalyst-x64</RuntimeIdentifier>
1213
<RuntimeIdentifier Condition="$(TargetFramework.Contains('-maccatalyst')) and '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'arm64'">maccatalyst-arm64</RuntimeIdentifier>

src/Core/AndroidNative/maui/src/main/java/com/microsoft/maui/PlatformInterop.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,4 +689,19 @@ public static Animatable getAnimatable(Drawable drawable) {
689689
}
690690
return null;
691691
}
692+
693+
/*
694+
* Checks if the ScaleType of the ImageView is CENTER_CROP.
695+
* Avoids returning an object to C#.
696+
*/
697+
public static boolean isImageViewCenterCrop(@NonNull ImageView imageView) {
698+
return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
699+
}
700+
701+
/*
702+
* Sets View.ClipBounds without creating a Rect object in C#
703+
*/
704+
public static void setClipBounds(@NonNull View view, int left, int top, int right, int bottom) {
705+
view.setClipBounds(new Rect(left, top, right, bottom));
706+
}
692707
}

src/Core/src/Handlers/Image/ImageHandler.Android.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,13 @@ await handler
7676

7777
public override void PlatformArrange(Graphics.Rect frame)
7878
{
79-
if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
79+
if (PlatformInterop.IsImageViewCenterCrop(PlatformView))
8080
{
8181
// If the image is center cropped (AspectFill), then the size of the image likely exceeds
8282
// the view size in some dimension. So we need to clip to the view's bounds.
8383

84-
var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
85-
var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
86-
PlatformView.ClipBounds = clipRect;
84+
var (left, top, right, bottom) = PlatformView.ToPixels(frame);
85+
PlatformInterop.SetClipBounds(PlatformView, 0, 0, right - left, bottom - top);
8786
}
8887
else
8988
{

src/Core/src/Platform/Android/ContextExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ static float ToPixelsUsingMetrics(double dp)
122122
return (float)Math.Ceiling((dp * s_displayDensity) - GeometryUtil.Epsilon);
123123
}
124124

125+
internal static (int left, int top, int right, int bottom) ToPixels(this View view, Graphics.Rect rectangle)
126+
{
127+
return
128+
(
129+
(int)view.ToPixels(rectangle.Left),
130+
(int)view.ToPixels(rectangle.Top),
131+
(int)view.ToPixels(rectangle.Right),
132+
(int)view.ToPixels(rectangle.Bottom)
133+
);
134+
}
135+
125136
public static (int left, int top, int right, int bottom) ToPixels(this Context context, Graphics.Rect rectangle)
126137
{
127138
return

src/Core/tests/DeviceTests/Core.DeviceTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<RootNamespace>Microsoft.Maui.DeviceTests</RootNamespace>
88
<AssemblyName>Microsoft.Maui.Core.DeviceTests</AssemblyName>
99
<NoWarn>$(NoWarn),CA1416</NoWarn>
10+
<IsTestProject>true</IsTestProject>
1011
<!-- Disable multi-RID builds to workaround a parallel build issue -->
1112
<RuntimeIdentifier Condition="$(TargetFramework.Contains('-maccatalyst'))">maccatalyst-x64</RuntimeIdentifier>
1213
<RuntimeIdentifier Condition="$(TargetFramework.Contains('-maccatalyst')) and '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'arm64'">maccatalyst-arm64</RuntimeIdentifier>

0 commit comments

Comments
 (0)