Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Java.Interop] Add JniEnvironment.WithinNewObjectScope.
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=37630 Context: #11 Context: xamarin/monodroid@940136eb Context: https://bugzilla.xamarin.com/show_bug.cgi?id=15542 Release builds with [Xamarin.Android + Java.Interop][0] are crashing on pre-Honeycomb devices (API-10 and earlier): UNHANDLED EXCEPTION: System.NotSupportedException: Unable to find the default constructor on type Android.Runtime.UncaughtExceptionHandler. Please provide the missing constructor. ---> Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown. Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown. --- End of managed exception stack trace --- java.lang.Error: Java callstack: at mono.android.TypeManager.n_activate(Native Method) at mono.android.TypeManager.Activate(TypeManager.java:7) at android.runtime.UncaughtExceptionHandler.<init>(UncaughtExceptionHandler.java:24) at mono.android.Runtime.init(Native Method) at mono.MonoPackageManager.LoadApplication(MonoPackageManager.java:40) at mono.MonoRuntimeProvider.attachInfo(MonoRuntimeProvider.java:22) at android.app.ActivityThread.installProvider(ActivityThread.java:4122) at android.app.ActivityThread.installContentProviders(ActivityThread.java:3832) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:3788) at android.app.ActivityThread.access$2200(ActivityThread.java:132) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1082) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:150) at android.app.ActivityThread.main(ActivityThread.java:4263) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:507) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597) at dalvik.system.NativeStart.main(Native Method) --- End of inner exception stack trace --- at Java.Interop.TypeManager.n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) <0x45b540c0 + 0x00570> in <filename unknown>:0 at (wrapper dynamic-method) System.Object:010d7c44-0c93-4b7d-b78f-129ff9bedae6 (intptr,intptr,intptr,intptr,intptr,intptr) UNHANDLED EXCEPTION: System.NotSupportedException: Don't know how to convert type 'System.String' to an Android.Runtime.IJavaObject. at Android.Runtime.JNIEnv.AssertIsJavaObject (System.Type targetType) <0x45b56130 + 0x000b4> in <filename unknown>:0 at Android.Runtime.JNIEnv.<CreateNativeArrayElementToManaged>m__B (System.Type type, IntPtr source, Int32 index) <0x45b5db30 + 0x0001b> in <filename unknown>:0 at Android.Runtime.JNIEnv.GetObjectArray (IntPtr array_ptr, System.Type[] element_types) <0x45b54e60 + 0x0010f> in <filename unknown>:0 at Java.Interop.TypeManager.n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) <0x45b540c0 + 0x00283> in <filename unknown>:0 at (wrapper dynamic-method) System.Object:010d7c44-0c93-4b7d-b78f-129ff9bedae6 (intptr,intptr,intptr,intptr,intptr,intptr) ... The root cause is that [Android sucks prior to API-11][1]: You can't use JNIEnv::CallVoidMethod() or JNIEnv::CallNonvirtualVoidMethod() to invoke constructors because Dalkvik raises a CloneNotSupportedException, which in turn means there's no actual point to using JNIEnv::AllocObject(), which means we need to use JNIEnv::NewObject(). JNIEnv::NewObject() sucks because it means we can enter managed code [before we've registered an instance mapping][2], which is painful. Specifically, in order to differentiate between the "Java code created this instance" vs. "managed code created this instance", Xamarin.Android's JNIEnv.NewObject() method sets an internal flag -- TypeManager.ActivationEnabled -- so that when the "activation" code path is hit it will *bail* if managed code created the instance. This prevents the constructor from nuking the stack -- C# invokes Java invokes C# (via activation) invokes Java invokes... The problem? Java.Interop has no analog to this infrastructure, and thus no way to check if we're within a "nested" JNIEnv::NewObject() invocation rooted in managed code. Consequently, on an API-10 device we'd try to activate the MainActivity instance...and possibly nuke the stack. That's the cause of this message: Unable to find the default constructor on type Android.Runtime.UncaughtExceptionHandler. We're trying to create the UncaughtExceptionHandler instance from managed code, which hits JNIEnv::NewObject(), which invokes the Java constructor, which hits the activation code path, which would then try to invoke the default constructor, which -- if it existed -- would go **BOOM**. There are two plausible fixes: 1. Drop support for API-10 and earlier devices. 2. Mirror the Xamarin.Android JNIEnv::NewObject() "hacks". (1) isn't really in the cards: even when we "dropped" bindings for API-4, we still continued to support *executing* on them while using the API-10 bindings. Which leaves (2). Add a new public read-only property, JniEnvironment.WithinNewObjectScope. This property is true while JniEnvironment.Object.NewObject() is executing -- in precisely the same way that TypeManager.ActivationEnabled is false while JNIEnv.NewObject() is executing, which can (will) include nested cross-VM invocations. (Fun!) With this support in place, Xamarin.Android can use JniEnvironment.WithinNewObjectScope instead of TypeManager.ActivationEnabled, fixing the crash. [0]: xamarin/monodroid#317 [1]: https://code.google.com/p/android/issues/detail?id=13832 [2]: http://developer.xamarin.com/guides/android/under_the_hood/architecture/#Java_Activation
- Loading branch information