diff --git a/Documentation/guides/BuildProcess.md b/Documentation/guides/BuildProcess.md
index f9c31f33cc2..5e21e91ca09 100644
--- a/Documentation/guides/BuildProcess.md
+++ b/Documentation/guides/BuildProcess.md
@@ -754,6 +754,30 @@ when packaing Release applications.
Added in Xamarin.Android 8.3.
+- **AndroidEnablePreloadAssemblies** – A boolean property which controls
+ whether or not all managed assemblies bundled within the application package
+ are loaded during process startup or not.
+
+ When set to `True`, all assemblies bundled within the application package
+ will be loaded during process startup, before any application code is invoked.
+ This is consistent with what Xamarin.Android did in releases prior to
+ Xamarin.Andorid 9.2.
+
+ When set to `False`, assemblies will only be loaded on an as-needed basis.
+ This allows applications to startup faster, and is also more consistent with
+ desktop .NET semantics. To see the time savings, set the `debug.mono.log`
+ System Property to include `timing`, and look for the
+ `Finished loading assemblies: preloaded` message within `adb logcat`.
+
+ Applications or libraries which use dependency injection may *require* that
+ this property be `True` if they in turn require that
+ `AppDomain.CurrentDomain.GetAssemblies()` return all assemblies within the
+ application bundle, even if the assembly wouldn't otherwise have been needed.
+
+ By default this value will be set to `True`.
+
+ Added in Xamarin.Android 9.2.
+
### 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