Linker - James Montemagno https://staging.montemagno.com/tag/linker/ https://staging.montemagno.com/content/images/2017/08/MonogramIcon_Orange_NoBuffer.png Linker - James Montemagno https://staging.montemagno.com/tag/linker/ Hugo -- gohugo.io Tue, 13 Oct 2026 22:20:07 GMT 60 Investing Time in the Xamarin Linker for Smaller App Sizes https://staging.montemagno.com/investing-time-in-the-xamarin-linker-for-smaller-app-sizes/ 5dd314f084e2c7003870dfd4 Mon, 02 Dec 2019 19:00:00 GMT When developing an app, 99% of our time goes into writing the code, testing the functionality, and making small UI tweaks to please our end users. Most of this is done in debug mode which optimizes compilation and app size for speed, which makes sense. When we flip that flag to Release with the default settings, we often pay very little attention as to what is happening to our app. We get a nice APK or IPA and ship it up to the app store and we are done. However, if we just spent a little time fine tuning our settings, we could really reduce the overall app size. I recently sat down for 1 hour and reduced Hanselman.Forms by 4 MB on Android by optimizing the linker in Full Link mode. When I flipped on Startup Tracing for comparison the app size was reduced by 10MB!

App Before and After Optimizing the Linker

I have written many times about using & understanding the linker, however today I want to talk about how to debug the linker.

My recommendation is to flip on Link "Sdk & User Assemblies" and run your application. See if everything works or if your application crashes. Most likely, it will crash and you will see an error such as:

Getting: System.InvalidOperationException: 'A suitable constructor for type 'Shiny.Jobs.JobManager' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.'

If you see this, it means it is time to debug through.

Debug the Linker

You should NEVER have the linker on when debugging an app.... unless you are debugging the linker settings! So, turn on that linker in debug and see what happens. Like I said above, you will probably see a crash with the linker stripping away too much. That is usually because of reflection or the main project not referencing items from your .NET Standard project. Hopefully library creators have optimized their libraries to handle the linker, however if they haven't you can easily setup assembly skipping in your csproj file. Here is what it looks like to completely skip over Xamarin.Forms:

// iOS
 <MtouchExtraArgs>--linkskip=Xamarin.Forms.Platform.iOS --linkskip=Xamarin.Forms.Platform --linkskip=Xamarin.Forms.Core --linkskip=Xamarin.Forms.Xaml --linkskip=Samples</MtouchExtraArgs>

// Android
<AndroidLinkSkip>Xamarin.Forms.Platform.Android;Xamarin.Forms.Platform;Xamarin.Forms.Core;Xamarin.Forms.Xaml;FormsViewGroup;</AndroidLinkSkip>

We can easily add assemblies into this list until the app runs. Here is what Hanselman.Forms looks like:

<AndroidLinkSkip>Xamarin.Forms.Platform.Android;Xamarin.Forms.Platform;Xamarin.Forms.Core;Xamarin.Forms.Xaml;FormsViewGroup;Shiny.Core;MediaManager;MediaManager.Forms;</AndroidLinkSkip>

Don't Skip, Inform the Linker

Have you ever wondered why you have all this those Init calls in your app's startup?

Forms.SetFlags("CarouselView_Experimental");
Forms.Init(this, bundle);
FormsMaterial.Init(this, bundle);
Xamarin.Essentials.Platform.Init(this, bundle);
CrossMediaManager.Current.Init(this);
ImageCircleRenderer.Init();
Android.Glide.Forms.Init(this);

This is to help the linker become aware that you are using this library, so don't remove it. If a library doesn't have an Init, you can easily inform the linker you are using a class that is getting linked out like this:

var pancake = typeof(PancakeView);
var circle = new CircleImage();

Depending on how complex the dependency is then you may want to link skip assemblies out since we don't have access to the source code.

Custom Preserve Flags

If you are getting exceptions in classes that are in your own code then you can add a special attribute to the class to tell the linker to preserve it.

In your shared code (usually .NET Standard) add a new file called PreserveAttribute:

public sealed class PreserveAttribute : System.Attribute
{
    public bool AllMembers;
    public bool Conditional;
}

Now, on any class that is getting stripped out add the following attribute:

[Preserve(AllMembers = true)]
public partial class Tweet
{
}

Keep doing this until your code is linker safe and everything is working as expected. In no time at all you will have your app ready to be fully linker optimized and the app size will be reduced :)

Cleanup Before Committing Code

When debugging linker issues, I like to have the same settings mirrored acrossed debug and release. However, leaving these settings on in debug will slow down your builds and will turn off XAML Hot Reload. So, NEVER turn on linking in debug unless you are doing this.

Read More

Now that you have an idea of how to debug and customize yoru code for the linker be sure to read the in-depth documentation for even more goodies. I would also read the following blogs on shrinking your app size, optimizing Android builds, and faster startup times with startup tracing.

]]>
Optimizing Xamarin Apps & Libraries with the Linker https://staging.montemagno.com/optimizing-xamarin-apps-libraries-with-the-linker/ 5b398519a4b3dd00bfb1cdad Thu, 13 Jun 2019 20:30:00 GMT The Xamarin linker is one of the most important pieces of technology when building mobile apps, that no one knows exists. It does all of the hard work for you, stripping out all unused code from libraries that you include in your apps. It should only be used in release mode as it takes extra time to compile your app, but means that your app will be smaller:

Capture

The linker has specific implementations for both iOS and Android and is completely customizable.

Linking for App Developers

Before I get to how to optimize libraries, let me go through when the linker actually kicks in and how app developers use it.

For the large majority of developers, all you need to know is turn on Link SDK Assemblies Only when your app is in Release (it is the default). This will only look to link and remove libraries that ship with Xamarin.iOS and Xamarin.Android. The other option is Link All Assemblies, which will look at every assembly and library that your app uses and attempt to link them. I always recommend Link SDK Assemblies Only as it is the safest and easiest to understand. The difference between the two in terms of application size is usually minimal and often when linking everything you can get in trouble as the linker can be aggressive and will remove more than needed, especially if you use reflection.

The linker is pretty customizable and you can tell it to ignore specific libraries in the project settings. For instance if you are building an app with Xamarin.Forms you probably want to leave all of Xamarin.Forms in there so you could add some flags to iOS and Android:

// iOS
 <MtouchExtraArgs>--linkskip=Xamarin.Forms.Platform.iOS --linkskip=Xamarin.Forms.Platform --linkskip=Xamarin.Forms.Core --linkskip=Xamarin.Forms.Xaml --linkskip=Samples</MtouchExtraArgs>

// Android
<AndroidLinkSkip>Xamarin.Forms.Platform.Android;Xamarin.Forms.Platform;Xamarin.Forms.Core;Xamarin.Forms.Xaml;Samples;FormsViewGroup;</AndroidLinkSkip>

You can learn more about this on the iOS and Android documentation.

Linking for Library Creators

For library creators it is super important to understand how the linker works, and how you can optimize your libraries for app developers. You never know when a developer may flip on Link All Assemblies or when the linker may get aggressive and link away some of your library.

Preserve Everything

There are instances where you are creating a library and always want to ensure that NOTHING is ever removed regardless of the linker setting. The [Preserve (AllMembers = true)] can be added to any class in your iOS or Android library and then the linker will ignore that class. I like to always test my library in Link ALL Assemblies mode and if something goes wrong and gets removed then I slap this on the class.

Linker Safe

Sometimes it is really important that unused code in your library is properly removed. This is where the [assembly:LinkerSafe] attribute comes in to inform the linker to include this library in ALL linking modes. This is cool because you are telling the linker that you have done the work to test your library and want it to be treated as if it was part of Xamarin.iOS or Xamarin.Android.

A great example of this is Xamarin.Essentials which is completely optimized with this property. It means that if you only use the Compass then that will be the ONLY chunk of code that is included in the app. This library was built and structured with the linker in mind with very little overlapping code, and each feature is implemented in it's own class.

When I started to look at my other cross-platform libraries I noticed that the PermissionsPlugin was starting to cause people some submission issues on iOS. This was because the library itself checks all sorts of different permissions and since I didn't optimize it for the linker, Apple was detecting usage of the APIs and started to require the info.plist to add permissions that weren't actually being used.

So, I turned on the [assembly:LinkerSafe] and thought everything was going to be fine... until I packaged up an app, extracted the dll from the final package and put it into dotPeek on Windows and found out that NOTHING was getting linked out:

AllItems

This fuzzy photo (sorry about that) shows that even though my code was only requesting one permission, all of the code was still there. Well, let's look at the code:

public async Task<Dictionary<Permission, PermissionStatus>> RequestPermissionsAsync(params Permission[] permissions)
{
    var results = new Dictionary<Permission, PermissionStatus>();
    foreach (var permission in permissions)
    {
        if (results.ContainsKey(permission))
            continue;

        switch (permission)
        {
            case Permission.Calendar:
                results.Add(permission, await RequestEventPermission(EKEntityType.Event));
                break;
            case Permission.Camera:
                results.Add(permission, await RequestAVPermissionStatusAsync(AVMediaType.Video));
                break;
            case Permission.Contacts:
                results.Add(permission, await RequestContactsPermission());
                break;
            case Permission.LocationWhenInUse:
            case Permission.LocationAlways:
            case Permission.Location:
                results.Add(permission, await RequestLocationPermission(permission));
                break;
            case Permission.MediaLibrary:
                results.Add(permission, await RequestMediaLibraryPermission());
                break;
            case Permission.Microphone:
                results.Add(permission, await RequestAVPermissionStatusAsync(AVMediaType.Audio));
                break;
            case Permission.Photos:
                results.Add(permission, await RequestPhotosPermission());
                break;
            case Permission.Reminders:
                results.Add(permission, await RequestEventPermission(EKEntityType.Reminder));
                break;
            case Permission.Sensors:
                results.Add(permission, await RequestSensorsPermission());
                break;
            case Permission.Speech:
                results.Add(permission, await RequestSpeechPermission());
                break;
        }

        if (!results.ContainsKey(permission))
            results.Add(permission, PermissionStatus.Granted);
    }

    return results;
}

At first, you are probably thinking like me that the linker obviously knows what Permission my app is passing in, so it would remove any case that wasn't being used. Nope, that linker is smart, but not that smart. If I call this method at all, the linker will include any method that is being used inside of it. This means that I would need to re-structure the entire library to make it linker safe. James Clancey came up with the amazing idea of breaking the mega method into smaller individual classes that compartmentalized functionality.

This is where the BasePermission came in:

public class BasePermission
{
    protected Permission permission;
    public BasePermission(Permission permission)
    {
        this.permission = permission;
    }

    public virtual Task<PermissionStatus> CheckPermissionStatusAsync() =>
#if __IOS__
        throw new NotImplementedException();
#else
        CrossPermissions.Current.CheckPermissionStatusAsync(permission);
#endif

    public virtual async Task<PermissionStatus> RequestPermissionAsync()
    {
#if __IOS__
        throw new NotImplementedException();
#else
        var results = await CrossPermissions.Current.RequestPermissionsAsync(permission);
        if (results.ContainsKey(permission))
            return results[permission];

        return PermissionStatus.Unknown;
#endif
    }
}

The code looks a little funny, but it has two virtual methods that on iOS have no implementation at all and must be implemented in a parent class. This means that when the linker sees the BasePermission on iOS it wont have any implementation and will get removed. Now, all I needed to do is create classes for each of the permissions such as this:

public class CameraPermission : BasePermission
{
    public CameraPermission() : base(Permission.Camera)
    {

    }
#if __IOS__
    public override Task<PermissionStatus> CheckPermissionStatusAsync() =>
        Task.FromResult(PermissionsImplementation.GetAVPermissionStatus(AVFoundation.AVMediaType.Video));

    public override Task<PermissionStatus> RequestPermissionAsync() =>
        PermissionsImplementation.RequestAVPermissionStatusAsync(AVFoundation.AVMediaType.Video);
#endif
}

public class ContactsPermission : BasePermission
{
    public ContactsPermission() : base(Permission.Contacts)
    {

    }
#if __IOS__
    public override Task<PermissionStatus> CheckPermissionStatusAsync() =>
        Task.FromResult(PermissionsImplementation.ContactsPermissionStatus);

    public override Task<PermissionStatus> RequestPermissionAsync() =>
        PermissionsImplementation.RequestContactsPermission();
#endif
}

On iOS you can see that I am calling into specific implementations for each permission check and request. This now means that even though the library contains 15 different permission classes if the calling app only uses 1 of them then it will be the only code that is included and there will be no more issues with app store submissions!

Some

The linker is pretty complex, but pretty awesome when you figure out how to optimize for it. If you are a library creator I encourage you to flip on this flag for your iOS and Android projects and give it a test drive. This will ensure that you always know if your library will work regardless of any linker setting.

Be sure to read through all the documentation I linked to and also check out [Jon Douglas' blog]9https://www.jon-douglas.com/2017/09/22/linker-analyzer/) on using the linker analyzer for Android. You can also watch me struggle on my live stream from a month back where I figured out all of this.

]]>
How To Keep Your Android App Size Down https://staging.montemagno.com/how-to-keep-your-android-app-size-down/ 598df8f2c121b60019bf3013 Wed, 25 Feb 2015 21:17:20 GMT On my recent visit out to Tonto National Forest to visit one of my best friends, Luke Karrys, I started to realize the importance of trying to keep your app size down when shipping Android apps to Google Play. Working from the middle of the forest is relaxing, productive, and just plain fun, but there is one huge issue, internet and cell reception barely exists in the the middle of no where. 

When I woke up one morning and saw that I had 10 app updates ready to be installed from Google Play I was pretty excited to see what was new and fixed. However, dropping in an out of 3G service combined with an internet connection that will become crippled for everyone else if you download any files resulted in less than stellar results, and had me completely cancel all of my app updates. It had me thinking, why does the Facebook app really need to be 30MB and are all of my apps oversized as well?

There are a few ways you can take to make your app smaller when developing with Xamarin. Additionally, any Android developer can do one simple thing when deploying to Google Play to reduce their app size.

Linking Your Libraries

Xamarin applications use a “linker” in order to reduce your app size. You can browse through the documentation and find out how this works, but to simplify things, it uses static analysis to your app to remove assemblies and types that are not used in your app to bring down your app size. This is for any Xamarin app, so you should also try this out in your iOS app because it can reduce your app size in a default “Hello, World” application from 16MB down to 2.9MB! There are three settings that you can supply from the projects settings:

Don’t Link will do just that, it won’t link anything and you will be left with All of Mono, mscorlib, Xamarin.Android, and a bunch of other stuff:

Link SDK assemblies only is your safest bet and should be your default as it will only attempt to strip things out of Xamarin.Android and any of your third party libraries will not be touched. However, to really bring down your app size you should try out Link All Assemblies, as it will investigate everything and bring down your app size. Be sure to FULLY test your app as it is possible that the linker may be agressive and strip out something you need, and if that is the case you can actually use a [Android.Runtime.Preserve] flag or set a linkskip in your MSBuild to ensure that not all of your libraries get linked. 

So employing this practice with my Bike Now app, which uses Json.NET, Android Support v4, v7, Google Play Services, and Xamarin.Insights, we can compare and contrast the app size when we build our app to support all three ABIs (we will talk about this next!).

  • Don’t Link: 40.7MB

  • Link SDK Assemblies Only: 18.7MB

  • Link All Assemblies: 13MB

As you can see linking correctly can make a huge impact, but we can do even better! 

Splitting your APKs

On Android, there are ABIs (Application Binary Interfaces) that you can support when you ship your application. The most used will be the armeabi-v7a, however there are still tons of devices that support and run the old armeabi ABI and even x86 devices as well. So to ensure your app is reaching the most users you most likely have come into the project settings and selected every single ABI (I know I do!).

However, for every ABI that you select you are actually bundling a separate libmonodroid and sgen with your app. Don’t believe me then rename your .apk to .zip and take a look in the lib folder:

This of course makes sense as you would need a different version of monodroid and sgen that supports that ABI. The issue is that you now have all of these libraries bundle into a single APK and your users will be downloading all of them! The solution for any Android developer (even Java devs) is to simply split up your APKs and upload all of them to Google Play! This way you have a smaller app size across all three APKs. You can do this now with a simple check in your project options:

Now, instead of just a single APK to upload I have three with different and smaller sizes (note it will take longer to create your packages):

  • armeabi-v7a: 10.2MB

  • armeabi: 10.3MB

  • x86: 10.4MB

Notes: 

You may need to close XS after selecting check box and ensure this flag is set in your csproj:

true 

Additionally, your new APKs will be in your /bin/Release folder and will be marked with Signed in their file name.

Keep your users happy and keep down that app size with these quick tips.

]]>