Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[runtime] Optionally preload all the assemblies from the apk (#2724)
Context: xamarin/Xamarin.Forms#5164 Commit b90d3ab altered how assemblies were loaded during process startup in an effort to reduce time spent in `Runtime.init()`: assembly loading has unavoidable overhead. Reducing the number of assemblies loaded results in less time spent in `Runtime.init()`. This change also meant that the semantics of `System.AppDomain.GetAssemblies()` changed: prior to b90d3ab, `AppDomain.CurrentDomain.GetAssemblies()` would return *all* assemblies present within the `.apk`. Starting with b90d3ab, `AppDomain.CurrentDomain.GetAssemblies()` would *instead* return only the assemblies which had been loaded *up to that point in time*, which may not be "everything". We honestly didn't spend too much time thinking about this particular change. The b90d3ab behavior is what desktop .NET has done since the beginning -- which makes sense, as desktop .NET didn't have a concept of "application" for which one could ask for "all assemblies that make up the application" -- so why would this break anything? Anything broken in such an environment would fail on .NET. Turns Out™ that there are mobile frameworks which assume that `AppDomain.CurrentDomain.GetAssemblies()` will return all known assemblies, and thus *break* with b90d3ab, including: * [Xamarin.Forms][0] * [MvvmCross][1] Which brings us to [Xamarin.Forms Issue #5164][2], in which a Xamarin.Forms app which uses CSS crashes with an NRE: UNHANDLED EXCEPTION: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NullReferenceException: Object reference not set to an instance of an object. at Xamarin.Forms.Xaml.ApplyPropertiesVisitor.ProvideValue (System.Object& value, Xamarin.Forms.Xaml.ElementNode node, System.Object source, Xamarin.Forms.Xaml.XmlName propertyName) [0x000a7] in D:\a\1\s\Xamarin.Forms.Xaml\ApplyPropertiesVisitor.cs:243 at Xamarin.Forms.Xaml.ApplyPropertiesVisitor.Visit (Xamarin.Forms.Xaml.ElementNode node, Xamarin.Forms.Xaml.INode parentNode) [0x000c9] in D:\a\1\s\Xamarin.Forms.Xaml\ApplyPropertiesVisitor.cs:107 at Xamarin.Forms.Xaml.ElementNode.Accept (Xamarin.Forms.Xaml.IXamlNodeVisitor visitor, Xamarin.Forms.Xaml.INode parentNode) [0x000ac] in D:\a\1\s\Xamarin.Forms.Xaml\XamlNode.cs:149 at Xamarin.Forms.Xaml.RootNode.Accept (Xamarin.Forms.Xaml.IXamlNodeVisitor visitor, Xamarin.Forms.Xaml.INode parentNode) [0x00044] in D:\a\1\s\Xamarin.Forms.Xaml\XamlNode.cs:200 at Xamarin.Forms.Xaml.XamlLoader.Visit (Xamarin.Forms.Xaml.RootNode rootnode, Xamarin.Forms.Xaml.HydrationContext visitorContext, System.Boolean useDesignProperties) [0x0008b] in D:\a\1\s\Xamarin.Forms.Xaml\XamlLoader.cs:150 at Xamarin.Forms.Xaml.XamlLoader.Load (System.Object view, System.String xaml, System.Boolean useDesignProperties) [0x00058] in D:\a\1\s\Xamarin.Forms.Xaml\XamlLoader.cs:91 at Xamarin.Forms.Xaml.XamlLoader.Load (System.Object view, System.Type callingType) [0x00028] in D:\a\1\s\Xamarin.Forms.Xaml\XamlLoader.cs:69 at Xamarin.Forms.Xaml.Extensions.LoadFromXaml[TXaml] (TXaml view, System.Type callingType) [0x00000] in D:\a\1\s\Xamarin.Forms.Xaml\ViewExtensions.cs:36 at TheLittleThingsPlayground.Views.ThreeFivePage.InitializeComponent () [0x00001] in H:\github\TheLittleThingsPlayground\TheLittleThingsPlayground\obj\Debug g.cs:21 at TheLittleThingsPlayground.Views.ThreeFivePage..ctor () [0x00008] in H:\github\TheLittleThingsPlayground\TheLittleThingsPlayground\Views\ThreeFivePage.xaml.cs:12 at (wrapper managed-to-native) System.Reflection.MonoCMethod.InternalInvoke(System.Reflection.MonoCMethod,object,object[],System.Exception&) at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters, System.Boolean wrapExceptions) [0x00005] in <84c6975c2cbc47b489a2a76477d7a312>:0 The `Xamarin.Forms.Xaml.dll` assembly isn't loaded when the `ThreeFivePage` type is constructed, which eventually means that a call to `DependencyService.Get<IResourcesLoader>()` returns `null`, because `Xamarin.Forms.Xaml.ResourcesLoader, Xamarin.Forms.Xaml` was not found or registered as an implementation for `IResourcesLoader`. There is a *workaround* for a post-b90d3ab7 environment: explicitly load the required assembly/assemblies earlier in the process: // In your MainActivity.cs protected override void OnCreate(Bundle bundle) { // Workaround Assembly.Load("Xamarin.Forms.Xaml"); base.OnCreate(bundle); global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new App()); } However, while there may be a workaround, this semantic change has unknown impact on the overall ecosystem and environment. Apps WILL break because of this change. We could revert the assembly loading portion of b90d3ab, but that means *all* apps need to pay the cost of loading all assemblies, even if they don't need to pay that startup cost, a cost of ~30ms for the `tests/Xamarin.Forms-Performance-Integration` app. Instead, we'll enable the new functionality to be "opt-in", and make the previous behavior the default. Add a new `$(AndroidEnablePreloadAssemblies)` MSBuild property which, when True, restores the previous behavior of loading all assemblies within the `.apk` during process startup. The default value is True. If the `debug.mono.log` system property is set and contains `timing`, `adb logcat` will contain messages regarding how much time was spent loading each assembly, and the sum total for all assemblies: monodroid-timing: Assembly load: FormsViewGroup.dll preloaded; elapsed: 0s:1::267136 monodroid-timing: Assembly load: Newtonsoft.Json.dll preloaded; elapsed: 0s:0::999584 ... monodroid-timing: Finished loading assemblies: preloaded 29 assemblies; elapsed: 0s:26::783805 Finally, update `tests/Xamarin.Forms-Performance-Integration` so that it uses CSS, and thus hits the Xamarin.Forms codepath that triggers the above `NullReferenceException. This should validate that the fix is correct, and help prevent future regressions. [0]: https://github.com/xamarin/Xamarin.Forms/blob/cd1cf19fcad6ae049e90fdc5819c4f7c6adb57f6/Xamarin.Forms.Platform.Android/Forms.cs#L417 [1]: https://github.com/MvvmCross/MvvmCross/blob/43c1c7848d482adbc2bdc984746f36d74f64c190/MvvmCross/Core/MvxSetup.cs#L34 [2]: xamarin/Xamarin.Forms#5164
- Loading branch information