Skip to content
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

[Xamarin.Android.Build.Tasks] Fragments & Shorter acw-map #1301

Merged
merged 1 commit into from
Feb 14, 2018

Commits on Feb 14, 2018

  1. [Xamarin.Android.Build.Tasks] Fragments & Shorter acw-map

    Fixes: dotnet#1296
    
    Xamarin.Android attempts to expose the Java-based Android API as a
    ".NET feeling" API. This takes many forms, such as prefixing interface
    names with `I`, mapping `get` and `set` methods to properties, mapping
    listener interfaces to events, and PascalCasing method names.
    
    This also affects Java package names and C# namespaces.
    
    When creating the `.apk` file, we philosophically need to go the
    opposite direction: PascalCased members need to be mapped to
    "something appropriate" within Java. For example, many Android
    Resource ids *must* be all lowercase, and Android doesn't support
    package names starting with an uppercase letter in all circumstances.
    
    At one point, we tried mapping C# PascalCased namespaces to camelCased
    namespaces, so e.g. `MyExampleNamespace` became the
    `myExampleNamesapce` Java package within Java Callable Wrappers.
    
    This turned out to be a Terrible Mistake, particularly on
    case-sensitive filesystems, as if casing was *inconsistent*
    (`MyExampleNamespace` vs `MyExamplenamespace`), files might not be
    packaged.
    
    By Mono for Android 1.0, we settled on just lowercasing the namespace
    name to produce Java package names within Android Callable Wrappers.
    
    This was not without it's own problems; in particular, the assembly
    name wasn't involved, so if the "same" type (namespace + type) were
    present in two different assemblies and we needed to generate Android
    Callable Wrappers, they'd "collide," the build would fail, and we'd
    have some unhappy customers.
    
    This was later addressed in Xamarin.Android 5.1 by changing the Java
    package name generation algorithm to be an MD5SUM of the assembly name
    and namespace name, thus allowing types to have assembly identity.
    (This was not without its own problems.)
    
    Then it gets slightly more complicated: Android allows type names to
    appear in various locations, such as in layout View XML. These don't
    allow "just" using the type name; the package name is required for
    types outside the `android.widget` Java package.
    
    Initially, we did nothing, so developers had to directly use the
    Android Callable Wrapper names:
    
    	<myexamplenamespace.MyCustomCSharpView android:id="@+id/yay_csharp" .../>
    	<fragment android:name="myexamplenamespace.MyCustomCSharpFragment" ... />
    
    With the change in Xamarin.Android 5.1, *this couldn't work*; those
    types didn't exist anymore.
    
    To square this circle, we processed the resource files to "fixup"
    identifiers and replace them with the actual Android Callable Wrapper
    names. We'd replace any/all of:
    
    	MyExampleNamespace.MyCustomCSharpView                           // Full name
    	MyExampleNamespace.MyCustomCSharpView, MyAssembly               // Partial assembly-qualified name
    	MyExampleNamespace.MyCustomCSharpView, MyAssembly, Version=...  // Full assembly qualified name
    	myexamplenamespace.MyCustomCSharpView                           // compatibility name
    
    with the appropriate md5'd Android Callable Wrapper name.
    
    Brilliant as this was, there was a problem: [Bug #61073][61073].
    If the assembly had a wildcard in the assembly version:
    
    [61073]: https://bugzilla.xamarin.com/show_bug.cgi?id=61073
    
    	[assembly: AssemblyVersion ("1.0.0.*")]
    
    then the "Full assembly qualified name" value would change on *every
    build*, which had numerious unintended knock-on effects.
    
    This was fixed in commit e5b1c92, which worked largely by just
    killing the Full assembly qualified name version entirely.
    Xamarin.Android doesn't support embedding two different versions of
    the same assembly, so this was considered to be fine.
    
    ...except for one compatibility case: `<fragment/>`s can contain
    ~arbitrary strings, and we support replacing the entire Full assembly
    qualified name within them:
    
    	<fragment
    	    android:name="MyExampleNamespace.MyCustomCSharpFragment, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    			...
    	/>
    
    However, in a post e5b1c92 world, the above now fails to load on the
    device, because it's *not* being appropriately fixed up!
    
    	FATAL EXCEPTION: main
    	Process: Mono.Samples.HelloTests, PID: 22977
    	java.lang.RuntimeException: Unable to start activity ComponentInfo{Mono.Samples.HelloTests/mono.samples.HelloApp}: android.view.InflateException: Binary XML file line #1: Binary XML file line #1: Error inflating class fragment
    		...
    	Caused by: android.view.InflateException: Binary XML file line #1: Binary XML file line #1: Error inflating class fragment
    	Caused by: android.view.InflateException: Binary XML file line #1: Error inflating class fragment
    	Caused by: android.app.Fragment$InstantiationException: Unable to instantiate fragment Mono.Samples.Hello.MyFragment, Hello, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: make sure class name exists, is public, and has an empty constructor that is public
    		...
    	Caused by: java.lang.ClassNotFoundException: Didn't find class "Mono.Samples.Hello.MyFragment, Hello, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" on path: DexPathList[[zip file "/data/app/Mono.Samples.HelloTests-1/base.apk"],nativeLibraryDirectories=[/data/app/Mono.Samples.HelloTests-1/lib/arm64, /system/fake-libs64, /data/app/Mono.Samples.HelloTests-1/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]
    		...
    
    The problem is that what Android "sees" *should* be
    
    	<fragment
    	    android:name="md5whatever.MyCustomCSharpFragment"
    			...
    	/>
    
    where `md5whatever.MyCustomCSharpFragment` *is* a valid Java type that
    Android is able to load successfully, but because of e5b1c92 this
    replacement was removed.
    
    The fix: simplify the Full assembly-qualified name case to an already
    supported example. If the `//fragment/@android:name` value contains a
    `,`, assume it's an assembly qualified name and compute the Full name
    from it, by stripping off the comma and everything after it, then use
    the Full name to lookup the correct Android Callable Wrapper type.
    jonpryor committed Feb 14, 2018
    Configuration menu
    Copy the full SHA
    29b53b2 View commit details
    Browse the repository at this point in the history