Repository navigation
Description
Steps to Reproduce
Given code like the following:
// Some static dictionary somewhere:
public static Dictionary<string, Func<object>> Services { get; } = new Dictionary<string, Func<object>>();
// From MainActivity.cs;
Services.Add(nameof(BarServices.Bar), CreateBar);
Services.Add(nameof(FooServices.Foo), CreateFoo);And given that the two methods are defined to explicitly not be inlined or otherwise loaded until invoked:
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static object CreateBar() => new BarServices.Bar();
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static object CreateFoo() => new FooServices.Foo();And given FooServices.dll and BarServices.dll are two completely independent assemblies from the main app's, it would be highly desirable for the assemblies to only be loaded when the dictionary entry is accessed and the delegate is invoked, instead of happening on app start, which slows down the process "unnecessarily".
The ~same code in a .NET app properly lazy loads the assemblies as needed:
public static Dictionary<string, Func<object>> Services { get; } = new Dictionary<string, Func<object>>(StringComparer.OrdinalIgnoreCase);
static void Main(string[] args)
{
// Both this approach as well as the inline lambda properly delay-load the assemblies
//Services.Add(nameof(BarServices.Bar), CreateBar);
//Services.Add(nameof(FooServices.Foo), CreateFoo);
Services.Add(nameof(BarServices.Bar), () => new BarServices.Bar());
Services.Add(nameof(FooServices.Foo), () => new FooServices.Foo());
var line = "";
while ((line = Console.ReadLine()).Length > 0)
{
Console.WriteLine(Services[line.Trim()].Invoke().ToString());
}
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static object CreateBar() => new BarServices.Bar();
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static object CreateFoo() => new FooServices.Foo();This is important for apps that use assembly-partitioning to split logic across modules, which should only be loaded whenever needed (i.e. when the UI that consumes them is navigated to, say).
Expected Behavior
Assemblies are lazy-loaded.
Actual Behavior
Both assemblies are loaded up-front:
03-19 15:36:33.243 nexus_5x Debug 13769 Mono Assembly FooServices[0x9de4ed60] added to domain RootDomain, ref_count=1
Version Information
Currently verified against 15.7 pre2
Activity
kzu commentedon Mar 19, 2018
Related: https://gitter.im/mono/mono?at=5ab00b4327c509a7747ed888
jonpryor commentedon Mar 19, 2018
Related: https://github.com/xamarin/xamarin-android/blob/8c210a0b5d4b3bd33c6bd2cfb010260c65fed1b3/src/monodroid/jni/monodroid-glue.c#L3684-L3700
From the original commit introducing the
register_packages()function:The mentioned
f0dad0a3commit has this to say:jonpryor commentedon Mar 19, 2018
Summarizing the above, here's the scenario:
You have a Java library which exports a
com.example.MyViewtype which inheritsandroid.view.View.The type from (1) is bound into an
Example.MyViewtype inExample.dllThe type from (1) is used in
Resources\layout\Main.axmlwithin the App, a'laThe App has code which calls:
Note: non-generic
Activity.FindViewById()invocation.The expectation is that
Activity.FindViewById()will return theExample.MyViewbinding type. In order for that to happen, Xamarin.Android needs to know that theExample.MyViewtype exists, possibly before it is instantiated from C# code (which may never happen; all use could be viaActivity.FindViewById()!).The chosen implementation strategy was to load all assemblies during process startup, look for the
Java.Interop.__TypeRegistrationstype within the assembly, and -- if found -- invokeJava.Interop. __TypeRegistrations.RegisterPackages(). This allowsMono.Android.dllto know thatExample.MyViewexists and use it for type mappings.jonpryor commentedon Jul 2, 2018
I believe that the previous description is now -- and has been! -- moot, since the introduction of "type maps" in 2015-Apr-22 and subsequent fixes.
The
typemap.jmandtypemap.mjfiles should address the scenario described, without requiring any startup assembly loading.Additionally, allow assemblies to be loaded on-demand -- instead of all at once, during process startup -- should help with our startup times.
jonpryor commentedon Jul 2, 2018
Related: #1829
jonpryor commentedon Jul 2, 2018
The fix for "all assemblies are loaded on startup" is to remove
register_packages(), and make sure that nothing breaks. :-)kzu commentedon Jul 11, 2018
Awesome news @jonpryor 💯 . I think this could have quite an impact in terms of app startup!
[monodroid] Runtime startup performance improvements (#2515)
claudioredi commentedon Nov 5, 2019
@jonpryor Was this already released on stable channel?
jonpryor commentedon Mar 9, 2022
@claudioredi: yes, this was released as part of Visual Studio 16.1 / Xamarin.Android 9.2. See also:
Note that
$(AndroidEnablePreloadAssemblies)is True by default in "Classic" Xamarin.Android, and False by default in .NET 6.