Xamarin.Android API 設計原則

除了屬於Mono的核心基類連結庫之外,Xamarin.Android 還隨附各種Android API的系結,可讓開發人員使用Mono建立原生Android應用程式。

在 Xamarin.Android 的核心中,有一個 Interop 引擎可橋接 C# 世界與 Java 世界,並提供開發人員從 C# 或其他 .NET 語言存取 Java API 的存取權。

設計原則

以下是 Xamarin.Android 系結的一些設計原則

  • 符合 .NET Framework 設計指導方針

  • 允許開發人員子類別 Java 類別。

  • 子類別應該使用 C# 標準建構。

  • 衍生自現有的類別。

  • 呼叫基底建構函式以鏈結。

  • 覆寫方法應該使用 C# 的覆寫系統來完成。

  • 讓一般 Java 工作變得簡單,而且可以執行硬式 Java 工作。

  • 將 JavaBean 屬性公開為 C# 屬性。

  • 公開強型別 API:

    • 增加類型安全性。

    • 將運行時間錯誤降到最低。

    • 取得傳回型別的 IDE Intellisense。

    • 允許 IDE 快顯檔。

  • 鼓勵 IDE 內探索 API:

    • 利用架構替代方案將 Java Classlib 暴露程度降到最低。

    • 在適當且適用時公開 C# 委派(Lambda、匿名方法和 System.Delegate),而不是單一方法介面。

    • 提供機制來呼叫任意 Java 連結庫 ( Android.Runtime.JNIEnv)。

組件

Xamarin.Android 包含構成MonoMobile配置檔的一些元件。 [ 元件] 頁面有詳細資訊。

Android 平臺的系結包含在元件中 Mono.Android.dll 。 此元件包含取用 Android API 和與 Android 執行時間 VM 通訊的整個系結。

系結設計

集合

Android API 會廣泛利用 java.util 集合來提供清單、集合和地圖。 我們會在系結中使用 System.Collections.Generic 介面來公開這些專案。 基本對應如下:

我們已提供協助程序類別,以加速這些類型的無複製封送處理。 可能的話,我們建議使用這些提供的集合,而不是架構提供的實作,例如 List<T>Dictionary<TKey, TValue>Android.Runtime 實作會在內部使用原生 Java 集合,因此在傳遞至 Android API 成員時,不需要從原生集合複製。

您可以將任何介面實作傳遞至接受該介面的Android方法,例如將 傳遞List<int>至ArrayAdapter<int>(Context、int、IList<int>) 建構函式。 不過,針對Android.Runtime實作以外的所有實作,這牽涉到將清單從Mono VM複製到Android運行時間 VM。 如果清單稍後在 Android 執行時間內變更(例如叫用 ArrayAdapter<T>。Add(T) 方法), 這些變更 將不會 顯示在 Managed 程式代碼中。 JavaList<int>如果使用 ,則會顯示這些變更。

Rephrased,集合介面實作不是上述其中一個列出的 Helper 類別,只會封送處理 [In]:

// This fails:
var badSource  = new List<int> { 1, 2, 3 };
var badAdapter = new ArrayAdapter<int>(context, textViewResourceId, badSource);
badAdapter.Add (4);
if (badSource.Count != 4) // true
    throw new InvalidOperationException ("this is thrown");

// this works:
var goodSource  = new JavaList<int> { 1, 2, 3 };
var goodAdapter = new ArrayAdapter<int> (context, textViewResourceId, goodSource);
goodAdapter.Add (4);
if (goodSource.Count != 4) // false
    throw new InvalidOperationException ("should not be reached.");

屬性

適當時,Java 方法會轉換成屬性:

  • Java 方法組 T getFoo()void setFoo(T) 會轉換成 Foo 屬性。 範例: Activity.Intent

  • Java 方法 getFoo() 會轉換成只讀 Foo 屬性。 範例: Context.PackageName

  • 不會產生僅限設定的屬性。

  • 如果屬性類型為數位,則 不會產生 屬性。

事件和接聽程式

Android API 建置在 Java 之上,其元件會遵循 Java 模式來連結事件接聽程式。 此模式通常很麻煩,因為它需要使用者建立匿名類別並宣告方法來覆寫,例如,這是使用 Java 在 Android 中完成作業的方式:

final android.widget.Button button = new android.widget.Button(context);

button.setText(this.count + " clicks!");
button.setOnClickListener (new View.OnClickListener() {
    public void onClick (View v) {
        button.setText(++this.count + " clicks!");
    }
});

C# 中使用事件的對等程式代碼會是:

var button = new Android.Widget.Button (context) {
    Text = string.Format ("{0} clicks!", this.count),
};
button.Click += (sender, e) => {
    button.Text = string.Format ("{0} clicks!", ++this.count);
};

請注意,上述這兩種機制都可搭配 Xamarin.Android 使用。 您可以實作接聽程式介面,並將它附加至 View.SetOnClickListener,也可以將透過任何一般 C# 範例建立的委派附加至 Click 事件。

當接聽程式回呼方法傳回 void 時,我們會根據 EventHandler<TEventArgs> 委派建立 API 元素。 我們會針對這些接聽程式類型產生類似上述範例的事件。 不過,如果接聽程式回呼傳回非 void 和非 布爾 值,則不會使用事件和 EventHandlers。 相反地,我們會為回呼的簽章產生特定的委派,並新增屬性,而不是事件。 原因是處理委派調用順序和傳回處理。 此方法會鏡像使用 Xamarin.iOS API 完成的工作。

只有在 Android 事件註冊方法時,才會自動產生 C# 事件或屬性:

  1. set具有前置詞,例如設定OnClickListener

  2. 具有傳 void 回型別。

  3. 只接受一個參數,參數類型是介面,介面只有一個方法,而介面名稱結尾 Listener 為 ,例如 View.OnClick 接聽程式

此外,如果 Listener 介面方法的傳回類型 為布爾值 而非 void,則產生的 EventArgs 子類別將會包含 Handled 屬性。 Handled 屬性的值會做為 Listener 方法的傳回值,預設為 true

例如,Android View.setOnKeyListener() 方法接受 View.OnKeyListener 介面,而 View.OnKeyListener.onKey(View, int, KeyEvent) 方法具有布爾傳回類型。 Xamarin.Android 會產生對應的 View.KeyPress 事件,這是 EventHandler<View.KeyEventArgs>。 KeyEventArgs 類別接著具有 View.KeyEventArgs.Handled 屬性,這個屬性會作為 View.OnKeyListener.onKey() 方法的傳回值。

我們想要為其他方法和函式新增多載,以公開委派型連線。 此外,具有多個回呼的接聽程式需要一些額外的檢查,以判斷實作個別回呼是否合理,因此我們會在識別回呼時轉換這些回呼。 如果沒有對應的事件,則必須在 C# 中使用接聽程式,但請讓任何您認為可能會有委派使用方式的接聽程式來引起我們的注意。 我們也做了一些沒有「接聽程式」後綴的介面轉換,因為很明顯,它們會受益於委派替代方案。

所有接聽程式介面都會實作 Android.Runtime.IJavaObject 介面,因為系結的實作詳細數據,所以接聽程式類別必須實作這個介面。 這可以藉由在 Java.Lang.Object 的子類別 或任何其他包裝的 Java 對象 上實作接聽程式介面,例如 Android 活動來完成。

Runnables

Java 會 利用 java.lang.Runnable 介面來提供委派機制。 java.lang.Thread 類別是這個介面值得注意的取用者。 Android 也採用 API 中的 介面。 Activity.runOnUiThread()View.post() 是值得注意的範例。

介面 Runnable 包含單一 void 方法 run()。 因此,它適合將 C# 系結為 System.Action 委派。 我們已在系結中提供多載,以接受 Action 原生 API 中取 Runnable 用 的所有 API 成員的參數,例如 Activity.RunOnUiThread()View.Post()

我們保留 IRunnable 多載, 而不是取代它們,因為數種類型會實作 介面,因此可以直接傳遞為可執行專案。

內部類別

Java 有兩種 不同類型的巢狀類別:靜態巢狀類別和非靜態類別。

Java 靜態巢狀類別與 C# 巢狀類型相同。

非靜態巢狀類別,也稱為 內部類別,明顯不同。 它們包含其封入型別實例的隱含參考,而且不能包含靜態成員(在此概觀範圍以外的其他差異)。

在系結和 C# 使用方面,靜態巢狀類別會被視為一般巢狀類型。 同時,內部類別有兩個顯著差異:

  1. 必須明確提供包含型別的隱含參考做為建構函式參數。

  2. 從內部類別繼承時,內部類別 必須 巢狀於繼承自基底內部類別之包含型別的型別內,而衍生型別必須提供與 C# 包含型別相同類型的建構函式。

例如,請考慮 Android.Service.Wallpaper.WallpaperService.Engine 內部類別。 因為它是內部類別,所以 WallpaperService.Engine() 建 函式會參考 WallpaperService 實例(比較和對比 Java WallpaperService.Engine() 建構函式,而該建構函式不採用任何參數。

內部類別的衍生範例是 CubeWallpaper.CubeEngine:

class CubeWallpaper : WallpaperService {
    public override WallpaperService.Engine OnCreateEngine ()
    {
        return new CubeEngine (this);
    }

    class CubeEngine : WallpaperService.Engine {
        public CubeEngine (CubeWallpaper s)
                : base (s)
        {
        }
    }
}

請注意 , CubeWallpaper 中的巢狀CubeWallpaper結構如何CubeWallpaper.CubeEngine繼承自 的 WallpaperService.Engine包含類別,而且CubeWallpaper.CubeEngine具有採用宣告型別的建構函式 ,CubeWallpaper在此案例中為 -- 全部如上所指定。

介面

Java 介面可以包含三組成員,其中兩組會造成 C# 的問題:

  1. 方法

  2. 類型

  3. 欄位​​

Java 介面會轉譯成兩種類型:

  1. 包含方法宣告的 (選擇性) 介面。 這個介面的名稱與 Java 介面相同, 不同之處在於 它也有 『 I 』 前置詞。

  2. 包含 Java 介面內宣告之任何欄位的 (選擇性) 靜態類別。

巢狀類型會「重新定位」為封入介面的同層級,而不是巢狀類型,並以封入介面名稱作為前置詞。

例如,請考慮 android.os.Parcelable 介面。 Parcelable 介面包含方法、巢狀類型和常數。 Parcelable 介面方法會放入 Android.OS.IParcelable 介面中。 Parcelable 介面常數會放在 Android.OS.ParcelableConsts 類型中。 巢狀 android.os.Parcelable.ClassLoaderCreator T> 和 android.os.Parcelable.Creator<T> 類型目前不會系結,因為我們的泛型支援有限制;如果支持它們,則會以 Android.OS.IParcelableClassLoaderCreator<和 Android.OS.IParcelableCreator 介面的形式呈現。 例如,巢狀 android.os.IBinder.DeathRecipient 介面會系結為 Android.OS.IBinderDeathRecipient 介面。

注意

從 Xamarin.Android 1.9 開始,Java 介面常數會 重複 ,以簡化移植 Java 程式代碼的工作。 這有助於改善移植 依賴 Android 提供者 介面常數的 Java 程式代碼。

除了上述類型之外,還有四個進一步的變更:

  1. 會產生與 Java 介面同名的類型,以包含常數。

  2. 包含介面常數的類型也包含來自實作 Java 介面的所有常數。

  3. 實作包含常數之 Java 介面的所有類別都會取得新的巢狀 InterfaceConsts 類型,其中包含來自所有實作介面的常數。

  4. Consts 類型現在已過時。

針對Android.os.Parcelable 介面,這表示現在會有Android.OS.Parcelable類型來包含常數。 例如,Parcelable.CONTENTS_FILE_DESCRIPTOR常數會系結為 Parcelable.ContentsFileDescriptor 常數,而不是作為 ParcelableConsts.ContentsFileDescriptor 常數。

對於包含常數的介面,實作其他包含更多常數之介面的介面,現在會產生所有常數的聯集。 例如, android.provider.MediaStore.Video.VideoColumns 介面會實作 android.provider.MediaStore.MediaColumns 介面。 不過,在 1.9 之前,Android.Provider.MediaStore.Video.VideoColumnsConsts 類型無法存取 Android.Provider.MediaStore.MediaColumnsConsts宣告的常數。 因此,Java 表達式 MediaStore.Video.VideoColumns.TITLE 必須系結至 C# 表達式 MediaStore.Video.MediaColumnsConsts.Title,這很難探索,而不需要閱讀許多 Java 檔。 在 1.9 中,對等的 C# 運算式會是 MediaStore.Video.VideoColumns.Title

此外,請考慮實作 Java Parcelable 介面的 android.os.Bundle 類型。 由於它會實作 介面,因此該介面上的所有常數都可以「透過」套件組合類型來存取,例如 Bundle.CONTENTS_FILE_DESCRIPTOR 是完全有效的 Java 表達式。 先前,若要將此表達式移植到 C#,您必須查看實作的所有介面,以查看CONTENTS_FILE_DESCRIPTOR來自哪個類型。 從 Xamarin.Android 1.9 開始,實作包含常數的 Java 介面的類別會有巢狀 InterfaceConsts 類型,其中包含所有繼承的介面常數。 這可讓Bundle.CONTENTS_FILE_DESCRIPTOR轉譯Bundle.InterfaceConsts.ContentsFileDescriptor

最後,具有 Android.OS.ParcelableConstsConsts 後綴的類型現在已過時,除了新引進的 InterfaceConsts 巢狀類型以外。 它們將會在 Xamarin.Android 3.0 中移除。

資源

影像、版面配置描述、二進位 Blob 和字串字典可以包含在應用程式中作為 資源檔。 各種 Android API 的設計目的是在 資源 識別碼上操作,而不是直接處理影像、字串或二進位 Blob。

例如,包含使用者介面配置 ()、國際化數據表字串 (main.axml) 和一些圖示 (strings.xmldrawable-*/icon.png) 的範例 Android 應用程式會將其資源保留在應用程式的 「資源」目錄中:

Resources/
    drawable-hdpi/
        icon.png

    drawable-ldpi/
        icon.png

    drawable-mdpi/
        icon.png

    layout/
        main.axml

    values/
        strings.xml

原生 Android API 不會直接使用檔名運作,而是在資源標識碼上操作。 當您編譯使用資源的 Android 應用程式時,建置系統會封裝資源以供散發,併產生名為 Resource 的類別,其中包含每個包含資源的令牌。 例如,針對上述資源配置,這是 R 類別會公開的內容:

public class Resource {
    public class Drawable {
        public const int icon = 0x123;
    }

    public class Layout {
        public const int main = 0x456;
    }

    public class String {
        public const int first_string = 0xabc;
        public const int second_string = 0xbcd;
    }
}

您接著會使用 Resource.Drawable.icon 來參考 drawable/icon.png 檔案,或 Resource.Layout.main 參考 layout/main.xml 檔案,或 Resource.String.first_string 參考字典檔案 values/strings.xml中的第一個字元串。

常數和列舉

原生 Android API 有許多方法會採用或傳回必須對應至常數位段的 int,以判斷 int 的意義。 若要使用這些方法,用戶必須查閱檔,才能查看哪些常數是適當的值,這不理想。

例如,請考慮 Activity.requestWindowFeature(int featureID)

在這些情況下,我們會努力將相關的常數分組到 .NET 列舉中,並重新對應 方法來取代列舉。 如此一來,我們就能提供潛在值的 IntelliSense 選取專案。

上述範例會變成: Activity.RequestWindowFeature(WindowFeatures featureId)

請注意,這是一個非常手動的程式,可找出哪些常數屬於一起,以及哪些 API 會取用這些常數。 請針對 API 中使用的任何常數提出錯誤,以更能表示為列舉。