-
Notifications
You must be signed in to change notification settings - Fork 525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[linker] preserve interfaces on Java types #7204
Conversation
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/MarkJavaObjects.cs
Outdated
Show resolved
Hide resolved
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
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/MarkJavaObjects.cs
Outdated
Show resolved
Hide resolved
4945f50
to
a95fc10
Compare
....Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc
Outdated
Show resolved
Hide resolved
....Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc
Outdated
Show resolved
Hide resolved
....Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc
Outdated
Show resolved
Hide resolved
5d936d9
to
5dcdcdc
Compare
@jonathanpeppers did the wonderful work to get the output of types_main.txt
types.txt
Through 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.IComponentCallbacks2 These 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 |
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/MarkJavaObjects.cs
Outdated
Show resolved
Hide resolved
1cd9107
to
7e7eb17
Compare
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/MarkJavaObjects.cs
Outdated
Show resolved
Hide resolved
7e7eb17
to
e1b2057
Compare
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/MarkJavaObjects.cs
Show resolved
Hide resolved
Commit message? Fixes: https://github.com/xamarin/xamarin-android/issues/7097
Context: https://github.com/xamarin/monodroid/commit/a619cbea33e1b7cd8ffd0352bb37ac551d324f67
Context: http://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: http://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
ITextWatcher
interface, and theAndroid.Text.TextWatcherImplementor
type 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.Activity
where we would not needto preserve all the interfaces.
However, types like
TextWatcherImplementor
would get theirinterfaces preserved appropriately.