Принципы проектирования API Xamarin.Android

Помимо основных библиотек базовых классов, которые входят в состав Mono, Xamarin.Android поставляется с привязками для различных API Android, чтобы разработчики могли создавать собственные приложения Android с помощью Mono.

В основе Xamarin.Android есть механизм взаимодействия, который мостит мир C# с миром Java и предоставляет разработчикам доступ к API Java из C# или других языков .NET.

Принципы проектирования

Это некоторые из наших принципов проектирования привязки Xamarin.Android

  • Соответствует рекомендациям по проектированию платформа .NET Framework.

  • Разрешить разработчикам классы Java подкласса.

  • Подкласс должен работать со стандартными конструкциями C#.

  • Производный от существующего класса.

  • Вызов базового конструктора для цепочки.

  • Переопределение методов должно выполняться с помощью системы переопределения C#.

  • Сделайте распространенные задачи Java простыми и сложными задачами Java.

  • Предоставление свойств JavaBean в качестве свойств C#.

  • Предоставляйте строго типизированный API:

    • Увеличьте безопасность типов.

    • Свести к минимуму ошибки среды выполнения.

    • Получение intellisense интегрированной среды разработки для типов возвращаемых значений.

    • Разрешает всплывающей документации по интегрированной среде разработки.

  • Поощряйте изучение API в интегрированной среде разработки:

    • Использование альтернатив платформы для минимизации воздействия класса Java.

    • Предоставление делегатов C# (лямбда-кодов, анонимных методов и System.Delegate) вместо интерфейсов с одним методом при необходимости.

    • Предоставьте механизм для вызова произвольных библиотек Java ( Android.Runtime.JNIEnv).

Сборки

Xamarin.Android включает ряд сборок, составляющих профиль MonoMobile. Дополнительные сведения см. на странице Сборки.

Привязки к платформе Android содержатся в сборке Mono.Android.dll . Эта сборка содержит всю привязку для использования API Android и взаимодействия с виртуальной машиной среды выполнения Android.

Разработка привязки

Коллекции

API Android используют коллекции java.util для предоставления списков, наборов и карт. Эти элементы предоставляются с помощью интерфейсов System.Collections.Generic в нашей привязке. Основные сопоставления:

Мы предоставили вспомогательные классы для ускорения маршалинг без копирования этих типов. По возможности рекомендуется использовать эти предоставленные коллекции вместо предоставленной платформы реализации, например List<T> или Dictionary<TKey, TValue>. Реализации Android.Runtime используют собственную коллекцию Java внутри себя и поэтому не требуют копирования в собственную коллекцию при передаче члену API Android и из нее.

Вы можете передать любую реализацию интерфейса в метод Android, принимающую этот интерфейс, например передать List<int>в конструктор ArrayAdapter<int>(Context, int, IList<int).> Однако для всех реализаций, кроме реализаций Android.Runtime, это включает копирование списка из виртуальной машины Mono в виртуальную машину среды выполнения Android. Если список позже изменяется в среде выполнения Android (например, путем вызова МассиваAdapter<T>. Метод Add(T) эти изменения не будут отображаться в управляемом коде. JavaList<int> Если использовалось, эти изменения будут видны.

Рефрессированные, реализации интерфейсов коллекций, которые не являются одним из перечисленных выше вспомогательных классовes только маршал [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 преобразуются в свойства при необходимости:

  • Пара T getFoo() методов Java и void setFoo(T) преобразуется в Foo свойство. Пример: Activity.Intent.

  • Метод getFoo() Java преобразуется в свойство Foo только для чтения. Пример: Context.PackageName.

  • Свойства только для набора не создаются.

  • Свойства не создаются, если тип свойства будет массивом.

События и прослушиватели

API Android основаны на Java, а его компоненты соответствуют шаблону Java для подключения прослушивателей событий. Этот шаблон, как правило, является громоздким, так как он требует от пользователя создать анонимный класс и объявить методы для переопределения, например, это то, как все будет сделано в Android с Java:

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.

Когда метод обратного вызова прослушивателя имеет пустоту, мы создадим элементы API на основе делегата EventHandler<TEventArgs> . Мы создадим такое событие, как приведенный выше пример для этих типов прослушивателя. Однако если обратный вызов прослушивателя возвращает непустое и логическое значение, события и EventHandlers не используются. Вместо этого мы создадим конкретный делегат для подписи обратного вызова и добавляем свойства вместо событий. Причина заключается в том, чтобы справиться с порядком вызова делегата и обработкой возврата. Этот подход зеркало, что делается с API Xamarin.iOS.

События или свойства C# создаются автоматически, только если метод регистрации событий Android:

  1. set Имеет префикс, например onClickListener.

  2. Имеет тип возвращаемого void значения.

  3. Принимает только один параметр, тип параметра — это интерфейс, интерфейс имеет только один метод, а имя интерфейса заканчивается Listener , например View.OnClick Listener.

Кроме того, если метод интерфейса прослушивателя имеет возвращаемый тип логического значения вместо void, то созданный подкласс EventArgs будет содержать свойство Handled. Значение свойства Handled используется в качестве возвращаемого значения для метода прослушивателя и по умолчанию true.

Например, метод Android View.setOnKeyListener() принимает интерфейс View.OnKeyListener , а метод View.OnKeyListener.onKeyListener.onKey(View, int, KeyEvent) имеет логический тип возвращаемого значения. Xamarin.Android создает соответствующее событие View.KeyPress , которое является eventHandler<View.KeyEventArgs>. Класс KeyEventArgs в свою очередь имеет свойство View.KeyEventArgs.Handled, которое используется в качестве возвращаемого значения для метода View.OnKeyListener.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.CubeEngine вложено внутри CubeWallpaper, CubeWallpaper наследует от содержащего класса WallpaperService.Engineи CubeWallpaper.CubeEngine имеет конструктор, который принимает декларативный тип - CubeWallpaper в данном случае - все, как указано выше.

Интерфейсы

Интерфейсы Java могут содержать три набора элементов, два из которых вызывают проблемы из C#:

  1. Методы

  2. Типы

  3. Поля

Интерфейсы Java превратятся в два типа:

  1. Интерфейс (необязательный) с объявлениями методов. Этот интерфейс имеет то же имя, что и интерфейс Java, за исключением префикса I'.

  2. Статический класс (необязательно), содержащий все поля, объявленные в интерфейсе Java.

Вложенные типы "перемещаются", чтобы быть братьями и сестрами заключенного интерфейса, а не вложенными типами, при этом включающее имя интерфейса в качестве префикса.

Например, рассмотрим интерфейс android.os.Parcelable . Интерфейс с пакетным доступом содержит методы, вложенные типы и константы. Методы интерфейса, доступныедля передачи, помещаются в интерфейс Android.OS.IParcelable. Константы интерфейса, доступные для передачи, помещаются в тип 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. Это помогает улучшить перенос кода Java, который зависит от констант интерфейса поставщика Android.

Помимо перечисленных выше типов, существуют четыре дальнейших изменения:

  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 должно быть привязано к выражению MediaStore.Video.MediaColumnsConsts.Title, которое трудно обнаружить, не читая много документации по Java. В версии 1.9 эквивалентное выражение C# будет mediaStore.Video.VideoColumns.Title.

Кроме того, рассмотрим тип android.os.Bundle , который реализует интерфейс Java Parcelable . Так как он реализует интерфейс, все константы этого интерфейса доступны через тип пакета, например Bundle.CONTENTS_FILE_DESCRIPTOR является совершенно допустимым выражением Java. Ранее для переноса этого выражения в C# необходимо просмотреть все интерфейсы, реализованные для просмотра типа CONTENTS_FILE_DESCRIPTOR. Начиная с Xamarin.Android 1.9, классы, реализующие интерфейсы Java, содержащие константы, будут иметь вложенный тип InterfaceConsts , который будет содержать все унаследованные константы интерфейса. Это позволит преобразовать Bundle.CONTENTS_FILE_DESCRIPTOR в Bundle.InterfaceConsts.ContentsFileDescriptor.

Наконец, типы с суффиксом Consts, например Android.OS.ParcelableConsts, теперь устарели, кроме недавно появившихся вложенных типов InterfaceConsts. Они будут удалены в Xamarin.Android 3.0.

Ресурсы

Изображения, описания макетов, двоичные BLOB-объекты и словари строк можно включить в приложение в качестве файлов ресурсов. Различные API Android предназначены для работы с идентификаторами ресурсов, а не для работы с изображениями, строками или двоичными BLOB-объектами напрямую.

Например, пример приложения Android, содержащего макет пользовательского интерфейса (), строку таблицы интернационализации (main.axmlstrings.xml) и некоторые значки (drawable-*/icon.png) сохранят свои ресурсы в каталоге Resources приложения:

Resources/
    drawable-hdpi/
        icon.png

    drawable-ldpi/
        icon.png

    drawable-mdpi/
        icon.png

    layout/
        main.axml

    values/
        strings.xml

Собственные API Android не работают непосредственно с именами файлов, а работают с идентификаторами ресурсов. При компиляции приложения 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словаря.

Константы и перечисления

Собственные API Android имеют множество методов, которые принимают или возвращают int, которые необходимо сопоставить с константным полем, чтобы определить, что означает int. Чтобы использовать эти методы, пользователю требуется обратиться к документации, чтобы узнать, какие константы являются подходящими значениями, что меньше идеала.

Например, рассмотрим Activity.requestWindowFeature(int featureID).

В этих случаях мы стараемся группировать связанные константы вместе в перечисление .NET и перенастраивать метод для принятия перечисления. Благодаря этому мы можем предложить IntelliSense выбор потенциальных значений.

Приведенный выше пример становится действием Activity.RequestWindowFeature(WindowFeatures featureId).

Обратите внимание, что это очень ручной процесс, чтобы выяснить, какие константы принадлежат вместе, и какие API используют эти константы. Укажите ошибки файлов для любых констант, используемых в API, которые лучше выразить как перечисление.