diff --git a/Documentation/guides/BuildProcess.md b/Documentation/guides/BuildProcess.md index f9c31f33cc2..ba82e8c81c0 100644 --- a/Documentation/guides/BuildProcess.md +++ b/Documentation/guides/BuildProcess.md @@ -754,6 +754,18 @@ when packaing Release applications. Added in Xamarin.Android 8.3. +- **AndroidEnablePreloadAssemblies** – A boolean property + which changes runtime behavior with relation to the managed assemblies + stored in the application package. When set to `True` (the default) it will + cause the Xamarin.Android runtime to load **all** the assemblies into the + application at the startup, before application code is invoked. This is + required by some Xamarin.Forms applications as well as by various dependency + injection libraries: they depend on `AppDomain.CurrentDomain.GetAssemblies()` + to return the full set of managed assemblies in order to find dependencies, + resources etc. Preloading all the assemblies carries a startup performance + penalty so, whenever possible, developers are encouraged to set this property to + `False` in their project file. + ### Binding Project Build Properties The following MSBuild properties are used with diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 317d546f4c8..868617d5388 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -317,6 +317,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. <_AndroidDesignTimeBuildPropertiesCache>$(_AndroidIntermediateDesignTimeBuildDirectory)build.props False False + + + True @@ -2386,8 +2389,23 @@ because xbuild doesn't support framework reference assemblies. + + + + + + + + diff --git a/src/monodroid/jni/android-system.cc b/src/monodroid/jni/android-system.cc index 13e0689856f..112d727bec5 100644 --- a/src/monodroid/jni/android-system.cc +++ b/src/monodroid/jni/android-system.cc @@ -779,6 +779,16 @@ AndroidSystem::setup_environment (jstring_wrapper& name, jstring_wrapper& value) knownEnvVars.MonoLLVM = true; return; } + + if (strcmp (k, "mono.enable_assembly_preload") == 0) { + if (*v == '\0') + knownEnvVars.EnableAssemblyPreload = KnownEnvironmentVariables::AssemblyPreloadDefault; + else if (v[0] == '1') + knownEnvVars.EnableAssemblyPreload = true; + else + knownEnvVars.EnableAssemblyPreload = false; + return; + } } add_system_property (k, v); diff --git a/src/monodroid/jni/android-system.h b/src/monodroid/jni/android-system.h index d83607140b2..60292395cce 100644 --- a/src/monodroid/jni/android-system.h +++ b/src/monodroid/jni/android-system.h @@ -23,9 +23,12 @@ namespace xamarin { namespace android { namespace internal struct KnownEnvironmentVariables { + static constexpr bool AssemblyPreloadDefault = true; + bool DSOInApk = false; MonoAotMode MonoAOT = MonoAotMode::MONO_AOT_MODE_NONE; bool MonoLLVM = false; + bool EnableAssemblyPreload = AssemblyPreloadDefault; }; class AndroidSystem @@ -133,6 +136,10 @@ namespace xamarin { namespace android { namespace internal #if defined (WINDOWS) int setenv (const char *name, const char *value, int overwrite); #endif + bool is_assembly_preload_enabled () const + { + return knownEnvVars.EnableAssemblyPreload; + } bool is_mono_llvm_enabled () const { diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 719c783de51..d3fd1e3bc39 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -957,7 +957,7 @@ mono_runtime_init (char *runtime_args) } static MonoDomain* -create_domain (JNIEnv *env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jstring assembly, jobject loader, bool is_root_domain) +create_domain (JNIEnv *env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jobject loader, bool is_root_domain) { MonoDomain *domain; int user_assemblies_count = 0;; @@ -1779,6 +1779,58 @@ _monodroid_counters_dump (const char *format, ...) monoFunctions.counters_dump (XA_LOG_COUNTERS, counters); } +static void +load_assembly (MonoDomain *domain, JNIEnv *env, jstring_wrapper &assembly) +{ + timing_period total_time; + if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) + total_time.mark_start (); + + const char *assm_name = assembly.get_cstr (); + MonoAssemblyName *aname; + + aname = monoFunctions.assembly_name_new (assm_name); + + if (domain != monoFunctions.domain_get ()) { + MonoDomain *current = monoFunctions.domain_get (); + monoFunctions.domain_set (domain, FALSE); + monoFunctions.assembly_load_full (aname, NULL, NULL, 0); + monoFunctions.domain_set (current, FALSE); + } else { + monoFunctions.assembly_load_full (aname, NULL, NULL, 0); + } + + monoFunctions.assembly_name_free (aname); + + if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { + total_time.mark_end (); + + timing_diff diff (total_time); + log_info (LOG_TIMING, "Assembly load: %s preloaded; elapsed: %lis:%lu::%lu", assm_name, diff.sec, diff.ms, diff.ns); + } +} + +static void +load_assemblies (MonoDomain *domain, JNIEnv *env, jstring_array_wrapper &assemblies) +{ + timing_period total_time; + if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) + total_time.mark_start (); + + /* skip element 0, as that's loaded in create_domain() */ + for (size_t i = 1; i < assemblies.get_length (); ++i) { + jstring_wrapper &assembly = assemblies [i]; + load_assembly (domain, env, assembly); + } + + if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { + total_time.mark_end (); + + timing_diff diff (total_time); + log_info (LOG_TIMING, "Finished loading assemblies: preloaded %u assemblies; wasted time: %lis:%lu::%lu", assemblies.get_length (), diff.sec, diff.ms, diff.ns); + } +} + static void monodroid_Mono_UnhandledException_internal (MonoException *ex) { @@ -1786,14 +1838,16 @@ monodroid_Mono_UnhandledException_internal (MonoException *ex) } static MonoDomain* -create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jobjectArray assemblies, jobject loader, bool is_root_domain) +create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jstring_array_wrapper &assemblies, jobject loader, bool is_root_domain) { - MonoDomain* domain = create_domain (env, runtimeClass, runtimeApks, reinterpret_cast (env->GetObjectArrayElement (assemblies, 0)), loader, is_root_domain); + MonoDomain* domain = create_domain (env, runtimeClass, runtimeApks, loader, is_root_domain); // When running on desktop, the root domain is only a dummy so don't initialize it if (is_running_on_desktop && is_root_domain) return domain; + if (androidSystem.is_assembly_preload_enabled ()) + load_assemblies (domain, env, assemblies); init_android_runtime (domain, env, runtimeClass, loader); osBridge.add_monodroid_domain (domain); @@ -1804,7 +1858,7 @@ create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wr JNIEXPORT void JNICALL Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava, jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader, - jobjectArray externalStorageDirs, jobjectArray assemblies, jstring packageName, + jobjectArray externalStorageDirs, jobjectArray assembliesJava, jstring packageName, jint apiLevel, jobjectArray environmentVariables) { init_logging_categories (); @@ -1987,6 +2041,7 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject log_info_nocheck (LOG_TIMING, "Runtime.init: Mono runtime init; elapsed: %lis:%lu::%lu", diff.sec, diff.ms, diff.ns); } + jstring_array_wrapper assemblies (env, assembliesJava); /* the first assembly is used to initialize the AppDomain name */ create_and_initialize_domain (env, klass, runtimeApks, assemblies, loader, /*is_root_domain:*/ true); @@ -2070,7 +2125,7 @@ reinitialize_android_runtime_type_manager (JNIEnv *env) } JNIEXPORT jint -JNICALL Java_mono_android_Runtime_createNewContext (JNIEnv *env, jclass klass, jobjectArray runtimeApksJava, jobjectArray assemblies, jobject loader) +JNICALL Java_mono_android_Runtime_createNewContext (JNIEnv *env, jclass klass, jobjectArray runtimeApksJava, jobjectArray assembliesJava, jobject loader) { log_info (LOG_DEFAULT, "CREATING NEW CONTEXT"); reinitialize_android_runtime_type_manager (env); @@ -2078,6 +2133,7 @@ JNICALL Java_mono_android_Runtime_createNewContext (JNIEnv *env, jclass klass, j monoFunctions.jit_thread_attach (root_domain); jstring_array_wrapper runtimeApks (env, runtimeApksJava); + jstring_array_wrapper assemblies (env, assembliesJava); MonoDomain *domain = create_and_initialize_domain (env, klass, runtimeApks, assemblies, loader, /*is_root_domain:*/ false); monoFunctions.domain_set (domain, FALSE); int domain_id = monoFunctions.domain_get_id (domain); diff --git a/tests/Xamarin.Forms-Performance-Integration/App.xaml.cs b/tests/Xamarin.Forms-Performance-Integration/App.xaml.cs index 6a821006fef..09d63f63160 100644 --- a/tests/Xamarin.Forms-Performance-Integration/App.xaml.cs +++ b/tests/Xamarin.Forms-Performance-Integration/App.xaml.cs @@ -2,7 +2,9 @@ using Xamarin.Forms; using Xamarin.Forms.Xaml; +#if !DEBUG [assembly: XamlCompilation (XamlCompilationOptions.Compile)] +#endif namespace Xamarin.Forms.Performance.Integration { diff --git a/tests/Xamarin.Forms-Performance-Integration/Global.css b/tests/Xamarin.Forms-Performance-Integration/Global.css new file mode 100644 index 00000000000..e25f536375d --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Global.css @@ -0,0 +1,5 @@ +.featureHeader { + font-size: 20; + font-style: bold; + margin: 0,20,0,10; +} \ No newline at end of file diff --git a/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.cs b/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.cs deleted file mode 100644 index 42242fc1e59..00000000000 --- a/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -using Xamarin.Forms; - -namespace Xamarin.Forms.Performance.Integration -{ - public class MainPage : TabbedPage - { - public MainPage () - { - Page itemsPage, aboutPage = null; - - switch (Device.RuntimePlatform) { - case Device.iOS: - itemsPage = new NavigationPage (new ItemsPage ()) { - Title = "Browse" - }; - - aboutPage = new NavigationPage (new AboutPage ()) { - Title = "About" - }; - itemsPage.Icon = "tab_feed.png"; - aboutPage.Icon = "tab_about.png"; - break; - default: - itemsPage = new ItemsPage () { - Title = "Browse" - }; - - aboutPage = new AboutPage () { - Title = "About" - }; - break; - } - - Children.Add (itemsPage); - Children.Add (aboutPage); - - Title = Children [0].Title; - } - - protected override void OnCurrentPageChanged () - { - base.OnCurrentPageChanged (); - Title = CurrentPage?.Title ?? string.Empty; - } - } -} diff --git a/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.xaml b/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.xaml new file mode 100644 index 00000000000..00ebf6b73ef --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.xaml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.xaml.cs b/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.xaml.cs new file mode 100644 index 00000000000..0c94b746b0f --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Views/MainPage.xaml.cs @@ -0,0 +1,50 @@ +using System; + +using Xamarin.Forms; + +namespace Xamarin.Forms.Performance.Integration +{ + public partial class MainPage : TabbedPage + { + public MainPage () + { + InitializeComponent (); + + Page itemsPage, aboutPage = null; + + switch (Device.RuntimePlatform) { + case Device.iOS: + itemsPage = new NavigationPage (new ItemsPage ()) { + Title = "Browse" + }; + + aboutPage = new NavigationPage (new AboutPage ()) { + Title = "About" + }; + itemsPage.Icon = "tab_feed.png"; + aboutPage.Icon = "tab_about.png"; + break; + default: + itemsPage = new ItemsPage () { + Title = "Browse" + }; + + aboutPage = new AboutPage () { + Title = "About" + }; + break; + } + + Children.Add (itemsPage); + Children.Add (aboutPage); + + Title = Children [0].Title; + } + + protected override void OnCurrentPageChanged () + { + base.OnCurrentPageChanged (); + Title = CurrentPage?.Title ?? string.Empty; + } + } +} \ No newline at end of file