dotnet / android

.NET for Android provides open-source bindings of the Android SDK for use with .NET managed languages such as C#
MIT License
1.94k stars 533 forks source link

Improve Trimming Native References in Android #6365

Open Redth opened 3 years ago

Redth commented 3 years ago

Problem

When .NET Android (including MAUI) projects reference external libraries/packages which contain Bindings to Native .aar/.jar libraries, it is currently difficult to trim the native libraries and in some cases impossible to achieve an optimal trimming result.

Native Linking with R8

It’s possible to use the R8 (formerly proguard) to perform trimming however this is an advanced concept for developers. We do not enable it by default for Release configurations because it usually does not work out of the box and requires manual additions to the proguard.config rules to tweak the java/native types from being removed by the overly aggressive trimming R8 performs.

Android Manifest prevents too many types from being trimmed

In android native .aar libraries, it is possible to define an AndroidManifest.xml file which gets included in the build of the application consuming the library. This manifest file can include entries for declaring the existence of Activities, Services, Broadcast Receivers, and other types which are registered with Android for other processes to interact with. When these declarations exist, the native linker (R8) has no way to ensure these types are unused (there is no trimming of the AndroidManifest files) and so it must keep the types referenced in this file from being trimmed out.
This means that even if the managed .NET assembly that is associated to the native library is trimmed out, but the native library is still included in the build, using R8 will still not remove as much of the native library as might be optimal.

.NET and Native Trimming have no relationship

Currently, even if a managed .NET assembly with bindings to a native library is completely trimmed out of a build, the native library assets will still be included in the build. It would be ideal to have a way to associate a native library (.aar/.jar/.so/etc) with a managed .NET assembly such that if the managed .NET assembly is trimmed out, the native library is also removed. There may be difficulties in the ordering of this process and could require an additional build step. It’s also possible that trimming out the native library in some of these cases is not desirable, so there would need to be a way to override this behaviour.

Redth commented 3 years ago

CC @steveisok @marek-safar

jpobst commented 3 years ago

One issue that came up about this that would need to be handled is that some bindings packages are not intended to be managed bindings, and exist simply to add the .jar/.aar to the application.

That is, they do this:

<remove-node path="/api/package" />

Which produces a .dll containing the .jar/.aar and no managed code. In this case, the user would want to Java code added to their app even though the .dll would be linked out.

Example: https://github.com/xamarin/XamarinComponents/blob/main/Android/Guava/source/Guava/Transforms/Transforms.xml

AmrAlSayed0 commented 3 years ago

I going to go ahead offer some ideas here but know that I could be way off mark since I'm not very familiar with the details of the build system.

Would it be possible to have the ILLinker to produce a (probably huge) tree of sorts of all the used/not-trimmed methods and classes and then another build task could use that tree/file to read and scan the assemblies for [Register] attributes and extract all the java members used and then from there another build task (or possibly the same one) that generates a progaurd file that gets included with the other progaurd files from the app head and the other included .aars and then fed to R8 ?

As for what @jpobst mentioned, I don't know how R8 works but if you managed to get R8 to not link out the Java code used from C# directly, doesn't that automatically tell R8 that the Java code used by that code is also used and shouldn't be trimmed? I'm guessing the exception here would be reflection in the Java side of things, this should be handled by the Java library author themselves and that responsibility shouldn't be on XA.

marek-safar commented 3 years ago

I'm wondering how much illinker involvement is actually desirable here. The illinker is not responsible for assemblies publishing and in this case it looks like the process is quite Android specific and sounds rather complicated due to possible customizations. Can Android do something similar to what other platforms do and scan/probe for needed dependencies of trimmed assemblies and combine that with other inputs to publish only needed dependencies?