Vinculação no Android

Aplicativos Xamarin.Android usam um vinculador para reduzir o tamanho do aplicativo. O vinculador emprega a análise estática do aplicativo para determinar quais assemblies são realmente usados, quais tipos são realmente usados e quais membros são realmente usados. O vinculador, em seguida, se comporta como um coletor de lixo, procurando continuamente assemblies, tipos e membros que são referenciados, até todo o fechamento desses elementos ser encontrado. Tudo fora esse fechamento é descartado.

Por exemplo, a amostra Hello, Android:

Configuração Tamanho 1.2.0 Tamanho 4.0.1
Versão sem vinculação: 14,0 MB 16,0 MB
Versão com vinculação: 4,2 MB 2,9 MB

A vinculação resulta em um pacote que tem 30% do tamanho do pacote original (desvinculado) na 1.2.0 e 18% do pacote desvinculado na 4.0.1.

Control

A vinculação se baseia em análise estática. Consequentemente, tudo o que depende do ambiente de runtime não será detectado:

// To play along at home, Example must be in a different assembly from MyActivity.
public class Example {
    // Compiler provides default constructor...
}

[Activity (Label="Linker Example", MainLauncher=true)]
public class MyActivity {
    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);

        // Will this work?
        var o = Activator.CreateInstance (typeof (ExampleLibrary.Example));
    }
}

Comportamento do vinculador

O mecanismo principal para controlar o vinculador é a lista suspensa Comportamento do vinculador (Vinculação no Visual Studio) dentro da caixa de diálogo Opções de Projeto. Há três opções:

  1. Não Vincular (Nenhum no Visual Studio)
  2. Vincular os Assemblies do SDK (Somente os Assemblies do SDK)
  3. Vincular Todos os Assemblies (Assemblies de SDK e de Usuário)

A opção Não Vincular desliga o vinculador; o exemplo de tamanho de aplicativo "Versão sem Vinculação" acima usava esse comportamento. Isso é útil para solucionar problemas de falhas de runtime, para ver se o vinculador é responsável. Essa configuração geralmente não é recomendada para builds de produção.

A opção Vincular Assemblies de SDK vincula apenas assemblies que vêm com o Xamarin.Android. Todos os outros assemblies (tais como o seu código) não estão vinculados.

A opção Vincular Todos os Assemblies vincula todos os assemblies, o que significa que seu código também pode ser removido se não houver nenhuma referência estática.

O exemplo acima funcionará com as opções Não Vincular e Vincular Assemblies de SDK e falhará com o comportamento Vincular Todos os Assemblies, gerando o erro a seguir:

E/mono    (17755): [0xafd4d440:] EXCEPTION handling: System.MissingMethodException: Default constructor not found for type ExampleLibrary.Example.
I/MonoDroid(17755): UNHANDLED EXCEPTION: System.MissingMethodException: Default constructor not found for type ExampleLibrary.Example.
I/MonoDroid(17755): at System.Activator.CreateInstance (System.Type,bool) <0x00180>
I/MonoDroid(17755): at System.Activator.CreateInstance (System.Type) <0x00017>
I/MonoDroid(17755): at LinkerScratch2.Activity1.OnCreate (Android.OS.Bundle) <0x00027>
I/MonoDroid(17755): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) <0x00057>
I/MonoDroid(17755): at (wrapper dynamic-method) object.95bb4fbe-bef8-4e5b-8e99-ca83a5d7a124 (intptr,intptr,intptr) <0x00033>
E/mono    (17755): [0xafd4d440:] EXCEPTION handling: System.MissingMethodException: Default constructor not found for type ExampleLibrary.Example.
E/mono    (17755):
E/mono    (17755): Unhandled Exception: System.MissingMethodException: Default constructor not found for type ExampleLibrary.Example.
E/mono    (17755):   at System.Activator.CreateInstance (System.Type type, Boolean nonPublic) [0x00000] in <filename unknown>:0
E/mono    (17755):   at System.Activator.CreateInstance (System.Type type) [0x00000] in <filename unknown>:0
E/mono    (17755):   at LinkerScratch2.Activity1.OnCreate (Android.OS.Bundle bundle) [0x00000] in <filename unknown>:0
E/mono    (17755):   at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) [0x00000] in <filename unknown>:0
E/mono    (17755):   at (wrapper dynamic-method) object:95bb4fbe-bef8-4e5b-8e99-ca83a5d7a124 (intptr,intptr,intptr)

Preservação do código

Às vezes, o vinculador removerá código que você deseja preservar. Por exemplo:

  • Talvez exista código que você chame dinamicamente via System.Reflection.MemberInfo.Invoke.

  • Se você cria uma instância de tipos dinamicamente, convém preservar o construtor padrão de seus tipos.

  • Se você usar a serialização de XML, você talvez queira preservar as propriedades de seus tipos.

Nesses casos, você pode usar o atributo Android.Runtime.Preserve. Cada membro que não está vinculado estaticamente pelo aplicativo está sujeito a ser removido, então esse atributo pode ser usado para marcar membros que não estão referenciados estaticamente, mas ainda são necessários para seu aplicativo. Você pode aplicar esse atributo a todos os membros de um tipo ou ao tipo propriamente dito.

No exemplo a seguir, esse atributo é usado para preservar o construtor da classe Example:

public class Example
{
    [Android.Runtime.Preserve]
    public Example ()
    {
    }
}

Se você quiser preservar o tipo inteiro, poderá usar a seguinte sintaxe de atributo:

[Android.Runtime.Preserve (AllMembers = true)]

Por exemplo, no fragmento de código a seguir, toda a classe Example é preservada para a serialização de XML:

[Android.Runtime.Preserve (AllMembers = true)]
class Example
{
    // Compiler provides default constructor...
}

Às vezes você deseja preservar a certos membros, mas somente se o tipo recipiente foi preservado. Nesses casos, use a seguinte sintaxe de atributo:

[Android.Runtime.Preserve (Conditional = true)]

Se você não quiser usar uma dependência das bibliotecas Xamarin , por exemplo, você está criando uma PCL (biblioteca de classes portátil) multiplataforma, ainda poderá usar o Android.Runtime.Preserve atributo . Para fazer isso, declare uma classe PreserveAttribute dentro do namespace Android.Runtime deste modo:

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

Nos exemplos acima, o atributo Preserve é declarado no namespace Android.Runtime; no entanto, você pode usar o atributo Preserve em qualquer namespace porque o vinculador procura esse atributo pelo nome do tipo.

falseflag

Se o atributo [Preserve] não pode ser usado, geralmente é útil fornecer um bloco de código para que o vinculador acredite que o tipo é usado, impedindo simultaneamente que o bloco de código seja executado em runtime. Para usar essa técnica, poderíamos fazer:

[Activity (Label="Linker Example", MainLauncher=true)]
class MyActivity {

#pragma warning disable 0219, 0649
    static bool falseflag = false;
    static MyActivity ()
    {
        if (falseflag) {
            var ignore = new Example ();
        }
    }
#pragma warning restore 0219, 0649

    // ...
}

linkskip

É possível especificar que um conjunto de assemblies fornecidos pelo usuário não sejam vinculados, permitindo simultaneamente que outros assemblies de usuário sejam ignorados com o comportamento Vincular Assemblies de SDK usando a Propriedade AndroidLinkSkip do MSBuild:

<PropertyGroup>
    <AndroidLinkSkip>Assembly1;Assembly2</AndroidLinkSkip>
</PropertyGroup>

LinkDescription

A @(LinkDescription)Ação de build pode ser usada em arquivos que podem conter um arquivo de configuração de vinculador personalizado. arquivo. Arquivos de configuração de vinculador personalizados podem ser necessários para preservar membros internal ou private que precisam ser preservados.

Atributos personalizados

Quando um assembly é vinculado, os seguintes tipos de atributo personalizados serão removidos de todos os membros:

  • System.ObsoleteAttribute
  • System.MonoDocumentationNoteAttribute
  • System.MonoExtensionAttribute
  • System.MonoInternalNoteAttribute
  • System.MonoLimitationAttribute
  • System.MonoNotSupportedAttribute
  • System.MonoTODOAttribute
  • System.Xml.MonoFIXAttribute

Quando um assembly é vinculado, os seguintes tipos de atributo personalizados serão removidos de todos os builds de versão:

  • System.Diagnostics.DebuggableAttribute
  • System.Diagnostics.DebuggerBrowsableAttribute
  • System.Diagnostics.DebuggerDisplayAttribute
  • System.Diagnostics.DebuggerHiddenAttribute
  • System.Diagnostics.DebuggerNonUserCodeAttribute
  • System.Diagnostics.DebuggerStepperBoundaryAttribute
  • System.Diagnostics.DebuggerStepThroughAttribute
  • System.Diagnostics.DebuggerTypeProxyAttribute
  • System.Diagnostics.DebuggerVisualizerAttribute