在 Android 上連結

Xamarin.Android 應用程式會使用「連結器」來縮小應用程式的大小。 鏈接器會採用應用程式的靜態分析,以判斷實際使用的元件、實際使用的類型,以及實際使用的成員。 連結器然後會像「記憶體回收行程」一樣,持續尋找參考的組件、類型及成員,直到整個參考組件、類型及成員終止為止。 然後,所有在此終止之外的項目便會遭到「捨棄」

例如,Hello, Android 範例:

組態 1.2.0 大小 4.0.1 大小
沒有連結的版本: 14.0 MB 16.0 MB
有連結的版本: 4.2 MB 2.9 MB

連結可將套件的大小縮小至原始 (未連結) 套件的 30% (1.2.0) 及 18% (4.0.1)。

控制

連結乃根據「靜態分析」(。 因此,無法偵測相依於執行階段環境的任何項目:

// 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));
    }
}

連結器行為

控制連結器的主要機制位於 [專案選項] 對話方塊中的 [連結器行為] (Visual Studio 中的 [連結]) 下拉式功能表。 共有三個選項:

  1. 不要連結 (Visual Studio 中的 [無])
  2. 連結 SDK 組件 (僅 SDK 組件)
  3. []連結所有組件 (SDK 及使用者組件)

[不要連結] 選項會關閉連結器,上述「沒有連結的版本」應用程式大小範例便使用了這項行為。 這在針對執行階段進行疑難排解失敗時非常有用,可查看問題是否出於連結器。 這項設定通常不建議用於正式版本。

[連結 SDK 組件] 選項只會連結 Xamarin.Android 隨附的組件。 所有其他的組件 (例如您的程式碼) 皆不會連結。

[連結所有組件] 選項會連結所有組件,表示若您的程式碼沒有靜態參考,也可能會遭到移除。

上述範例若選取 [不要連結] 與 [連結 SDK 組件] 選項皆可以正常運作,但若選取 [連結所有組件] 便會失敗,並產生下列錯誤:

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)

保留程式碼

連結器有時候會移除您想要保留的程式碼。 例如:

  • 您可能也有透過 System.Reflection.MemberInfo.Invoke 動態呼叫的程式碼。

  • 若以動態方式具現化類型,您可能會想要保留您類型的預設建構函式。

  • 若使用 XML 序列化,您可能會想要保留您類型的屬性。

在這些案例下,您可以使用 Android.Runtime.Preserve 屬性。 由於每個未由應用程式靜態連結的成員都會遭到移除,因此這個屬性可用來標示沒有靜態參考,但您的應用程式仍然需要的成員。 您可以將此屬性套用到類型的每個成員,或是類型本身。

在下列範例中,此屬性會用於保留 Example 類別的建構函式:

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

若您要保留整個類型,可使用下列屬性語法:

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

例如,在下列程式碼片段中,整個 Example 類別都會針對 XML 序列化進行保留:

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

有時候您會想要只有在包含的類型受到保留時,才保留特定的成員。 在這些案例中,請使用下列屬性語法:

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

如果您不想相依於 Xamarin 連結庫 –例如,您要建置跨平台可攜式類別庫 (PCL) – 您仍然可以使用 Android.Runtime.Preserve 屬性。 若要執行此動作,請遵循此方式在 Android.Runtime 命名空間中宣告 PreserveAttribute 類別:

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

在上述範例中,在 Android.Runtime 命名空間中宣告了 Preserve 屬性,但因為連結器會依類型名稱查詢此屬性,所以您可在任何命名空間中使用 Preserve 屬性。

falseflag

若無法使用 [Preserve] 屬性,其通常提供一個程式碼區塊,讓連結器認為有使用到該類型,同時防止程式碼區塊在執行階段時執行是很有用的做法。 若要使用這項技術,我們可以這樣做:

[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

您也可以指定一組使用者提供的組件完全不進行連結,同時透過利用 AndroidLinkSkip MSBuild 屬性允許其他使用者組件使用「連結 SDK 組件」行為來跳過:

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

LinkDescription

@(LinkDescription)建置動作可用於包含自訂連結器組態檔。 檔的 SSDL 區段。 為保留需保留的 internalprivate 成員,自訂連結器組態檔可能為必要項目。

自訂屬性

連結組件時,會從所有成員移除下列自訂屬性類型:

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

連結組件時,會從發行組建中的所有成員移除下列自訂屬性類型:

  • 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