From 4e526dbdd64698da3c548ee2c4a3caf4e2b290ab Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 17 Feb 2020 18:40:58 +0100 Subject: [PATCH] Startup performance improvements (less reflection) Java.Interop uses the `JniAddNativeMethodRegistrationAttribute` custom attribute to determine whether to register native methods for a given type. It turns out that in most cases there isn't a single instance of this attribute throughout the application and all the calls to `GetCustomAttribute` end up returning nothing while incurring a large performance penalty on application startup. Eliminate the call after checking that no type in the application is decorated with the above custom attribute. Another observation made while profiling a couple of XA applications was that the installation of the uncaught exception handler with `Java.Lang.Thread` was quite expensive while, in most cases, completely not useful during startup (and, hopefully, during application lifetime). Instead of registering a managed type to handle the exceptions, register the handler in Java which then calls down to Xamarin.Android native runtime which, in turn, propagates the exception to the managed code. Gains in startup time are quite nice: Device name: **Pixel 3 XL** Device architecture: **arm64-v8a** Number of test runs: **10** Test application: **Xamarin.Forms integration test** | | **Native to managed** | **Runtime init** | **Displayed** | **Notes** | |-----------------|------------------------|------------------|---------------|--------------------------------| | **master** | 131.278 | 149.074 | 789.10 | preload enabled; 32-bit build | | **this commit** | 49.446 | 66.989 | 764.30 | | | **master** | 132.315 | 147.187 | 795.60 | preload disabled; 32-bit build | | **this commit** | 48.876 | 63.822 | 754.30 | | | **master** | 121.544 | 137.736 | 728.20 | preload enabled; 64-bit build | | **this commit** | 45.350 | 61.464 | 699.50 | | | **master** | 123.448 | 137.052 | 727.40 | preload disabled; 64-bit build | | **this commit** | 44.765 | 58.047 | 689.00 | | Device name: **Pixel 3 XL** Device architecture: **arm64-v8a** Number of test runs: **10** Test application: Xamarin.Forms "Hello World" app with one label | | **Native to managed** | **Runtime init** | **Displayed** | **Notes** | |-----------------|------------------------|------------------|---------------|--------------------------------| | **master** | 122.090 | 142.004 | 639.00 | preload enabled; 32-bit build | | **this commit** | 44.370 | 63.803 | 586.10 | | | **master** | 121.110 | 134.378 | 634.20 | preload disabled; 32-bit build | | **this commit** | 45.085 | 57.992 | 580.40 | | | **master** | 120.973 | 141.235 | 637.20 | preload enabled; 64-bit build | | **this commit** | 44.767 | 63.846 | 578.50 | | | **master** | 120.785 | 134.588 | 627.00 | preload disabled; 64-bit build | | **this commit** | 44.859 | 57.590 | 575.40 | | --- Documentation/release-notes/4302.md | 11 +++ .../Android.Runtime/AndroidRuntime.cs | 37 ++++++++-- src/Mono.Android/Android.Runtime/JNIEnv.cs | 67 +++++++++++++----- .../UncaughtExceptionHandler.cs | 69 ------------------- .../Android.Runtime/XAPeerMembers.cs | 4 +- src/Mono.Android/Java.Interop/TypeManager.cs | 2 +- src/Mono.Android/Mono.Android.csproj | 1 - .../Test/Mono.Android-Tests.csproj | 1 + .../Tasks/GenerateJavaStubs.cs | 5 +- .../Tasks/GeneratePackageManagerJava.cs | 2 + .../Xamarin.Android.Build.Tests/BuildTest.cs | 2 +- .../Utilities/EnvironmentHelper.cs | 18 +++-- ...pplicationConfigNativeAssemblyGenerator.cs | 4 ++ .../Utilities/ApplicationConfigTaskState.cs | 9 +++ .../Utilities/TypeMapGenerator.cs | 22 +++++- .../Xamarin.Android.Common.targets | 4 +- .../java/mono/android/Runtime.java | 25 ++++++- src/monodroid/jni/application_dso_stub.cc | 1 + src/monodroid/jni/monodroid-glue-internal.hh | 1 + src/monodroid/jni/monodroid-glue.cc | 1 + src/monodroid/jni/xamarin-app.hh | 1 + .../Mono.Android-TestsAppBundle.csproj | 1 + .../Mono.Android-TestsMultiDex.csproj | 1 + 23 files changed, 179 insertions(+), 110 deletions(-) create mode 100644 Documentation/release-notes/4302.md delete mode 100644 src/Mono.Android/Android.Runtime/UncaughtExceptionHandler.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigTaskState.cs diff --git a/Documentation/release-notes/4302.md b/Documentation/release-notes/4302.md new file mode 100644 index 00000000000..13428ebdac3 --- /dev/null +++ b/Documentation/release-notes/4302.md @@ -0,0 +1,11 @@ +### App startup performance + + * [GitHub PR 4302](https://github.com/xamarin/xamarin-android/pull/4302): + Avoid unneeded calls to `GetCustomAttribute()` during app startup for the + common case where an app has no types decorated with the + `[JniAddNativeMethodRegistration]` attribute. Additionally, instead of + using a managed method to propagate uncaught exceptions from Java, use a + Java method that calls into the unmanaged Xamarin.Android runtime. These + changes reduced the time to display the first screen of a small test + Xamarin.Forms app from about 730 milliseconds to about 700 milliseconds for + a Release configuration build on a Google Pixel 3 XL device. diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index f23cf9f5358..22d4cca155d 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -15,8 +15,18 @@ namespace Android.Runtime { class AndroidRuntime : JniRuntime { - internal AndroidRuntime (IntPtr jnienv, IntPtr vm, bool allocNewObjectSupported, IntPtr classLoader, IntPtr classLoader_loadClass) - : base (new AndroidRuntimeOptions (jnienv, vm, allocNewObjectSupported, classLoader, classLoader_loadClass)) + internal AndroidRuntime (IntPtr jnienv, + IntPtr vm, + bool allocNewObjectSupported, + IntPtr classLoader, + IntPtr classLoader_loadClass, + bool jniAddNativeMethodRegistrationAttributePresent) + : base (new AndroidRuntimeOptions (jnienv, + vm, + allocNewObjectSupported, + classLoader, + classLoader_loadClass, + jniAddNativeMethodRegistrationAttributePresent)) { } @@ -67,8 +77,12 @@ public override void RaisePendingException (Exception pendingException) } class AndroidRuntimeOptions : JniRuntime.CreationOptions { - - public AndroidRuntimeOptions (IntPtr jnienv, IntPtr vm, bool allocNewObjectSupported, IntPtr classLoader, IntPtr classLoader_loadClass) + public AndroidRuntimeOptions (IntPtr jnienv, + IntPtr vm, + bool allocNewObjectSupported, + IntPtr classLoader, + IntPtr classLoader_loadClass, + bool jniAddNativeMethodRegistrationAttributePresent) { EnvironmentPointer = jnienv; ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global); @@ -76,9 +90,10 @@ public AndroidRuntimeOptions (IntPtr jnienv, IntPtr vm, bool allocNewObjectSuppo InvocationPointer = vm; NewObjectRequired = !allocNewObjectSupported; ObjectReferenceManager = new AndroidObjectReferenceManager (); - TypeManager = new AndroidTypeManager (); + TypeManager = new AndroidTypeManager (jniAddNativeMethodRegistrationAttributePresent); ValueManager = new AndroidValueManager (); UseMarshalMemberBuilder = false; + JniAddNativeMethodRegistrationAttributePresent = jniAddNativeMethodRegistrationAttributePresent; } } @@ -219,6 +234,12 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value) } class AndroidTypeManager : JniRuntime.JniTypeManager { + bool jniAddNativeMethodRegistrationAttributePresent; + + public AndroidTypeManager (bool jniAddNativeMethodRegistrationAttributePresent) + { + this.jniAddNativeMethodRegistrationAttributePresent = jniAddNativeMethodRegistrationAttributePresent; + } protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) { @@ -347,13 +368,15 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, stri return; if (string.IsNullOrEmpty (methods)) { - base.RegisterNativeMembers (nativeClass, type, methods); + if (jniAddNativeMethodRegistrationAttributePresent) + base.RegisterNativeMembers (nativeClass, type, methods); return; } string[] members = methods.Split ('\n'); if (members.Length < 2) { - base.RegisterNativeMembers (nativeClass, type, methods); + if (jniAddNativeMethodRegistrationAttributePresent) + base.RegisterNativeMembers (nativeClass, type, methods); return; } diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 4b3bc8d3bf8..aa356c88d38 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -33,6 +33,7 @@ struct JnienvInitializeArgs { public byte brokenExceptionTransitions; public int packageNamingPolicy; public byte ioExceptionType; + public int jniAddNativeMethodRegistrationAttributePresent; } #pragma warning restore 0649 @@ -54,7 +55,6 @@ public static partial class JNIEnv { internal static int gref_gc_threshold; internal static bool PropagateExceptions; - static UncaughtExceptionHandler defaultUncaughtExceptionHandler; internal static bool IsRunningOnDesktop; internal static bool LogTypemapMissStackTrace; @@ -173,7 +173,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) Mono.SystemDependencyProvider.Initialize (); BoundExceptionType = (BoundExceptionType)args->ioExceptionType; - androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass); + androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; AllocObjectSupported = androidSdkVersion > 10; @@ -183,12 +183,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) PropagateExceptions = args->brokenExceptionTransitions == 0; - if (PropagateExceptions) { - defaultUncaughtExceptionHandler = new UncaughtExceptionHandler (Java.Lang.Thread.DefaultUncaughtExceptionHandler); - if (!IsRunningOnDesktop) - Java.Lang.Thread.DefaultUncaughtExceptionHandler = defaultUncaughtExceptionHandler; - } - JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; if (IsRunningOnDesktop) { string packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__"); @@ -204,13 +198,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) internal static void Exit () { - /* Reset uncaught exception handler so that we don't mistakenly reuse a - * now-invalid handler the next time we reinitialize JNIEnv. - */ - var uncaughtExceptionHandler = Java.Lang.Thread.DefaultUncaughtExceptionHandler as UncaughtExceptionHandler; - if (uncaughtExceptionHandler != null && uncaughtExceptionHandler == defaultUncaughtExceptionHandler) - Java.Lang.Thread.DefaultUncaughtExceptionHandler = uncaughtExceptionHandler.DefaultHandler; - /* Manually dispose surfaced objects and close the current JniEnvironment to * avoid ObjectDisposedException thrown on finalizer threads after shutdown */ @@ -249,15 +236,59 @@ static void ManualJavaObjectDispose (Java.Lang.Object obj) GC.SuppressFinalize (obj); } + static Action mono_unhandled_exception; + static Action AppDomain_DoUnhandledException; + + static void Initialize () + { + if (mono_unhandled_exception == null) { + var mono_UnhandledException = typeof (System.Diagnostics.Debugger) + .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); + mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), mono_UnhandledException); + } + + if (AppDomain_DoUnhandledException == null) { + var ad_due = typeof (AppDomain) + .GetMethod ("DoUnhandledException", + bindingAttr: BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + types: new []{typeof (UnhandledExceptionEventArgs)}, + modifiers: null); + if (ad_due != null) { + AppDomain_DoUnhandledException = (Action) Delegate.CreateDelegate ( + typeof (Action), ad_due); + } + } + } + internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPtr, IntPtr javaExceptionPtr) { - if (defaultUncaughtExceptionHandler == null) + if (!PropagateExceptions) return; - var javaThread = JavaObject.GetObject (env, javaThreadPtr, JniHandleOwnership.DoNotTransfer); + try { + Initialize (); + } catch (Exception e) { + Android.Runtime.AndroidEnvironment.FailFast ($"Unable to initialize UncaughtExceptionHandler. Nested exception caught: {e}"); + } + var javaException = JavaObject.GetObject (env, javaExceptionPtr, JniHandleOwnership.DoNotTransfer); - defaultUncaughtExceptionHandler.UncaughtException (javaThread, javaException); + // Disabled until Linker error surfaced in https://github.com/xamarin/xamarin-android/pull/4302#issuecomment-596400025 is resolved + //System.Diagnostics.Debugger.Mono_UnhandledException (javaException); + mono_unhandled_exception (javaException); + + try { + var jltp = javaException as JavaProxyThrowable; + Exception innerException = jltp?.InnerException; + var args = new UnhandledExceptionEventArgs (innerException ?? javaException, isTerminating: true); + + // Disabled until Linker error surfaced in https://github.com/xamarin/xamarin-android/pull/4302#issuecomment-596400025 is resolved + //AppDomain.CurrentDomain.DoUnhandledException (args); + AppDomain_DoUnhandledException (AppDomain.CurrentDomain, args); + } catch (Exception e) { + Logger.Log (LogLevel.Error, "monodroid", "Exception thrown while raising AppDomain.UnhandledException event: " + e.ToString ()); + } } [DllImport ("__Internal", CallingConvention = CallingConvention.Cdecl)] diff --git a/src/Mono.Android/Android.Runtime/UncaughtExceptionHandler.cs b/src/Mono.Android/Android.Runtime/UncaughtExceptionHandler.cs deleted file mode 100644 index 57cd49911a1..00000000000 --- a/src/Mono.Android/Android.Runtime/UncaughtExceptionHandler.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Reflection; - -namespace Android.Runtime { - - sealed class UncaughtExceptionHandler : Java.Lang.Object, Java.Lang.Thread.IUncaughtExceptionHandler { - - Action mono_unhandled_exception; - - Action AppDomain_DoUnhandledException; - - Java.Lang.Thread.IUncaughtExceptionHandler defaultHandler; - - public UncaughtExceptionHandler (Java.Lang.Thread.IUncaughtExceptionHandler defaultHandler) - { - this.defaultHandler = defaultHandler; - } - - internal Java.Lang.Thread.IUncaughtExceptionHandler DefaultHandler { - get { return defaultHandler; } - } - - public void UncaughtException (Java.Lang.Thread thread, Java.Lang.Throwable ex) - { - try { - Initialize (); - } catch (Exception e) { - Android.Runtime.AndroidEnvironment.FailFast ($"Unable to initialize UncaughtExceptionHandler. Nested exception caught: {e}"); - } - - mono_unhandled_exception (ex); - if (AppDomain_DoUnhandledException != null) { - try { - var jltp = ex as JavaProxyThrowable; - Exception innerException = jltp?.InnerException; - var args = new UnhandledExceptionEventArgs (innerException ?? ex, isTerminating: true); - AppDomain_DoUnhandledException (AppDomain.CurrentDomain, args); - } - catch (Exception e) { - Logger.Log (LogLevel.Error, "monodroid", "Exception thrown while raising AppDomain.UnhandledException event: " + e.ToString ()); - } - } - if (defaultHandler != null) - defaultHandler.UncaughtException (thread, ex); - } - - void Initialize () - { - if (mono_unhandled_exception == null) { - var mono_UnhandledException = typeof (System.Diagnostics.Debugger) - .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); - mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), mono_UnhandledException); - } - - if (AppDomain_DoUnhandledException == null) { - var ad_due = typeof (AppDomain) - .GetMethod ("DoUnhandledException", - bindingAttr: BindingFlags.NonPublic | BindingFlags.Instance, - binder: null, - types: new []{typeof (UnhandledExceptionEventArgs)}, - modifiers: null); - if (ad_due != null) { - AppDomain_DoUnhandledException = (Action) Delegate.CreateDelegate ( - typeof (Action), ad_due); - } - } - } - } -} diff --git a/src/Mono.Android/Android.Runtime/XAPeerMembers.cs b/src/Mono.Android/Android.Runtime/XAPeerMembers.cs index a8a75e315e2..23bc4ebce01 100644 --- a/src/Mono.Android/Android.Runtime/XAPeerMembers.cs +++ b/src/Mono.Android/Android.Runtime/XAPeerMembers.cs @@ -7,7 +7,7 @@ namespace Android.Runtime { public class XAPeerMembers : JniPeerMembers { - static Dictionary LegacyPeerMembers = new Dictionary (); + static Dictionary LegacyPeerMembers = new Dictionary (StringComparer.Ordinal); public XAPeerMembers (string jniPeerTypeName, Type managedPeerType) : base (jniPeerTypeName, managedPeerType) @@ -73,4 +73,4 @@ static IntPtr GetThresholdClass (IJavaPeerable value) return IntPtr.Zero; } } -} \ No newline at end of file +} diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index ebab6f33f49..555df07677c 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -23,7 +23,7 @@ static class TypeManagerMapDictionaries public static Dictionary JniToManaged { get { if (_jniToManaged == null) - _jniToManaged = new Dictionary (); + _jniToManaged = new Dictionary (StringComparer.Ordinal); return _jniToManaged; } } diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index f6851d03598..513de23cd49 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -234,7 +234,6 @@ - diff --git a/src/Mono.Android/Test/Mono.Android-Tests.csproj b/src/Mono.Android/Test/Mono.Android-Tests.csproj index 76e6a21f8fc..d841ef19104 100644 --- a/src/Mono.Android/Test/Mono.Android-Tests.csproj +++ b/src/Mono.Android/Test/Mono.Android-Tests.csproj @@ -22,6 +22,7 @@ ..\..\..\product.snk v10.0 d8 + <_SkipJniAddNativeMethodRegistrationAttributeScan>True diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 11d27543278..2570de86a1d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -77,6 +77,8 @@ public class GenerateJavaStubs : AndroidTask public string ApplicationJavaClass { get; set; } + public bool SkipJniAddNativeMethodRegistrationAttributeScan { get; set; } + [Output] public string [] GeneratedBinaryTypeMaps { get; set; } @@ -400,9 +402,10 @@ void SaveResource (string resource, string filename, string destDir, Func types) { var tmg = new TypeMapGenerator ((string message) => Log.LogDebugMessage (message), SupportedAbis); - if (!tmg.Generate (types, TypemapOutputDirectory, GenerateNativeAssembly)) + if (!tmg.Generate (SkipJniAddNativeMethodRegistrationAttributeScan, types, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState)) throw new XamarinAndroidException (4308, Properties.Resources.XA4308); GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray (); + BuildEngine4.RegisterTaskObject (ApplicationConfigTaskState.RegisterTaskObjectKey, appConfState, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: false); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 8a597557228..47e3fd9c952 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -249,6 +249,7 @@ void AddEnvironment () throw new InvalidOperationException ($"Unsupported BoundExceptionType value '{BoundExceptionType}'"); } + var appConfState = BuildEngine4.GetRegisteredTaskObject (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build) as ApplicationConfigTaskState; foreach (string abi in SupportedAbis) { NativeAssemblerTargetProvider asmTargetProvider; string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{abi.ToLowerInvariant ()}"); @@ -285,6 +286,7 @@ void AddEnvironment () PackageNamingPolicy = pnp, BoundExceptionType = boundExceptionType, InstantRunEnabled = InstantRunEnabled, + JniAddNativeMethodRegistrationAttributePresent = appConfState != null ? appConfState.JniAddNativeMethodRegistrationAttributePresent : false, }; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs index f62d73b5ea7..388d25f57ef 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs @@ -1099,7 +1099,7 @@ public void BuildProguardEnabledProject ([Values (true, false)] bool isRelease, FileAssert.Exists (dexFile); var classes = new [] { "Lmono/MonoRuntimeProvider;", - "Landroid/runtime/UncaughtExceptionHandler;", + "Landroid/runtime/JavaProxyThrowable;", "Landroid/support/v7/widget/Toolbar;" }; foreach (var className in classes) { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index a81b9aefbe7..53365211cf0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -23,13 +23,14 @@ public sealed class ApplicationConfig public bool is_a_bundled_app; public bool broken_exception_transitions; public bool instant_run_enabled; + public bool jni_add_native_method_registration_attribute_present; public byte bound_stream_io_exception_type; public uint package_naming_policy; public uint environment_variable_count; public uint system_property_count; public string android_package_name; }; - const uint ApplicationConfigFieldCount = 11; + const uint ApplicationConfigFieldCount = 12; static readonly object ndkInitLock = new object (); static readonly char[] readElfFieldSeparator = new [] { ' ', '\t' }; @@ -143,27 +144,32 @@ static ApplicationConfig ReadApplicationConfig (string envFile) ret.instant_run_enabled = ConvertFieldToBool ("instant_run_enabled", envFile, i, field [1]); break; - case 6: // bound_stream_io_exception_type: byte / .byte + case 6: // jni_add_native_method_registration_attribute_present: bool / .byte + AssertFieldType (envFile, ".byte", field [0], i); + ret.jni_add_native_method_registration_attribute_present = ConvertFieldToBool ("jni_add_native_method_registration_attribute_present", envFile, i, field [1]); + break; + + case 7: // bound_stream_io_exception_type: byte / .byte AssertFieldType (envFile, ".byte", field [0], i); ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile, i, field [1]); break; - case 7: // package_naming_policy: uint32_t / .word | .long + case 8: // package_naming_policy: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile, i, field [1]); break; - case 8: // environment_variable_count: uint32_t / .word | .long + case 9: // environment_variable_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile, i, field [1]); break; - case 9: // system_property_count: uint32_t / .word | .long + case 10: // system_property_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile, i, field [1]); break; - case 10: // android_package_name: string / [pointer type] + case 11: // android_package_name: string / [pointer type] Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}"); pointers.Add (field [1].Trim ()); break; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 1c5b70cf0f2..7b0f7056b52 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -21,6 +21,7 @@ class ApplicationConfigNativeAssemblyGenerator : NativeAssemblyGenerator public bool BrokenExceptionTransitions { get; set; } public global::Android.Runtime.BoundExceptionType BoundExceptionType { get; set; } public bool InstantRunEnabled { get; set; } + public bool JniAddNativeMethodRegistrationAttributePresent { get; set; } public PackageNamingPolicy PackageNamingPolicy { get; set; } @@ -66,6 +67,9 @@ protected override void WriteSymbols (StreamWriter output) WriteCommentLine (output, "instant_run_enabled"); size += WriteData (output, InstantRunEnabled); + WriteCommentLine (output, "jni_add_native_method_registration_attribute_present"); + size += WriteData (output, JniAddNativeMethodRegistrationAttributePresent); + WriteCommentLine (output, "bound_exception_type"); size += WriteData (output, (byte)BoundExceptionType); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigTaskState.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigTaskState.cs new file mode 100644 index 00000000000..5cdd3a6e6a2 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigTaskState.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Android.Tasks +{ + class ApplicationConfigTaskState + { + public const string RegisterTaskObjectKey = "Xamarin.Android.Tasks.ApplicationConfigTaskState"; + + public bool JniAddNativeMethodRegistrationAttributePresent { get; set; } = false; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index d1c51660a7f..8e5c35f8d75 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -87,7 +87,22 @@ public TypeMapGenerator (Action logger, string[] supportedAbis) typemapIndexMagicString = outputEncoding.GetBytes (TypeMapIndexMagicString); } - public bool Generate (List javaTypes, string outputDirectory, bool generateNativeAssembly) + void UpdateApplicationConfig (TypeDefinition javaType, ApplicationConfigTaskState appConfState) + { + if (appConfState.JniAddNativeMethodRegistrationAttributePresent) + return; + if (!javaType.HasCustomAttributes) + return; + + foreach (CustomAttribute ca in javaType.CustomAttributes) { + if (!appConfState.JniAddNativeMethodRegistrationAttributePresent && String.Compare ("JniAddNativeMethodRegistrationAttribute", ca.AttributeType.Name, StringComparison.Ordinal) == 0) { + appConfState.JniAddNativeMethodRegistrationAttributePresent = true; + break; + } + } + } + + public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List javaTypes, string outputDirectory, bool generateNativeAssembly, out ApplicationConfigTaskState appConfState) { if (String.IsNullOrEmpty (outputDirectory)) throw new ArgumentException ("must not be null or empty", nameof (outputDirectory)); @@ -102,8 +117,13 @@ public bool Generate (List javaTypes, string outputDirectory, bo var tempModules = new Dictionary (); Dictionary moduleCounter = null; var mvidCache = new Dictionary (); + appConfState = new ApplicationConfigTaskState { + JniAddNativeMethodRegistrationAttributePresent = skipJniAddNativeMethodRegistrationAttributeScan + }; foreach (TypeDefinition td in javaTypes) { + UpdateApplicationConfig (td, appConfState); + string assemblyName = td.Module.Assembly.FullName; if (!knownAssemblies.ContainsKey (assemblyName)) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 36f68afdd14..fc8e5413be0 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -354,6 +354,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. $(_AndroidEnablePreloadAssembliesDefault) <_NativeAssemblySourceDir>$(IntermediateOutputPath)android\ <_AndroidUseNewTypemaps>True + <_SkipJniAddNativeMethodRegistrationAttributeScan Condition=" '$(_SkipJniAddNativeMethodRegistrationAttributeScan)' == '' ">False @@ -2094,7 +2095,8 @@ because xbuild doesn't support framework reference assemblies. ApplicationJavaClass="$(AndroidApplicationJavaClass)" FrameworkDirectories="$(_XATargetFrameworkDirectories);$(_XATargetFrameworkDirectories)Facades" AcwMapFile="$(_AcwMapFile)" - SupportedAbis="@(_BuildTargetAbis)"> + SupportedAbis="@(_BuildTargetAbis)" + SkipJniAddNativeMethodRegistrationAttributeScan="$(_SkipJniAddNativeMethodRegistrationAttributeScan)"> diff --git a/src/java-runtime/java/mono/android/Runtime.java b/src/java-runtime/java/mono/android/Runtime.java index d1b251c2db1..ef9fa0e1867 100644 --- a/src/java-runtime/java/mono/android/Runtime.java +++ b/src/java-runtime/java/mono/android/Runtime.java @@ -1,5 +1,8 @@ package mono.android; +import java.lang.Thread; +import java.lang.Throwable; + public class Runtime { static java.lang.Class java_lang_Class = java.lang.Class.class;; static java.lang.Class java_lang_System = java.lang.System.class; @@ -7,8 +10,8 @@ public class Runtime { static java.lang.Class mono_android_IGCUserPeer = mono.android.IGCUserPeer.class; static java.lang.Class mono_android_GCUserPeer = mono.android.GCUserPeer.class; - private Runtime () - { + static { + Thread.setDefaultUncaughtExceptionHandler (new XamarinUncaughtExceptionHandler (Thread.getDefaultUncaughtExceptionHandler ())); } public static native void init (String lang, String[] runtimeApks, String runtimeDataDir, String[] appDirs, ClassLoader loader, String[] externalStorageDirs, String[] assemblies, String packageName, int apiLevel, String[] environmentVariables); @@ -21,3 +24,21 @@ private Runtime () public static native void destroyContexts (int[] contextIDs); public static native void propagateUncaughtException (Thread javaThread, Throwable javaException); } + +final class XamarinUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + Thread.UncaughtExceptionHandler defaultHandler; + + public XamarinUncaughtExceptionHandler (Thread.UncaughtExceptionHandler previousHandler) + { + defaultHandler = previousHandler; + } + + @Override + public final void uncaughtException (Thread t, Throwable e) + { + Runtime.propagateUncaughtException (t, e); + + if (defaultHandler != null) + defaultHandler.uncaughtException (t, e); + } +} diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index 671808544a7..d7fe5d85205 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -19,6 +19,7 @@ ApplicationConfig application_config = { /*.is_a_bundled_app =*/ false, /*.broken_exception_transitions =*/ false, /*.instant_run_enabled =*/ false, + /*.jni_add_native_method_registration_attribute_present =*/ false, /*.bound_exception_type =*/ 0, // System /*.package_naming_policy =*/ 0, /*.environment_variable_count =*/ 0, diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index c4afbfc8e1a..8ab70212529 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -49,6 +49,7 @@ namespace xamarin::android::internal uint8_t brokenExceptionTransitions; int packageNamingPolicy; uint8_t boundExceptionType; + int jniAddNativeMethodRegistrationAttributePresent; }; private: diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 294fbb99125..43b8a81b48b 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -933,6 +933,7 @@ MonodroidRuntime::init_android_runtime (MonoDomain *domain, JNIEnv *env, jclass init.brokenExceptionTransitions = application_config.broken_exception_transitions ? 1 : 0; init.packageNamingPolicy = static_cast(application_config.package_naming_policy); init.boundExceptionType = application_config.bound_exception_type; + init.jniAddNativeMethodRegistrationAttributePresent = application_config.jni_add_native_method_registration_attribute_present ? 1 : 0; // GC threshold is 90% of the max GREF count init.grefGcThreshold = static_cast(androidSystem.get_gref_gc_threshold ()); diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index 7f71a0cf1b5..6cbef89d428 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -65,6 +65,7 @@ struct ApplicationConfig bool is_a_bundled_app; bool broken_exception_transitions; bool instant_run_enabled; + bool jni_add_native_method_registration_attribute_present; uint8_t bound_exception_type; uint32_t package_naming_policy; uint32_t environment_variable_count; diff --git a/tests/Runtime-AppBundle/Mono.Android-TestsAppBundle.csproj b/tests/Runtime-AppBundle/Mono.Android-TestsAppBundle.csproj index f087d605d4e..97e60b7325e 100644 --- a/tests/Runtime-AppBundle/Mono.Android-TestsAppBundle.csproj +++ b/tests/Runtime-AppBundle/Mono.Android-TestsAppBundle.csproj @@ -23,6 +23,7 @@ <_MonoAndroidTest>..\..\src\Mono.Android\Test\ true ..\..\product.snk + <_SkipJniAddNativeMethodRegistrationAttributeScan>True diff --git a/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj index a8306f81f2e..69b981d2a58 100644 --- a/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj +++ b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj @@ -22,6 +22,7 @@ d8 true ..\..\product.snk + <_SkipJniAddNativeMethodRegistrationAttributeScan>True