diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
index b8aa2e2507d..71b370a3cee 100644
--- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
+++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
@@ -204,9 +204,11 @@ static void RunStartupHooks ()
return;
}
- // Pass empty string for diagnosticStartupHooks parameter
- // The method will read STARTUP_HOOKS from AppContext internally
- method.Invoke (null, [ "" ]);
+ // ProcessStartupHooks accepts startup hooks directly via parameter.
+ // It will also read STARTUP_HOOKS from AppContext internally.
+ // Pass DOTNET_STARTUP_HOOKS env var value so it works without needing AppContext setup.
+ string? startupHooks = Environment.GetEnvironmentVariable ("DOTNET_STARTUP_HOOKS");
+ method.Invoke (null, [ startupHooks ?? "" ]);
}
static void SetSynchronizationContext () =>
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
index 9cb93fb2e06..01a64d12687 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
@@ -33,4 +33,5 @@ This file is imported *after* the Microsoft.NET.Sdk/Sdk.targets.
+
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets
new file mode 100644
index 00000000000..b092912c2ef
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+ <_HotReloadAgentAssemblyName>$([System.IO.Path]::GetFileNameWithoutExtension('$(DotNetHotReloadAgentStartupHook)'))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_HotReloadEnvironment Include="DOTNET_STARTUP_HOOKS=$(_HotReloadAgentAssemblyName)" />
+
+
+
+
+ <_HotReloadEnvironment Include="@(DotNetHotReloadAgentEnvironment->'%(Identity)=%(Value)')" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
index 5e4c4a5e1b8..8b29c9a2159 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
@@ -1582,5 +1582,109 @@ public void AppStartsWithManagedMarshalMethodsLookupEnabled ()
Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), 30);
Assert.IsTrue (didLaunch, "Activity should have started.");
}
+
+ [Test]
+ public void DeployHotReloadAgentConfiguration ()
+ {
+ // Create a library project that contains the startup hook
+ var startupHookLib = new XamarinAndroidLibraryProject {
+ ProjectName = "MyStartupHook",
+ Sources = {
+ new BuildItem.Source ("StartupHook.cs") {
+ TextContent = () => @"
+using System;
+
+internal static class StartupHook
+{
+ public static void Initialize ()
+ {
+ Console.WriteLine (""HOTRELOAD_TEST_HOOK_INITIALIZED=true"");
+ }
+}
+"
+ }
+ }
+ };
+
+ var proj = new XamarinAndroidApplicationProject {
+ ProjectName = nameof (DeployHotReloadAgentConfiguration),
+ IsRelease = false,
+ Imports = {
+ // Add a .targets file that simulates what dotnet-watch/IDE would inject
+ new Import (() => "HotReload.targets") {
+ TextContent = () => @"
+
+ MyStartupHook
+
+
+
+
+
+"
+ },
+ }
+ };
+ proj.SetRuntime (AndroidRuntime.CoreCLR);
+ proj.AddReference (startupHookLib);
+ proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", @"
+ Console.WriteLine (""DOTNET_STARTUP_HOOKS="" + Environment.GetEnvironmentVariable(""DOTNET_STARTUP_HOOKS""));
+ Console.WriteLine (""HOTRELOAD_TEST_VAR="" + Environment.GetEnvironmentVariable(""HOTRELOAD_TEST_VAR""));
+ Console.WriteLine (""ANOTHER_VAR="" + Environment.GetEnvironmentVariable(""ANOTHER_VAR""));
+");
+
+ var rootPath = Path.Combine (Root, "temp", TestName);
+ using var libBuilder = CreateDllBuilder (Path.Combine (rootPath, startupHookLib.ProjectName));
+ Assert.IsTrue (libBuilder.Build (startupHookLib), "Library build should have succeeded.");
+
+ using var builder = CreateApkBuilder (Path.Combine (rootPath, proj.ProjectName));
+ builder.Save (proj);
+
+ var projectDirectory = Path.Combine (rootPath, proj.ProjectName);
+ var dotnet = new DotNetCLI (Path.Combine (projectDirectory, proj.ProjectFilePath));
+
+ // Build normally first
+ Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed");
+
+ // Run the DeployHotReloadAgentConfiguration target (hot reload properties come from HotReload.targets)
+ Assert.IsTrue (dotnet.Build (target: "DeployHotReloadAgentConfiguration"),
+ "`dotnet build -t:DeployHotReloadAgentConfiguration` should succeed");
+
+ // Launch the app using adb
+ ClearAdbLogcat ();
+ var result = AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity");
+ Assert.IsTrue (result.Contains ("Starting: Intent"), $"Activity should have launched. adb output:\n{result}");
+
+ bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity",
+ Path.Combine (projectDirectory, "logcat.log"), 30);
+ Assert.IsTrue (didLaunch, "Activity should have started.");
+
+ var logcatOutput = File.ReadAllText (Path.Combine (projectDirectory, "logcat.log"));
+
+ // Verify the startup hook was set via DOTNET_STARTUP_HOOKS
+ StringAssert.Contains (
+ "DOTNET_STARTUP_HOOKS=MyStartupHook",
+ logcatOutput,
+ "DOTNET_STARTUP_HOOKS should be set to MyStartupHook"
+ );
+
+ // Verify the startup hook was called
+ StringAssert.Contains (
+ "HOTRELOAD_TEST_HOOK_INITIALIZED=true",
+ logcatOutput,
+ "StartupHook.Initialize() should have been called"
+ );
+
+ // Verify the additional env vars from DotNetHotReloadAgentEnvironment were set
+ StringAssert.Contains (
+ "HOTRELOAD_TEST_VAR=TestValue123",
+ logcatOutput,
+ "HOTRELOAD_TEST_VAR should be set from DotNetHotReloadAgentEnvironment"
+ );
+ StringAssert.Contains (
+ "ANOTHER_VAR=AnotherValue456",
+ logcatOutput,
+ "ANOTHER_VAR should be set from DotNetHotReloadAgentEnvironment"
+ );
+ }
}
}
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
index 8047c3713ef..eb93a305fa7 100644
--- a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
@@ -52,11 +52,12 @@
true
-
+
-
+
+
@@ -231,7 +232,8 @@
-
+
+
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/System/StartupHookTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/System/StartupHookTest.cs
index f58bf1b48ab..02dc861eb82 100644
--- a/tests/Mono.Android-Tests/Mono.Android-Tests/System/StartupHookTest.cs
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/System/StartupHookTest.cs
@@ -7,6 +7,20 @@ namespace SystemTests
[TestFixture]
public class StartupHookTest
{
+ [Test]
+ public void FeatureFlagIsEnabled ()
+ {
+ // NOTE: this is set to true in tests\Mono.Android-Tests\Mono.Android-Tests\Mono.Android.NET-Tests.csproj
+ Assert.IsTrue (Microsoft.Android.Runtime.RuntimeFeature.StartupHookSupport, "RuntimeFeature.StartupHookSupport should be true");
+ }
+
+ [Test]
+ public void EnvironmentVariableIsSet ()
+ {
+ var value = Environment.GetEnvironmentVariable ("DOTNET_STARTUP_HOOKS");
+ Assert.AreEqual ("StartupHook", value, "DOTNET_STARTUP_HOOKS should be set to 'StartupHook'");
+ }
+
[Test]
public void IsInitialized ()
{
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt b/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt
index d3141692f53..cb897b79238 100644
--- a/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt
@@ -1,4 +1,3 @@
# Environment Variables and system properties
# debug.mono.log=gref,default
debug.mono.debug=1
-DOTNET_STARTUP_HOOKS=StartupHook
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/hotreload.env b/tests/Mono.Android-Tests/Mono.Android-Tests/hotreload.env
new file mode 100644
index 00000000000..2c9605d07dd
--- /dev/null
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/hotreload.env
@@ -0,0 +1 @@
+DOTNET_STARTUP_HOOKS=StartupHook