Skip to content

Commit 101fea2

Browse files
authored
[illink] Redo Java.Lang.Object serialization (#5473)
Context: #5454 The use of `DataContract` attribute on `Java.Lang.Object` class pulls in `System.Runtime.Serialization.Primitives` assembly. We still need to make the `Java.Lang.Object` class serializable, so that inherited types can be serialized. Otherwise that would lead to `System.Runtime.Serialization.InvalidDataContractException` during runtime. Instead of decorating `Java.Lang.Object` with `DataContract` attribute, use `Serializable` attribute on class and `NonSerialized` attribute on fields. This might seem contraproductive, to use multiple attribute instances instead of one. In real it is not and leads to size saving and getting rid of `System.Runtime.Serialization.Primitives` assembly reference in linked apps. Because the `Serializable` and `NonSerialized` attributes are not present in the compiled output, instead flags are used. The compiled `Java.Lang.Object` class IL looks like this: ``` .class public auto ansi serializable beforefieldinit Java.Lang.Object extends [System.Private.CoreLib]System.Object implements [System.Private.CoreLib]System.IDisposable, Android.Runtime.IJavaObject, Java.Interop.IJavaObjectEx, [Java.Interop]Java.Interop.IJavaPeerable { .custom instance void Android.Runtime.RegisterAttribute::.ctor(string) = ( 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A // ...java/lang/Obj 65 63 74 01 00 54 02 10 44 6F 4E 6F 74 47 65 6E // ect..T..DoNotGen 65 72 61 74 65 41 63 77 01 ) // erateAcw. .field private static initonly class [Java.Interop]Java.Interop.JniPeerMembers _members .field private static class [System.Private.CoreLib]System.Delegate cb_equals_Ljava_lang_Object_ .field private static class [System.Private.CoreLib]System.Delegate cb_hashCode .field private static class [System.Private.CoreLib]System.Delegate cb_toString .field private notserialized native int key_handle .field private notserialized native int weak_handle .field private notserialized int32 refs_added .field private notserialized valuetype Android.Runtime.JObjectRefType handle_type .field private notserialized native int handle .field private notserialized bool needsActivation .field private notserialized bool isProxy ... ``` The apk size savings on net6 in BuildReleaseArm64False test: ``` > apkdiff -md -e dll$ before.apk after.apk Size difference in bytes ([*1] apk1 only, [*2] apk2 only): - 13 assemblies/Mono.Android.dll - 120 Metadata - 60 Stream #~ (tables) - 6 Table TypeRef - 6 Table MemberRef - 6 Table CustomAttribute - 40 Table AssemblyRef - 60 Stream #Strings Type Java.Lang.Object - CustomAttribute System.Runtime.Serialization.DataContractAttribute - 2,583 assemblies/System.Runtime.Serialization.Primitives.dll *1 Summary: - 2,596 Assemblies -0.28% (of 915,384) ``` I have also added a new test for `Java.Lang.Object` class serialization with code from old `Hello` sample. Example of `System.Runtime.Serialization.InvalidDataContractException` during runtime, if `Java.Lang.Object` class was not serializable: ``` I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception of type 'Android.Runtime.JavaProxyThrowable' was thrown. I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace --- I MonoDroid: android.runtime.JavaProxyThrowable: System.Runtime.Serialization.InvalidDataContractException: Type 'UnnamedProject.Person' cannot inherit from a type that is not marked with DataContractAttribute or SerializableAttribute. Consider marking the base type 'Java.Lang.Object' with DataContractAttribute or SerializableAttribute, or removing them from the derived type. I MonoDroid: at System.Runtime.Serialization.ClassDataContract+ClassDataContractCriticalHelper..ctor (System.Type type) [0x001c3] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.ClassDataContract..ctor (System.Type type) [0x00000] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.DataContract+DataContractCriticalHelper.CreateDataContract (System.Int32 id, System.RuntimeTypeHandle typeHandle, System.Type type) [0x000e0] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.DataContract+DataContractCriticalHelper.GetDataContractSkipValidation (System.Int32 id, System.RuntimeTypeHandle typeHandle, System.Type type) [0x0000b] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.DataContract.GetDataContractSkipValidation (System.Int32 id, System.RuntimeTypeHandle typeHandle, System.Type type) [0x00000] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.DataContract.GetDataContract (System.Int32 id, System.RuntimeTypeHandle typeHandle, System.Runtime.Serialization.SerializationMode mode) [0x00000] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.DataContract.GetDataContract (System.RuntimeTypeHandle typeHandle, System.Type type, System.Runtime.Serialization.SerializationMode mode) [0x00006] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.DataContract.GetDataContract (System.Type type) [0x00006] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.Json.DataContractJsonSerializer.get_RootContract () [0x00022] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObjectContent (System.Runtime.Serialization.XmlWriterDelegator writer, System.Object graph) [0x00031] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObject (System.Runtime.Serialization.XmlWriterDelegator writer, System.Object graph) [0x00008] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.XmlObjectSerializer.InternalWriteObject (System.Runtime.Serialization.XmlWriterDelegator writer, System.Object graph, System.Runtime.Serialization.DataContractResolver dataContractResolver) [0x00000] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions (System.Runtime.Serialization.XmlWriterDelegator writer, System.Object graph, System.Runtime.Serialization.DataContractResolver dataContractResolver) [0x00073] in <686be187480b41979dcbf5635f805a7b>:0 01-07 16:13:09.246 10624 10624 I MonoDroid: at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions (System.Runtime.Serialization.XmlWriterDelegator writer, System.Object graph) [0x00000] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject (System.Xml.XmlDictionaryWriter writer, System.Object graph) [0x0000d] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject (System.IO.Stream stream, System.Object graph) [0x00018] in <686be187480b41979dcbf5635f805a7b>:0 I MonoDroid: at UnnamedProject.MainActivity.TestJsonDeserializationCreatesJavaHandle () [0x00033] in <11a2c65ab60d41dea720c7638d552ce0>:0 I MonoDroid: at UnnamedProject.MainActivity.OnCreate (Android.OS.Bundle bundle) [0x0004b] in <11a2c65ab60d41dea720c7638d552ce0>:0 01-07 16:13:09.247 10624 10624 I MonoDroid: at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState) [0x00012] in <3045e86f0ced43c19ee1b522f77c72d7>:0 I MonoDroid: at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState) [0x00012] in <3045e86f0ced43c19ee1b522f77c72d7>:0 I MonoDroid: at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr) I MonoDroid: at unnamedproject.unnamedproject.MainActivity.n_onCreate(Native Method) I MonoDroid: at unnamedproject.unnamedproject.MainActivity.onCreate(MainActivity.java:29) I MonoDroid: at android.app.Activity.performCreate(Activity.java:8000) I MonoDroid: at android.app.Activity.performCreate(Activity.java:7984) I MonoDroid: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309) I MonoDroid: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404) I MonoDroid: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595) I MonoDroid: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) I MonoDroid: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) I MonoDroid: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) I MonoDroid: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) I MonoDroid: at android.os.Handler.dispatchMessage(Handler.java:106) I MonoDroid: at android.os.Looper.loop(Looper.java:223) I MonoDroid: at android.app.ActivityThread.main(ActivityThread.java:7660) I MonoDroid: at java.lang.reflect.Method.invoke(Native Method) I MonoDroid: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) I MonoDroid: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) I MonoDroid: W ActivityTaskManager: Force finishing activity UnnamedProject.UnnamedProject/unnamedproject.unnamedproject.MainActivity ```
1 parent 597e716 commit 101fea2

File tree

5 files changed

+80
-10
lines changed

5 files changed

+80
-10
lines changed

src/Mono.Android/Java.Lang/Object.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,21 @@
1212

1313
namespace Java.Lang {
1414

15-
[DataContract]
15+
[Serializable]
1616
public partial class Object : IDisposable, IJavaObject, IJavaObjectEx
1717
#if JAVA_INTEROP
1818
, IJavaPeerable
1919
#endif // JAVA_INTEROP
2020
{
21-
IntPtr key_handle;
21+
[NonSerialized] IntPtr key_handle;
2222
#pragma warning disable CS0649, CS0169, CS0414 // Suppress fields are never used warnings, these fields are used directly by monodroid-glue.cc
23-
IntPtr weak_handle;
24-
int refs_added;
23+
[NonSerialized] IntPtr weak_handle;
24+
[NonSerialized] int refs_added;
2525
#pragma warning restore CS0649, CS0169, CS0414
26-
JObjectRefType handle_type;
27-
IntPtr handle;
28-
bool needsActivation;
29-
bool isProxy;
26+
[NonSerialized] JObjectRefType handle_type;
27+
[NonSerialized] IntPtr handle;
28+
[NonSerialized] bool needsActivation;
29+
[NonSerialized] bool isProxy;
3030

3131
IntPtr IJavaObjectEx.KeyHandle {
3232
get {return key_handle;}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ public void CheckIncludedAssemblies ()
8989
"System.Console.dll",
9090
"System.Linq.Expressions.dll",
9191
"System.ObjectModel.dll",
92-
"System.Runtime.Serialization.Primitives.dll",
9392
"System.Private.CoreLib.dll",
9493
"System.Collections.Concurrent.dll",
9594
"System.Collections.dll",
@@ -103,7 +102,6 @@ public void CheckIncludedAssemblies ()
103102
"System.Core.dll",
104103
"System.Data.dll",
105104
"System.dll",
106-
"System.Runtime.Serialization.dll",
107105
"UnnamedProject.dll",
108106
"Mono.Data.Sqlite.dll",
109107
"Mono.Data.Sqlite.dll.config",

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/MainActivity.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ protected override void OnCreate (Bundle bundle)
3232
//${AFTER_ONCREATE}
3333
}
3434
}
35+
//${AFTER_MAINACTIVITY}
3536
}
3637

3738

tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,5 +419,75 @@ public class LinkModeFullClass {
419419
}
420420
}
421421

422+
[Test]
423+
public void JsonDeserializationCreatesJavaHandle ([Values (false, true)] bool isRelease)
424+
{
425+
AssertHasDevices ();
426+
427+
proj = new XamarinAndroidApplicationProject () {
428+
IsRelease = isRelease,
429+
};
430+
431+
if (isRelease || !CommercialBuildAvailable) {
432+
proj.SetAndroidSupportedAbis ("armeabi-v7a", "arm64-v8a", "x86");
433+
}
434+
435+
proj.References.Add (new BuildItem.Reference ("System.Runtime.Serialization"));
436+
437+
if (Builder.UseDotNet)
438+
proj.References.Add (new BuildItem.Reference ("System.Runtime.Serialization.Json"));
439+
440+
proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}",
441+
@"TestJsonDeserializationCreatesJavaHandle();
442+
}
443+
444+
void TestJsonDeserializationCreatesJavaHandle ()
445+
{
446+
Person p = new Person () {
447+
Name = ""John Smith"",
448+
Age = 900,
449+
};
450+
var stream = new MemoryStream ();
451+
var serializer = new DataContractJsonSerializer (typeof (Person));
452+
453+
serializer.WriteObject (stream, p);
454+
455+
stream.Position = 0;
456+
StreamReader sr = new StreamReader (stream);
457+
458+
Console.WriteLine ($""JSON Person representation: {sr.ReadToEnd ()}"");
459+
460+
stream.Position = 0;
461+
Person p2 = (Person) serializer.ReadObject (stream);
462+
463+
Console.WriteLine ($""JSON Person parsed: Name '{p2.Name}' Age '{p2.Age}' Handle '0x{p2.Handle:X}'"");
464+
465+
if (p2.Name != ""John Smith"")
466+
throw new InvalidOperationException (""JSON deserialization of Name"");
467+
if (p2.Age != 900)
468+
throw new InvalidOperationException (""JSON deserialization of Age"");
469+
if (p2.Handle == IntPtr.Zero)
470+
throw new InvalidOperationException (""Failed to instantiate new Java instance for Person!"");
471+
472+
Console.WriteLine ($""JSON Person deserialized OK"");").Replace ("//${AFTER_MAINACTIVITY}", @"
473+
[DataContract]
474+
class Person : Java.Lang.Object {
475+
[DataMember]
476+
public string Name;
477+
478+
[DataMember]
479+
public int Age;
480+
}").Replace ("using System;", @"using System;
481+
using System.IO;
482+
using System.Runtime.Serialization;
483+
using System.Runtime.Serialization.Json;");
484+
builder = CreateApkBuilder ();
485+
Assert.IsTrue (builder.Install (proj), "Install should have succeeded.");
486+
ClearAdbLogcat ();
487+
AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity");
488+
Assert.IsFalse (MonitorAdbLogcat ((line) => {
489+
return line.Contains ("InvalidOperationException");
490+
}, Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log"), 45), $"Output did contain InvalidOperationException!");
491+
}
422492
}
423493
}

tests/api-compatibility/acceptable-breakages-vReference.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ CannotRemoveAttribute : Attribute 'System.ComponentModel.CategoryAttribute' exis
2222
CannotRemoveAttribute : Attribute 'System.ComponentModel.CategoryAttribute' exists on 'Android.Content.ContentProviderAttribute.Icon' in the contract but not the implementation.
2323
CannotRemoveAttribute : Attribute 'System.ComponentModel.CategoryAttribute' exists on 'Android.Content.ContentProviderAttribute.Label' in the contract but not the implementation.
2424
CannotRemoveAttribute : Attribute 'System.ComponentModel.CategoryAttribute' exists on 'Android.Content.ContentProviderAttribute.RoundIcon' in the contract but not the implementation.
25+
CannotRemoveAttribute : Attribute 'System.Runtime.Serialization.DataContractAttribute' exists on 'Java.Lang.Object' in the contract but not the implementation.

0 commit comments

Comments
 (0)