[linker] preserve interfaces on Java types - #7204
Conversation
|
I'm still confused, overall. What is so special about How did 4d8c28f manage to fix the original issue #3263 when it now looks like the problem is around |
91efce1 to
ffaa55e
Compare
A diff would be useful. What types are now preserved after this change? |
ffaa55e to
4945f50
Compare
4945f50 to
a95fc10
Compare
5d936d9 to
5dcdcdc
Compare
|
@jonathanpeppers did the wonderful work to get the output of types_main.txttypes.txtThrough the glory that is awk, we can easily see which types were added by this PR: --- types_main-s.txt 2022-08-31 16:06:46.000000000 -0400
+++ types-s.txt 2022-08-31 16:06:26.000000000 -0400
@@ -7,13 +7,35 @@
Android.App.Application
Android.App.SyncContext
Android.App.SyncContext/<>c__DisplayClass3_0
+Android.Content.ComponentName
Android.Content.Context
Android.Content.ContextInvoker
Android.Content.ContextWrapper
+Android.Content.IComponentCallbacks
+Android.Content.IComponentCallbacks2
+Android.Content.IComponentCallbacks2Invoker
+Android.Content.IComponentCallbacksInvoker
+Android.Content.Intent
+Android.Content.Res.ColorStateList
+Android.Content.Res.Configuration
+Android.Content.TrimMemory
+Android.Graphics.BlendMode
+Android.Graphics.Drawables.Drawable
+Android.Graphics.Drawables.Drawable/ICallback
+Android.Graphics.Drawables.Drawable/ICallbackInvoker
+Android.Graphics.Drawables.DrawableInvoker
+Android.Graphics.Point
+Android.Graphics.PorterDuff
+Android.Graphics.PorterDuff/Mode
+Android.Graphics.Rect
Android.OS.BaseBundle
Android.OS.Bundle
Android.OS.Handler
+Android.OS.IParcelable
+Android.OS.IParcelableInvoker
Android.OS.Looper
+Android.OS.Parcel
+Android.OS.ParcelableWriteFlags
Android.Runtime.AndroidEnvironment
Android.Runtime.AndroidObjectReferenceManager
Android.Runtime.AndroidRuntime
@@ -26,18 +48,36 @@
Android.Runtime.AndroidValueManager
Android.Runtime.AnnotationAttribute
Android.Runtime.BoundExceptionType
+Android.Runtime.CharSequence
Android.Runtime.DynamicMethodNameCounter
Android.Runtime.IJavaObject
Android.Runtime.IJavaObjectValueMarshaler
Android.Runtime.IdentityHashTargets
Android.Runtime.InputStreamAdapter
Android.Runtime.InputStreamInvoker
+Android.Runtime.IntDefAttribute
Android.Runtime.JNIEnv
Android.Runtime.JNIEnv/<>c
Android.Runtime.JNINativeWrapper
Android.Runtime.JObjectRefType
Android.Runtime.JValue
+Android.Runtime.JavaCollection
+Android.Runtime.JavaCollection`1
+Android.Runtime.JavaDictionary
+Android.Runtime.JavaDictionary/<>c__DisplayClass13_0
+Android.Runtime.JavaDictionary/<>c__DisplayClass13_1
+Android.Runtime.JavaDictionary/<System-Collections-IEnumerable-GetEnumerator>d__38
+Android.Runtime.JavaDictionary/DictionaryEnumerator
+Android.Runtime.JavaDictionary`2
+Android.Runtime.JavaDictionary`2/<>c__DisplayClass4_0
+Android.Runtime.JavaDictionary`2/<>c__DisplayClass4_1
+Android.Runtime.JavaDictionary`2/<GetEnumerator>d__18
+Android.Runtime.JavaList
+Android.Runtime.JavaList`1
+Android.Runtime.JavaObject
Android.Runtime.JavaProxyThrowable
+Android.Runtime.JavaSet
+Android.Runtime.JavaSet`1
Android.Runtime.JniHandleOwnership
Android.Runtime.JnienvInitializeArgs
Android.Runtime.LogCategories
@@ -53,9 +93,87 @@
Android.Runtime.ResourceIdManager
Android.Runtime.TypeManager
Android.Runtime.XAPeerMembers
+Android.Util.IAttributeSet
+Android.Util.IAttributeSetInvoker
+Android.Views.Accessibility.AccessibilityEvent
+Android.Views.Accessibility.AccessibilityRecord
+Android.Views.Accessibility.Action
+Android.Views.Accessibility.ContentChangeTypes
+Android.Views.Accessibility.EventTypes
+Android.Views.Accessibility.IAccessibilityEventSource
+Android.Views.Accessibility.IAccessibilityEventSourceInvoker
+Android.Views.ActionMode
+Android.Views.ActionMode/ICallback
+Android.Views.ActionMode/ICallbackInvoker
+Android.Views.ActionModeInvoker
+Android.Views.ActionModeType
+Android.Views.ActionProvider
+Android.Views.ActionProviderInvoker
Android.Views.ContextThemeWrapper
+Android.Views.FocusSearchDirection
+Android.Views.IContextMenu
+Android.Views.IContextMenuContextMenuInfo
+Android.Views.IContextMenuContextMenuInfoInvoker
+Android.Views.IContextMenuInvoker
+Android.Views.IMenu
+Android.Views.IMenuInvoker
+Android.Views.IMenuItem
+Android.Views.IMenuItemInvoker
+Android.Views.IMenuItemOnActionExpandListener
+Android.Views.IMenuItemOnActionExpandListenerInvoker
+Android.Views.IMenuItemOnMenuItemClickListener
+Android.Views.IMenuItemOnMenuItemClickListenerInvoker
+Android.Views.ISubMenu
+Android.Views.ISubMenuInvoker
+Android.Views.IViewManager
+Android.Views.IViewManagerInvoker
+Android.Views.IViewParent
+Android.Views.IViewParentInvoker
+Android.Views.InputEvent
+Android.Views.InputEventInvoker
+Android.Views.KeyEvent
+Android.Views.KeyEvent/ICallback
+Android.Views.KeyEvent/ICallbackInvoker
+Android.Views.KeyboardShortcutGroup
+Android.Views.Keycode
+Android.Views.LayoutDirection
+Android.Views.LayoutInflater
+Android.Views.LayoutInflater/IFactory
+Android.Views.LayoutInflater/IFactory2
+Android.Views.LayoutInflater/IFactory2Invoker
+Android.Views.LayoutInflater/IFactoryInvoker
+Android.Views.LayoutInflaterInvoker
+Android.Views.MenuAppendFlags
+Android.Views.MenuPerformFlags
+Android.Views.MotionEvent
+Android.Views.ScrollAxis
+Android.Views.SearchEvent
+Android.Views.ShowAsAction
+Android.Views.TextAlignment
+Android.Views.TextDirection
+Android.Views.View
+Android.Views.View/IOnCreateContextMenuListener
+Android.Views.View/IOnCreateContextMenuListenerInvoker
+Android.Views.ViewGroup
+Android.Views.ViewGroup/LayoutParams
+Android.Views.ViewGroupInvoker
+Android.Views.Window
+Android.Views.Window/ICallback
+Android.Views.Window/ICallbackInvoker
+Android.Views.WindowInvoker
+Android.Views.WindowManagerLayoutParams
+Android.Window.IOnBackInvokedCallback
+Android.Window.IOnBackInvokedCallbackInvoker
+Android.Window.IOnBackInvokedDispatcher
+Android.Window.IOnBackInvokedDispatcherInvoker
Java.IO.FileInputStream
+Java.IO.ICloseable
+Java.IO.ICloseableInvoker
+Java.IO.IFlushable
+Java.IO.IFlushableInvoker
Java.IO.IOException
+Java.IO.ISerializable
+Java.IO.ISerializableInvoker
Java.IO.InputStream
Java.IO.InputStreamInvoker
Java.IO.OutputStream
@@ -68,6 +186,9 @@
Java.Interop.ExportParameterKind
Java.Interop.IJavaObjectEx
Java.Interop.IJniNameProviderAttribute
+Java.Interop.JavaConvert
+Java.Interop.JavaConvert/<>c
+Java.Interop.JavaConvert/<>c__DisplayClass1_0
Java.Interop.JavaLocationException
Java.Interop.JavaObjectExtensions
Java.Interop.Runtime
@@ -79,60 +200,168 @@
Java.Interop.TypeManager/JavaTypeManager
Java.Interop.TypeManager/TypeNameComparer
Java.Interop.TypeManagerMapDictionaries
+Java.Lang.Annotation.IAnnotation
+Java.Lang.Annotation.IAnnotationInvoker
+Java.Lang.Boolean
+Java.Lang.Byte
+Java.Lang.Character
Java.Lang.Class
+Java.Lang.ClassCastException
+Java.Lang.Double
+Java.Lang.Enum
+Java.Lang.EnumInvoker
Java.Lang.Error
Java.Lang.Exception
+Java.Lang.Float
+Java.Lang.IAppendable
+Java.Lang.IAppendableInvoker
+Java.Lang.ICharSequence
+Java.Lang.ICharSequenceInvoker
+Java.Lang.ICharSequenceInvoker/<GetEnumerator>d__35
+Java.Lang.ICloneable
+Java.Lang.ICloneableInvoker
+Java.Lang.IComparable
+Java.Lang.IComparableInvoker
Java.Lang.IRunnable
Java.Lang.IRunnableInvoker
+Java.Lang.IllegalArgumentException
+Java.Lang.IllegalStateException
+Java.Lang.IndexOutOfBoundsException
+Java.Lang.Integer
+Java.Lang.Long
+Java.Lang.NullPointerException
+Java.Lang.Number
+Java.Lang.NumberInvoker
Java.Lang.Object
+Java.Lang.Reflect.IAnnotatedElement
+Java.Lang.Reflect.IAnnotatedElementInvoker
+Java.Lang.Reflect.IGenericDeclaration
+Java.Lang.Reflect.IGenericDeclarationInvoker
+Java.Lang.Reflect.IType
+Java.Lang.Reflect.ITypeInvoker
+Java.Lang.Reflect.ITypeVariable
+Java.Lang.Reflect.ITypeVariableInvoker
+Java.Lang.RuntimeException
+Java.Lang.Short
Java.Lang.String
Java.Lang.String/<GetEnumerator>d__102
Java.Lang.Thread
Java.Lang.Thread/RunnableImplementor
Java.Lang.Throwable
+Java.Lang.UnsupportedOperationException
+Java.Nio.Buffer
+Java.Nio.BufferInvoker
+Java.Nio.ByteBuffer
+Java.Nio.ByteBufferInvoker
Java.Nio.Channels.FileChannel
Java.Nio.Channels.FileChannelInvoker
+Java.Nio.Channels.IByteChannel
+Java.Nio.Channels.IByteChannelInvoker
+Java.Nio.Channels.IChannel
+Java.Nio.Channels.IChannelInvoker
+Java.Nio.Channels.IGatheringByteChannel
+Java.Nio.Channels.IGatheringByteChannelInvoker
+Java.Nio.Channels.IInterruptibleChannel
+Java.Nio.Channels.IInterruptibleChannelInvoker
+Java.Nio.Channels.IReadableByteChannel
+Java.Nio.Channels.IReadableByteChannelInvoker
+Java.Nio.Channels.IScatteringByteChannel
+Java.Nio.Channels.IScatteringByteChannelInvoker
+Java.Nio.Channels.ISeekableByteChannel
+Java.Nio.Channels.ISeekableByteChannelInvoker
+Java.Nio.Channels.IWritableByteChannel
+Java.Nio.Channels.IWritableByteChannelInvoker
Java.Nio.Channels.Spi.AbstractInterruptibleChannel
Java.Nio.Channels.Spi.AbstractInterruptibleChannelInvoker
+Java.Util.Functions.IConsumer
+Java.Util.Functions.IConsumerInvoker
+Java.Util.IIterator
+Java.Util.IIteratorInvoker
+System.Linq.Extensions
+System.Linq.Extensions/<ToEnumerator_Dispose>d__3
+System.Linq.Extensions/<ToEnumerator_Dispose>d__5`1
Table
+_JniMarshal_PPCCII_L
+_JniMarshal_PPCC_L
+_JniMarshal_PPCI_L
+_JniMarshal_PPC_L
+_JniMarshal_PPIF_F
+_JniMarshal_PPIIII_L
_JniMarshal_PPIIII_V
+_JniMarshal_PPIIILLLIL_I
+_JniMarshal_PPIIIL_L
_JniMarshal_PPIII_V
_JniMarshal_PPIIL_V
+_JniMarshal_PPIIL_Z
+_JniMarshal_PPII_I
+_JniMarshal_PPII_L
_JniMarshal_PPII_V
+_JniMarshal_PPII_Z
+_JniMarshal_PPILI_I
+_JniMarshal_PPILI_Z
_JniMarshal_PPILL_V
+_JniMarshal_PPILL_Z
+_JniMarshal_PPIL_V
_JniMarshal_PPIL_Z
+_JniMarshal_PPIZI_L
+_JniMarshal_PPIZZ_V
+_JniMarshal_PPIZ_V
+_JniMarshal_PPIZ_Z
+_JniMarshal_PPI_C
_JniMarshal_PPI_I
_JniMarshal_PPI_J
_JniMarshal_PPI_L
_JniMarshal_PPI_V
_JniMarshal_PPJ_L
_JniMarshal_PPJ_Z
+_JniMarshal_PPLFFZ_Z
+_JniMarshal_PPLFF_Z
_JniMarshal_PPLF_V
_JniMarshal_PPLIIIIIIII_V
_JniMarshal_PPLIIII_V
+_JniMarshal_PPLIIL_V
_JniMarshal_PPLII_I
+_JniMarshal_PPLII_J
+_JniMarshal_PPLII_L
_JniMarshal_PPLII_V
_JniMarshal_PPLII_Z
_JniMarshal_PPLIL_Z
_JniMarshal_PPLI_L
_JniMarshal_PPLI_V
+_JniMarshal_PPLLF_F
+_JniMarshal_PPLLI_I
+_JniMarshal_PPLLI_L
+_JniMarshal_PPLLI_V
+_JniMarshal_PPLLI_Z
+_JniMarshal_PPLLJ_V
_JniMarshal_PPLLJ_Z
+_JniMarshal_PPLLLI_I
+_JniMarshal_PPLLLL_L
_JniMarshal_PPLLLL_V
_JniMarshal_PPLLL_L
_JniMarshal_PPLLL_V
_JniMarshal_PPLLL_Z
+_JniMarshal_PPLLZ_Z
_JniMarshal_PPLL_L
_JniMarshal_PPLL_V
_JniMarshal_PPLL_Z
_JniMarshal_PPLZZL_Z
_JniMarshal_PPLZ_V
_JniMarshal_PPL_I
+_JniMarshal_PPL_J
_JniMarshal_PPL_L
_JniMarshal_PPL_V
_JniMarshal_PPL_Z
_JniMarshal_PPZIIII_V
+_JniMarshal_PPZ_L
+_JniMarshal_PPZ_V
+_JniMarshal_PP_B
+_JniMarshal_PP_C
+_JniMarshal_PP_D
+_JniMarshal_PP_F
_JniMarshal_PP_I
_JniMarshal_PP_J
_JniMarshal_PP_L
+_JniMarshal_PP_S
_JniMarshal_PP_V
_JniMarshal_PP_Z |
|
Now that we know what types are added, we can better understand what is going in. What first jumps out to me is: +Android.Content.IComponentCallbacks
+Android.Content.IComponentCallbacks2These come from public partial class Activity : Android.Views.ContextThemeWrapper,
Android.Content.IComponentCallbacks,
Android.Content.IComponentCallbacks2,
Android.Views.KeyEvent.ICallback,
Android.Views.LayoutInflater.IFactory,
Android.Views.LayoutInflater.IFactory2,
Android.Views.View.IOnCreateContextMenuListener,
Android.Views.Window.ICallback {
}I had previously suggested:
which then morphed into "why not preserve all interfaces on all preserved types?". (I can't find a comment on this PR making that suggestion, but I think I made that suggestion verbally…?) Here we see the problem with not constraining to "just This is also more than we need; when we subclass When would we need to preserve partial class MyComponentsCallback : Java.Lang.Object, Android.Content.IComponentCallbacks {
// …
}This is also the issue with Preserving everything is "valid", but results in "larger than necessary" applications. Thus, we should instead preserve all implemented interfaces on non-bound types. If a type has This should allow us to remove interfaces implemented by |
1cd9107 to
7e7eb17
Compare
7e7eb17 to
e1b2057
Compare
|
Commit message? Fixes: https://github.com/xamarin/xamarin-android/issues/7097
Context: https://github.com/xamarin/monodroid/commit/a619cbea33e1b7cd8ffd0352bb37ac551d324f67
Context: https://github.com/xamarin/Java.Interop/commit/4787e0179b349ab5ee0d0dd03d08b694acea4971
Usage of a [`Google.Android.Material.TextField.TextInputEditText`][0]:
var filterBox = FindViewById<TextInputEditText>(Resource.Id.filterBox);
filterBox.TextChanged += (s, e) => { };
may crash at runtime with:
android.runtime.JavaProxyThrowable: System.TypeLoadException: Could not load type '{0}' from assembly '{1}'., Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
at System.RuntimeTypeHandle.GetTypeByName(String , Boolean , Boolean , StackCrawlMark& , Boolean )
at System.RuntimeType.GetType(String , Boolean , Boolean , StackCrawlMark& )
at System.Type.GetType(String , Boolean )
at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , ReadOnlySpan`1 )
--- End of stack trace from previous location ---
at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* )
at Android.Runtime.JNIEnv.FindClass(String )
at Android.Runtime.JNIEnv.AllocObject(String )
at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* )
at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] )
at Android.Text.TextWatcherImplementor..ctor(Object , EventHandler`1 , EventHandler`1 , EventHandler`1 )
at Android.Widget.TextView.add_TextChanged(EventHandler`1 )
at AndroidApp1.MainActivity.OnCreate(Bundle savedInstanceState)
at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(IntPtr , IntPtr , IntPtr )
at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V(_JniMarshal_PPL_V , IntPtr , IntPtr , IntPtr )
at crc64a6e0c00971f6cd91.MainActivity.n_onCreate(Native Method)
at crc64a6e0c00971f6cd91.MainActivity.onCreate(MainActivity.java:29)
The problem is that the .NET 6 linker *completely* removed the
`Android.Text.ITextWatcher` interface, which involved updating the
post-linked `Android.Text.TextWatcherImplementor` type to no longer
implement the `ITextWatcher` interface.
[`Android.Text.TextWatcherImplementor`][1] in turn is a hand-written
type within `Mono.Android.dll` which we expected (required!) to
always implement the `ITextWatcher` interface, so that we could pass
instances of it to Java code.
The *cause* of the `TypeLoadException` is through the confluence of
multiple factors:
1. Java Callable Wrappers for `Mono.Android.dll` are generated at
*xamarin-android* build time, *not* App build time. This was
originally done to reduce App build times, but also means that
the Java Callable Wrapper for `TextWatcherImplementor` mentions
the `ITextWatcherInvoker` type, which is only preserved if the
`ITextWatcher` interface is preserved.
/* partial */ class TextWatcherImplementor {
static {
__md_methods =
"n_afterTextChanged:(Landroid/text/Editable;)V:GetAfterTextChanged_Landroid_text_Editable_Handler:Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" +
"n_beforeTextChanged:(Ljava/lang/CharSequence;III)V:GetBeforeTextChanged_Ljava_lang_CharSequence_IIIHandler:Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" +
"n_onTextChanged:(Ljava/lang/CharSequence;III)V:GetOnTextChanged_Ljava_lang_CharSequence_IIIHandler:Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" +
"";
mono.android.Runtime.register ("Android.Text.TextWatcherImplementor, Mono.Android", TextWatcherImplementor.class, __md_methods);
}
}
2. The .NET linker determines that the `ITextWatcher` interface is
not actually used, and removes it from `Mono.Android.dll`.
Without `ITextWatcher`, the `ITextWatcherInvoker` type is also
removed from `Mono.Android.dll`.
3. At runtime when subscribing to the `TextView.TextChanged` event,
an instance of `TextWatcherImplementor` is created. This in turn
cases the Java peer `TextWatcherImplementor` type to be created,
triggering the [Java Type Registration process][2], which
includes the `Runtime.register()` invocation mentioning a method
which involves the `ITextWatcherInvoker` type, e.g.
"n_afterTextChanged:(Landroid/text/Editable;)V:GetAfterTextChanged_Landroid_text_Editable_Handler:Android.Text.ITextWatcherInvoker, Mono.Android\n"
4. `AndroidTypeManager.RegisterNativeMembers()` eventually attempts
`Type.GetType("Android.Text.ITextWatcherInvoker, Mono.Android")`.
This throws the `TypeLoadException`, as that type doesn't exist.
Commits 4d8c28f6 and d762aa99 fixed a very similar scenario in
Classic Xamarin.Android. The difference now is that the .NET 6+
linker is getting *so* good that more types are now eligible for
removal by the linker.
Oversimplifying, the "real" cause of the crash is that when:
1. We have a "non-bound" type which implements a Java interface,
such as `TextWatcherImplementor`, *and*
2. The linker decides that no IL-visible code uses that Java
interface, and decides to remove that interface
then we're going to be in a world of hurt.
The fix is to update `MonoDroid.Tuner.MarkJavaObject` so that if
we're preserving a type, we also explicitly preserve all Java
interfaces implemented by the type as well, *if the type is not bound*.
This allows a linked `TextWatcherImplementor` to continue to implement
`ITextWatcher`, which in turn causes `ITextWatcherInvoker` to be
preserved, which prevents the `TypeLoadException` from occurring.
We determine that a type is "bound" by looking for:
* `[Android.Runtime.RegisterAttribute (…, DoNotGenerateAcw=true)]`, or
* `[Java.Interop.JniTypeSignatureAttribute (…, GenerateJavaPeer=false)]`
A `Java.Lang.Object` subclass which *isn't* "bound" is considered to
be "non-bound").
We don't want to preserve all Java interfaces for all types,
including bound types, because that isn't necessary and causes app
sizes to balloon.
[0]: https://developer.android.com/reference/com/google/android/material/textfield/TextInputEditText
[1]: https://github.com/xamarin/xamarin-android/blob/619420ae1d77d0ff3ec1b59afd156bfdd63c5c26/src/Mono.Android/Android.Text/ITextWatcher.cs#L52-L93
[2]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration |
|
While pondering that commit message, it occurs to me that there might be an "alternate" fix; cause (2) is:
Why isn't Because we're hand-writing our JNI invocations! Conjecture: if that method instead used Would this be a better fix? |
After some discussion on Discord, the answer is "maybe, but we should preserve Java interfaces anyway." The hypothetical example is this: imagine a Java library which utilizes optional interfaces: public interface Fooable {
void foo();
}
public interface Barable {
void bar();
}
public class Lib {
public static void doSomething(Object o) {
if (o instanceof Fooable) { ((Fooable)o).foo(); }
if (o instanceof Barable) { ((Barable)o).bar(); }
// …
}
}This would result in a binding a'la: [Register(…)]
public interface IFooable : IJavaPeerable {
[Register(…)] void Foo();
}
[Register(…)]
public interface IBarable : IJavaPeerable {
[Register(…)] void Bar();
}
[Register(…)]
public class Lib {
public static void DoSomething(Java.Lang.Object o) {…}
}So far so good. But to use that lib: class MySomething : Java.Lang.Object, IFooable, IBarable {
public void Foo() {}
public void Bar() {}
}
// …
Lib.DoSomething(new MySomething());The expectation is that both However, if the linker is run, and the linker processes the assembly containing Therefore, we should do as this PR currently does: preserve all Java interfaces on all "hand-written" Java.Lang.Object subclasses. This will allow |
Fixes: dotnet#7097 Context: xamarin/monodroid@a619cbe Usage of a `Google.Android.Material.TextField.TextInputEditText`: var filterBox = FindViewById<TextInputEditText>(Resource.Id.filterBox); filterBox.TextChanged += (s, e) => { }; Crashes at runtime with: android.runtime.JavaProxyThrowable: System.TypeLoadException: Could not load type '{0}' from assembly '{1}'., Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null at System.RuntimeTypeHandle.GetTypeByName(String , Boolean , Boolean , StackCrawlMark& , Boolean ) at System.RuntimeType.GetType(String , Boolean , Boolean , StackCrawlMark& ) at System.Type.GetType(String , Boolean ) at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , ReadOnlySpan`1 ) --- End of stack trace from previous location --- at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* ) at Android.Runtime.JNIEnv.FindClass(String ) at Android.Runtime.JNIEnv.AllocObject(String ) at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* ) at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] ) at Android.Text.TextWatcherImplementor..ctor(Object , EventHandler`1 , EventHandler`1 , EventHandler`1 ) at Android.Widget.TextView.add_TextChanged(EventHandler`1 ) at AndroidApp1.MainActivity.OnCreate(Bundle savedInstanceState) at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(IntPtr , IntPtr , IntPtr ) at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V(_JniMarshal_PPL_V , IntPtr , IntPtr , IntPtr ) at crc64a6e0c00971f6cd91.MainActivity.n_onCreate(Native Method) at crc64a6e0c00971f6cd91.MainActivity.onCreate(MainActivity.java:29) The problem being that the linker completely removed the `ITextWatcher` interface, and the `Android.Text.TextWatcherImplementor` type no longer implemented that interface. It appears we should preserve interfaces in .NET 6 for Java types *except* if the type defines: [Android.Runtime.Register (DoNotGenerateAcw = true)] This is on types like `Android.App.Activity` where we would *not* need to preserve all the interfaces. However, types like `TextWatcherImplementor` would get their interfaces preserved appropriately.
e1b2057 to
195cb9f
Compare
* main: [Xamarin.Android.Build.Tasks] Add AndroidPackagingOptionsExclude (dotnet#7356) [linker] preserve Java interfaces on non-bound Java types (dotnet#7204) [Xamarin.Android.Build.Tasks] AndroidLinkResources and Styleables (dotnet#7306)
Fixes: #7097 Context: xamarin/monodroid@a619cbe Context: https://github.com/xamarin/Java.Interop/commit/4787e0179b349ab5ee0d0dd03d08b694acea4971 Usage of a [`Google.Android.Material.TextField.TextInputEditText`][0]: var filterBox = FindViewById<TextInputEditText>(Resource.Id.filterBox); filterBox.TextChanged += (s, e) => { }; may crash at runtime with: android.runtime.JavaProxyThrowable: System.TypeLoadException: Could not load type '{0}' from assembly '{1}'., Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null at System.RuntimeTypeHandle.GetTypeByName(String , Boolean , Boolean , StackCrawlMark& , Boolean ) at System.RuntimeType.GetType(String , Boolean , Boolean , StackCrawlMark& ) at System.Type.GetType(String , Boolean ) at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , ReadOnlySpan`1 ) --- End of stack trace from previous location --- at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* ) at Android.Runtime.JNIEnv.FindClass(String ) at Android.Runtime.JNIEnv.AllocObject(String ) at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* ) at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] ) at Android.Text.TextWatcherImplementor..ctor(Object , EventHandler`1 , EventHandler`1 , EventHandler`1 ) at Android.Widget.TextView.add_TextChanged(EventHandler`1 ) at AndroidApp1.MainActivity.OnCreate(Bundle savedInstanceState) at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(IntPtr , IntPtr , IntPtr ) at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V(_JniMarshal_PPL_V , IntPtr , IntPtr , IntPtr ) at crc64a6e0c00971f6cd91.MainActivity.n_onCreate(Native Method) at crc64a6e0c00971f6cd91.MainActivity.onCreate(MainActivity.java:29) The problem is that the .NET 6 linker *completely* removed the `Android.Text.ITextWatcher` interface, which involved updating the post-linked `Android.Text.TextWatcherImplementor` type to no longer implement the `ITextWatcher` interface. [`Android.Text.TextWatcherImplementor`][1] in turn is a hand-written type within `Mono.Android.dll` which we expected (required!) to always implement the `ITextWatcher` interface, so that we could pass instances of it to Java code. The *cause* of the `TypeLoadException` is through the confluence of multiple factors: 1. Java Callable Wrappers for `Mono.Android.dll` are generated at *xamarin-android* build time, *not* App build time. This was originally done to reduce App build times, but also means that the Java Callable Wrapper for `TextWatcherImplementor` mentions the `ITextWatcherInvoker` type, which is only preserved if the `ITextWatcher` interface is preserved. /* partial */ class TextWatcherImplementor { static { __md_methods = "n_afterTextChanged:(Landroid/text/Editable;)V:GetAfterTextChanged_Landroid_text_Editable_Handler:Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" + "n_beforeTextChanged:(Ljava/lang/CharSequence;III)V:GetBeforeTextChanged_Ljava_lang_CharSequence_IIIHandler:Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" + "n_onTextChanged:(Ljava/lang/CharSequence;III)V:GetOnTextChanged_Ljava_lang_CharSequence_IIIHandler:Android.Text.ITextWatcherInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" + ""; mono.android.Runtime.register ("Android.Text.TextWatcherImplementor, Mono.Android", TextWatcherImplementor.class, __md_methods); } } 2. The .NET linker determines that the `ITextWatcher` interface is not actually used, and removes it from `Mono.Android.dll`. Without `ITextWatcher`, the `ITextWatcherInvoker` type is also removed from `Mono.Android.dll`. 3. At runtime when subscribing to the `TextView.TextChanged` event, an instance of `TextWatcherImplementor` is created. This in turn cases the Java peer `TextWatcherImplementor` type to be created, triggering the [Java Type Registration process][2], which includes the `Runtime.register()` invocation mentioning a method which involves the `ITextWatcherInvoker` type, e.g. "n_afterTextChanged:(Landroid/text/Editable;)V:GetAfterTextChanged_Landroid_text_Editable_Handler:Android.Text.ITextWatcherInvoker, Mono.Android\n" 4. `AndroidTypeManager.RegisterNativeMembers()` eventually attempts `Type.GetType("Android.Text.ITextWatcherInvoker, Mono.Android")`. This throws the `TypeLoadException`, as that type doesn't exist. Commits 4d8c28f and d762aa9 fixed a very similar scenario in Classic Xamarin.Android. The difference now is that the .NET 6+ linker is getting *so* good that more types are now eligible for removal by the linker. Oversimplifying, the "real" cause of the crash is that when: 1. We have a "non-bound" type which implements a Java interface, such as `TextWatcherImplementor`, *and* 2. The linker decides that no IL-visible code uses that Java interface, and decides to remove that interface then we're going to be in a world of hurt. The fix is to update `MonoDroid.Tuner.MarkJavaObject` so that if we're preserving a type, we also explicitly preserve all Java interfaces implemented by the type as well, *if the type is not bound*. This allows a linked `TextWatcherImplementor` to continue to implement `ITextWatcher`, which in turn causes `ITextWatcherInvoker` to be preserved, which prevents the `TypeLoadException` from occurring. We determine that a type is "bound" by looking for: * `[Android.Runtime.RegisterAttribute (…, DoNotGenerateAcw=true)]`, or * `[Java.Interop.JniTypeSignatureAttribute (…, GenerateJavaPeer=false)]` A `Java.Lang.Object` subclass which *isn't* "bound" is considered to be "non-bound"). We don't want to preserve all Java interfaces for all types, including bound types, because that isn't necessary and causes app sizes to balloon. [0]: https://developer.android.com/reference/com/google/android/material/textfield/TextInputEditText [1]: https://github.com/xamarin/xamarin-android/blob/619420ae1d77d0ff3ec1b59afd156bfdd63c5c26/src/Mono.Android/Android.Text/ITextWatcher.cs#L52-L93 [2]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration
Fixes: #7097
Context: https://github.com/xamarin/monodroid/commit/a619cbea33e1b7cd8ffd0352bb37ac551d324f67
Usage of a
Google.Android.Material.TextField.TextInputEditText:Crashes at runtime with:
The problem being that the linker completely removed the
ITextWatcherinterface, and theAndroid.Text.TextWatcherImplementortype no longer implemented thatinterface.
It appears we should preserve interfaces in .NET 6 for Java types
except if the type defines:
This is on types like
Android.App.Activitywhere we would not needto preserve all the interfaces.
However, types like
TextWatcherImplementorwould get theirinterfaces preserved appropriately.