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