Руководство по API Android

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

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

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

Инструмент API Lint

API Lint интегрирован в инструмент статического анализа Metalava и запускается автоматически во время проверки в CI. Вы можете запустить его вручную из локальной платформы checkout с помощью m checkapi или локальной AndroidX checkout с помощью ./gradlew :path:to:project:checkApi .

API-правила

Платформа Android и многие библиотеки Jetpack существовали до создания этого набора правил, и политики, изложенные далее на этой странице, постоянно развиваются для удовлетворения потребностей экосистемы Android.

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

Используйте свое суждение и обращайтесь в Совет API, если у вас есть сложные вопросы по API, которые необходимо решить, или рекомендации, которые необходимо обновить.

Основы API

Эта категория относится к основным аспектам API Android.

Все API должны быть реализованы

Независимо от аудитории API (например, публичная или @SystemApi ), все поверхности API должны быть реализованы при слиянии или представлении в качестве API. Не объединяйте заглушки API с реализацией, которая должна произойти позже.

Поверхности API без реализаций имеют ряд проблем:

  • Нет гарантии, что была раскрыта надлежащая или полная поверхность. Пока API не будет протестирован или использован клиентами, нет способа проверить, что у клиента есть соответствующие API, чтобы иметь возможность использовать эту функцию.
  • API без реализации не могут быть протестированы в Developer Previews.
  • API без реализации не могут быть протестированы в CTS.

Все API должны быть протестированы

Это соответствует требованиям CTS платформы, политикам AndroidX и общей идее о необходимости внедрения API.

Тестирование поверхностей API обеспечивает базовую гарантию того, что поверхность API пригодна к использованию, и мы рассмотрели ожидаемые варианты использования. Тестирования на существование недостаточно; необходимо тестировать поведение самого API.

Изменение, добавляющее новый API, должно включать соответствующие тесты в той же теме CL или Gerrit.

API также должны быть тестируемыми . Вы должны быть в состоянии ответить на вопрос: «Как разработчик приложения будет тестировать код, использующий ваш API?»

Все API должны быть задокументированы

Документация — ключевая часть удобства использования API. Хотя синтаксис поверхности API может показаться очевидным, новые клиенты не поймут семантику, поведение или контекст, лежащий в основе API.

Все созданные API должны соответствовать рекомендациям.

API, созданные с помощью инструментов, должны соответствовать тем же правилам API, что и написанный вручную код.

Инструменты, которые не рекомендуется использовать для создания API:

  • AutoValue : нарушает рекомендации различными способами, например, не существует способа реализовать классы конечных значений или конечные построители с помощью способа работы AutoValue.

Стиль кода

Эта категория относится к общему стилю кода, который следует использовать разработчикам, особенно при написании публичных API.

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

Соглашения о кодировании Android для внешних участников задокументированы здесь:

https://source.android.com/source/code-style.html

В целом мы стремимся следовать стандартным соглашениям по кодированию Java и Kotlin.

В названиях методов не следует использовать заглавные буквы в акронимах.

Например: имя метода должно быть runCtsTests , а не runCTSTests .

Имена не должны заканчиваться на «Импл».

Это раскрывает детали реализации, избегайте этого.

Классы

В этом разделе описываются правила, касающиеся классов, интерфейсов и наследования.

Наследовать новые публичные классы от соответствующего базового класса

Наследование раскрывает элементы API в вашем подклассе, которые могут быть неподходящими. Например, новый публичный подкласс FrameLayout выглядит как FrameLayout плюс новые поведения и элементы API. Если этот унаследованный API не подходит для вашего варианта использования, унаследуйте от класса, расположенного выше по дереву, например, ViewGroup или View .

Если у вас возникло искушение переопределить методы базового класса, чтобы выдать исключение UnsupportedOperationException , пересмотрите используемый вами базовый класс.

Используйте базовые классы коллекций

Независимо от того, принимаете ли вы коллекцию в качестве аргумента или возвращаете ее как значение, всегда отдавайте предпочтение базовому классу, а не конкретной реализации (например, return List<Foo> вместо ArrayList<Foo> ).

Используйте базовый класс, который выражает соответствующие ограничения для API. Например, используйте List для API, коллекция которого должна быть упорядочена, и используйте Set для API, коллекция которого должна состоять из уникальных элементов.

В Kotlin предпочитайте неизменяемые коллекции. Подробнее см. в разделе Изменяемость коллекций .

Абстрактные классы против интерфейсов

Java 8 добавляет поддержку методов интерфейса по умолчанию, что позволяет разработчикам API добавлять методы к интерфейсам, сохраняя при этом двоичную совместимость. Код платформы и все библиотеки Jetpack должны быть ориентированы на Java 8 или более позднюю версию.

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

В случаях, когда реализация по умолчанию требует конструктора или внутреннего состояния, необходимо использовать абстрактные классы.

В обоих случаях разработчики API могут оставить один абстрактный метод, чтобы упростить использование в качестве лямбда-выражения:

public interface AnimationEndCallback {
  // Always called, must be implemented.
  public void onFinished(Animation anim);
  // Optional callbacks.
  public default void onStopped(Animation anim) { }
  public default void onCanceled(Animation anim) { }
}

Имена классов должны отражать то, что они расширяют.

Например, классы, расширяющие Service для ясности следует называть FooService :

public class IntentHelper extends Service {}
public class IntentService extends Service {}

Общие суффиксы

Избегайте использования общих суффиксов имен классов, таких как Helper и Util для коллекций методов утилит. Вместо этого поместите методы непосредственно в ассоциированные классы или в функции расширения Kotlin.

В случаях, когда методы объединяют несколько классов, дайте содержащему их классу осмысленное имя, объясняющее, что он делает.

В очень редких случаях может быть целесообразно использование суффикса Helper :

  • Используется для составления поведения по умолчанию
  • Может включать делегирование существующего поведения новым классам.
  • Может потребоваться сохранение состояния
  • Обычно включает в себя View

Например, если для бэкпортирования всплывающих подсказок требуется сохранение состояния, связанного с View , и вызов нескольких методов View для установки бэкпорта, то TooltipHelper будет приемлемым именем класса.

Не выставляйте код, сгенерированный IDL, напрямую в виде публичных API

Сохраняйте сгенерированный IDL код в качестве деталей реализации. Сюда входят protobuf, сокеты, FlatBuffers или любая другая не-Java, не-NDK API-поверхность. Однако большая часть IDL в Android находится в AIDL, поэтому эта страница посвящена AIDL.

Сгенерированные классы AIDL не соответствуют требованиям руководства по стилю API (например, они не могут использовать перегрузку), а инструмент AIDL не предназначен специально для поддержания совместимости с API языка, поэтому вы не можете встроить их в общедоступный API.

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

Интерфейсы связующего

Если интерфейс Binder является деталью реализации, его можно свободно изменять в будущем, при этом общедоступный уровень позволяет поддерживать требуемую обратную совместимость. Например, вам может потребоваться добавить новые аргументы к внутренним вызовам или оптимизировать трафик IPC с помощью пакетной обработки или потоковой передачи, использования общей памяти или чего-то подобного. Ничего из этого нельзя сделать, если ваш интерфейс AIDL также является общедоступным API.

Например, не предоставляйте FooService как публичный API напрямую:

// BAD: Public API generated from IFooService.aidl
public class IFooService {
   public void doFoo(String foo);
}

Вместо этого оберните интерфейс Binder в менеджер или другой класс:

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo);
}

public IFooManager {
   public void doFoo(String foo) {
      mFooService.doFoo(foo);
   }
}

Если позже для этого вызова понадобится новый аргумент, внутренний интерфейс может быть минимальным, а удобные перегрузки могут быть добавлены в публичный API. Вы можете использовать слой-обертку для решения других проблем обратной совместимости по мере развития реализации:

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo, int flags);
}

public IFooManager {
   public void doFoo(String foo) {
      if (mAppTargetSdkLevel < 26) {
         useOldFooLogic(); // Apps targeting API before 26 are broken otherwise
         mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK);
      } else {
         mFooService.doFoo(foo, 0);
      }
   }

   public void doFoo(String foo, int flags) {
      mFooService.doFoo(foo, flags);
   }
}

Для интерфейсов Binder , которые не являются частью платформы Android (например, интерфейс службы, экспортируемый службами Google Play для использования приложениями), требование стабильного, опубликованного и версионированного интерфейса IPC означает, что развивать сам интерфейс гораздо сложнее. Тем не менее, все равно стоит иметь слой-оболочку вокруг него, чтобы соответствовать другим рекомендациям API и упростить использование того же публичного API для новой версии интерфейса IPC, если это когда-либо станет необходимым.

Не используйте необработанные объекты Binder в публичном API

Объект Binder сам по себе не имеет никакого значения и, следовательно, не должен использоваться в публичном API. Одним из распространенных вариантов использования является использование Binder или IBinder в качестве токена, поскольку он имеет семантику идентичности. Вместо использования сырого объекта Binder используйте класс токена-обертки.

public final class IdentifiableObject {
  public Binder getToken() {...}
}
public final class IdentifiableObjectToken {
  /**
   * @hide
   */
  public Binder getRawValue() {...}

  /**
   * @hide
   */
  public static IdentifiableObjectToken wrapToken(Binder rawValue) {...}
}

public final class IdentifiableObject {
  public IdentifiableObjectToken getToken() {...}
}

Классы менеджеров должны быть окончательными

Классы менеджеров должны быть объявлены как final . Классы менеджеров общаются с системными службами и являются единственной точкой взаимодействия. Нет необходимости в настройке, поэтому объявите его как final .

Не используйте CompletableFuture или Future

java.util.concurrent.CompletableFuture имеет большую поверхность API, которая допускает произвольные изменения значения будущего и имеет подверженные ошибкам значения по умолчанию.

Напротив, java.util.concurrent.Future отсутствует неблокирующее прослушивание, что затрудняет его использование с асинхронным кодом.

В коде платформы и низкоуровневых библиотечных API, используемых как Kotlin, так и Java , предпочтительнее использовать комбинацию обратного вызова завершения, Executor и, если API поддерживает отмену, CancellationSignal .

public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
    Executor callbackExecutor,
    android.os.OutcomeReceiver<FooResult, Throwable> callback);

Если вы ориентируетесь на Kotlin , отдавайте предпочтение функциям suspend .

suspend fun asyncLoadFoo(): Foo

В интеграционных библиотеках, специфичных для Java , можно использовать ListenableFuture от Guava.

public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();

Не использовать Необязательно

Хотя Optional может иметь преимущества в некоторых областях API, он несовместим с существующей областью API Android. @Nullable и @NonNull предоставляют инструментальную помощь для безопасности null , а Kotlin обеспечивает соблюдение контрактов на допустимость null на уровне компилятора, что делает Optional ненужным.

Для необязательных примитивов используйте парные методы has и get . Если значение не установлено ( has возвращает false ), метод get должен выдать IllegalStateException .

public boolean hasAzimuth() { ... }
public int getAzimuth() {
  if (!hasAzimuth()) {
    throw new IllegalStateException("azimuth is not set");
  }
  return azimuth;
}

Используйте закрытые конструкторы для не создаваемых экземпляров классов

Классы, которые могут быть созданы только с помощью Builder , классы, содержащие только константы или статические методы, или иные не создающие экземпляры классы должны включать по крайней мере один закрытый конструктор, чтобы предотвратить создание экземпляров с использованием конструктора без аргументов по умолчанию.

public final class Log {
  // Not instantiable.
  private Log() {}
}

Одиночки

Singleton не рекомендуется использовать, поскольку у него есть следующие недостатки, связанные с тестированием:

  1. Строительством руководит класс, что исключает использование подделок.
  2. Тесты не могут быть герметичными из-за статической природы синглтона.
  3. Чтобы обойти эти проблемы, разработчикам нужно либо знать внутренние детали синглтона, либо создать оболочку вокруг него.

Отдайте предпочтение шаблону с одним экземпляром , который для решения этих проблем опирается на абстрактный базовый класс.

Единичный экземпляр

Классы отдельных экземпляров используют абстрактный базовый класс с private или internal конструктором и предоставляют статический метод getInstance() для получения экземпляра. Метод getInstance() должен возвращать тот же объект при последующих вызовах.

Объект, возвращаемый getInstance() должен быть частной реализацией абстрактного базового класса.

class Singleton private constructor(...) {
  companion object {
    private val _instance: Singleton by lazy { Singleton(...) }

    fun getInstance(): Singleton {
      return _instance
    }
  }
}
abstract class SingleInstance private constructor(...) {
  companion object {
    private val _instance: SingleInstance by lazy { SingleInstanceImp(...) }
    fun getInstance(): SingleInstance {
      return _instance
    }
  }
}

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

Классы, освобождающие ресурсы, должны реализовывать AutoCloseable

Классы, которые освобождают ресурсы с помощью методов close , release , destroy или подобных, должны реализовывать java.lang.AutoCloseable , чтобы позволить разработчикам автоматически очищать эти ресурсы при использовании блока try-with-resources .

Избегайте введения новых подклассов View в Android.*

Не вводите новые классы, которые наследуют напрямую или косвенно от android.view.View в публичном API платформы (то есть в android.* ).

Инструментарий Android UI теперь Compose-first . Новые функции UI, предоставляемые платформой, должны быть представлены как API более низкого уровня, которые могут использоваться для реализации Jetpack Compose и, опционально, компонентов UI на основе View для разработчиков в библиотеках Jetpack. Предложение этих компонентов в библиотеках предоставляет возможности для бэкпортированных реализаций, когда функции платформы недоступны.

Поля

Эти правила касаются публичных полей в классах.

Не выставляйте необработанные поля

Классы Java не должны раскрывать поля напрямую. Поля должны быть закрытыми и доступными только с использованием открытых геттеров и сеттеров, независимо от того, являются ли эти поля окончательными или нет.

Редкие исключения включают базовые структуры данных, где нет необходимости улучшать поведение указания или извлечения поля. В таких случаях поля следует именовать с использованием стандартных соглашений об именовании переменных, например, Point.x и Point.y .

Классы Kotlin могут раскрывать свойства.

Открытые поля должны быть помечены как окончательные.

Необработанные поля настоятельно не рекомендуются (@see Не раскрывайте необработанные поля ). Но в редких случаях, когда поле раскрывается как публичное поле, отметьте это поле final .

Внутренние поля не должны быть открыты

Не ссылайтесь на внутренние имена полей в публичном API.

public int mFlags;

Используйте public вместо protected

@see Используйте public вместо protected

Константы

Это правила, касающиеся публичных констант.

Константы флагов не должны перекрывать значения int или long.

Флаги подразумевают биты, которые могут быть объединены в некоторое объединенное значение. Если это не так, не называйте переменную или константу flag .

public static final int FLAG_SOMETHING = 2;
public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2;
public static final int FLAG_PRESENTATION = 1 << 3;

Дополнительную информацию об определении констант публичных флагов см. в описании флагов битовой маски @IntDef

Для статических конечных констант следует использовать соглашение об именовании, состоящее из заглавных букв и разделенное подчеркиванием.

Все слова в константе должны быть написаны заглавными буквами, а несколько слов должны быть разделены символом _ . Например:

public static final int fooThing = 5
public static final int FOO_THING = 5

Используйте стандартные префиксы для констант

Многие константы, используемые в Android, предназначены для стандартных вещей, таких как флаги, ключи и действия. Эти константы должны иметь стандартные префиксы, чтобы сделать их более идентифицируемыми как эти вещи.

Например, дополнения к намерениям должны начинаться с EXTRA_ . Действия намерений должны начинаться с ACTION_ . Константы, используемые с Context.bindService() должны начинаться с BIND_ .

Ключевые константные имена и области действия

Значения строковых констант должны соответствовать имени самой константы и, как правило, должны быть ограничены пакетом или доменом. Например:

public static final String FOO_THING = "foo"

не назван последовательно и не имеет соответствующей области действия. Вместо этого рассмотрите:

public static final String FOO_THING = "android.fooservice.FOO_THING"

Префиксы android в строковых константах с ограниченной областью действия зарезервированы для проекта Android Open Source.

Действия намерений и дополнения, а также записи Bundle должны иметь пространство имен, используя имя пакета, в котором они определены.

package android.foo.bar {
  public static final String ACTION_BAZ = "android.foo.bar.action.BAZ"
  public static final String EXTRA_BAZ = "android.foo.bar.extra.BAZ"
}

Используйте public вместо protected

@see Используйте public вместо protected

Используйте последовательные префиксы

Связанные константы должны начинаться с одного и того же префикса. Например, для набора констант, используемых со значениями флагов:

public static final int SOME_VALUE = 0x01;

public static final int SOME_OTHER_VALUE = 0x10;

public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;

public static final int FLAG_SOME_OTHER_VALUE = 0x10;

public static final int FLAG_SOME_THIRD_VALUE = 0x100;

@see Используйте стандартные префиксы для констант

Используйте единообразные названия ресурсов

Публичные идентификаторы, атрибуты и значения должны именоваться с использованием соглашения об именах camelCase, например @id/accessibilityActionPageUp или @attr/textAppearance , аналогично публичным полям в Java.

В некоторых случаях публичный идентификатор или атрибут включает общий префикс, разделенный подчеркиванием:

  • Значения конфигурации платформы, такие как @string/config_recentsComponentName в config.xml
  • Атрибуты представления, специфичные для макета, такие как @attr/layout_marginStart в attrs.xml

Публичные темы и стили должны следовать иерархическому соглашению об именовании PascalCase, например @style/Theme.Material.Light.DarkActionBar или @style/Widget.Material.SearchView.ActionBar , аналогично вложенным классам в Java.

Ресурсы макетов и рисуемых объектов не должны быть представлены как публичные API. Однако, если они должны быть представлены, то публичные макеты и рисуемые объекты должны быть названы с использованием соглашения об именовании under_score, например, layout/simple_list_item_1.xml или drawable/title_bar_tall.xml .

Если константы могут изменяться, сделайте их динамическими

Компилятор может встраивать константные значения, поэтому сохранение значений неизменными считается частью контракта API. Если значение константы MIN_FOO или MAX_FOO может измениться в будущем, рассмотрите возможность сделать их динамическими методами.

CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()

Рассмотрите возможность прямой совместимости для обратных вызовов

Константы, определенные в будущих версиях API, не известны приложениям, нацеленным на старые API. По этой причине константы, предоставляемые приложениям, должны учитывать целевую версию API этого приложения и сопоставлять новые константы с согласованным значением. Рассмотрим следующий сценарий:

Предполагаемый источник SDK:

// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;

Гипотетическое приложение с targetSdkVersion="22" :

if (result == STATUS_FAILURE) {
  // Oh no!
} else {
  // Success!
}

В этом случае приложение было разработано в рамках ограничений API уровня 22 и сделало (довольно) разумное предположение, что существует только два возможных состояния. Однако, если приложение получает недавно добавленный STATUS_FAILURE_RETRY , оно интерпретирует это как успех.

Методы, возвращающие константы, могут безопасно обрабатывать подобные случаи, ограничивая свои выходные данные в соответствии с уровнем API, на который нацелено приложение:

private int mapResultForTargetSdk(Context context, int result) {
  int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  if (targetSdkVersion < 26) {
    if (result == STATUS_FAILURE_ABORT) {
      return STATUS_FAILURE;
    }
    if (targetSdkVersion < 23) {
      if (result == STATUS_FAILURE_RETRY) {
        return STATUS_FAILURE;
      }
    }
  }
  return result;
}

Разработчики не могут предвидеть, изменится ли список констант в будущем. Если вы определяете API с константой UNKNOWN или UNSPECIFIED , которая выглядит как универсальная константа, разработчики предполагают, что опубликованные константы при написании приложения являются исчерпывающими. Если вы не хотите устанавливать такое ожидание, пересмотрите, является ли универсальная константа хорошей идеей для вашего API.

Кроме того, библиотеки не могут указывать собственную targetSdkVersion отдельно от приложения, а обработка изменений поведения targetSdkVersion из кода библиотеки сложна и подвержена ошибкам.

Целое число или строковая константа

Используйте целочисленные константы и @IntDef , если пространство имен для значений не расширяется за пределами вашего пакета. Используйте строковые константы, если пространство имен является общим или может быть расширено кодом за пределами вашего пакета.

Классы данных

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

Не используйте data class в публичных API Kotlin, так как компилятор Kotlin не гарантирует языковой API или двоичную совместимость для сгенерированного кода. Вместо этого вручную реализуйте требуемые функции.

Инстанцирование

В Java классы данных должны предоставлять конструктор, если свойств немного, или использовать шаблон Builder если свойств много.

В Kotlin классы данных должны предоставлять конструктор с аргументами по умолчанию независимо от количества свойств. Классы данных, определенные в Kotlin, также могут выиграть от предоставления конструктора при ориентации на Java-клиенты.

Модификация и копирование

В случаях, когда необходимо изменить данные, предоставьте либо класс Builder с конструктором копирования (Java), либо функцию-член copy() (Kotlin), которая возвращает новый объект.

При предоставлении функции copy() в Kotlin аргументы должны соответствовать конструктору класса, а значения по умолчанию должны быть заполнены с использованием текущих значений объекта:

class Typography(
  val labelMedium: TextStyle = TypographyTokens.LabelMedium,
  val labelSmall: TextStyle = TypographyTokens.LabelSmall
) {
    fun copy(
      labelMedium: TextStyle = this.labelMedium,
      labelSmall: TextStyle = this.labelSmall
    ): Typography = Typography(
      labelMedium = labelMedium,
      labelSmall = labelSmall
    )
}

Дополнительные модели поведения

Классы данных должны реализовывать как equals() , так и hashCode() , и каждое свойство должно учитываться в реализациях этих методов.

Классы данных могут реализовывать toString() с рекомендуемым форматом, соответствующим реализации класса данных Kotlin , например User(var1=Alex, var2=42) .

Методы

Это правила, касающиеся различных особенностей методов, параметров, имен методов, типов возвращаемых данных и спецификаторов доступа.

Время

Эти правила определяют, как такие временные концепции, как даты и продолжительность, должны быть выражены в API.

По возможности отдавайте предпочтение типам java.time.*

java.time.Duration , java.time.Instant и многие другие типы java.time.* доступны на всех версиях платформы посредством дешугаринга и должны быть предпочтительными при выражении времени в параметрах API или возвращаемых значениях.

Предпочитайте предоставлять только те варианты API, которые принимают или возвращают java.time.Duration или java.time.Instant , и опускайте примитивные варианты с тем же вариантом использования, если только область API не является такой, где распределение объектов в предполагаемых шаблонах использования будет иметь недопустимое влияние на производительность.

Методы, выражающие длительность, следует называть длительностью.

Если значение времени выражает продолжительность соответствующего периода времени, назовите параметр «продолжительность», а не «время».

ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);

Исключения:

«тайм-аут» подходит, когда длительность относится конкретно к значению тайм-аута.

«время» с типом java.time.Instant уместно при ссылке на конкретный момент времени, а не на длительность.

Методы, выражающие длительность или время как примитивы, должны именоваться по их единицам времени и использовать long

Методы, принимающие или возвращающие длительность как примитив, должны добавлять к имени метода суффикс с соответствующими единицами времени (например, Millis , Nanos , Seconds ), чтобы зарезервировать недекорированное имя для использования с java.time.Duration . См. Time .

Методы также должны быть соответствующим образом аннотированы с указанием их единицы измерения и временной базы:

  • @CurrentTimeMillisLong : Значение — неотрицательная временная метка, измеряемая как количество миллисекунд с 1970-01-01T00:00:00Z.
  • @CurrentTimeSecondsLong : Значение — неотрицательная временная метка, измеряемая как количество секунд с 1970-01-01T00:00:00Z.
  • @DurationMillisLong : Значение — неотрицательная длительность в миллисекундах.
  • @ElapsedRealtimeLong : Значение — неотрицательная временная метка в базе времени SystemClock.elapsedRealtime() .
  • @UptimeMillisLong : Значение — неотрицательная временная метка в базе времени SystemClock.uptimeMillis() .

Примитивные параметры времени или возвращаемые значения должны использовать long , а не int .

ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);

Методы, выражающие единицы времени, должны предпочитать несокращенные сокращения для названий единиц.

public void setIntervalNs(long intervalNs);

public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);

public void setTimeoutMicros(long timeoutMicros);

Аннотируйте длительные аргументы

Платформа включает в себя несколько аннотаций, обеспечивающих более строгую типизацию для long единиц времени:

  • @CurrentTimeMillisLong : Значение — неотрицательная временная метка, измеряемая как количество миллисекунд с 1970-01-01T00:00:00Z , то есть во временной базе System.currentTimeMillis() .
  • @CurrentTimeSecondsLong : Значение — неотрицательная временная метка, измеряемая как количество секунд с 1970-01-01T00:00:00Z .
  • @DurationMillisLong : Значение — неотрицательная длительность в миллисекундах.
  • @ElapsedRealtimeLong : Значение представляет собой неотрицательную временную метку в базе времени SystemClock#elapsedRealtime() .
  • @UptimeMillisLong : Значение представляет собой неотрицательную временную метку в базе времени SystemClock#uptimeMillis() .

Единицы измерения

Для всех методов, выражающих единицы измерения, отличные от времени, предпочитайте префиксы единиц СИ в стиле CamelCased.

public  long[] getFrequenciesKhz();

public  float getStreamVolumeDb();

Поместите необязательные параметры в конец перегрузок

Если у вас есть перегрузки метода с необязательными параметрами, размещайте эти параметры в конце и соблюдайте согласованный порядок с другими параметрами:

public int doFoo(boolean flag);

public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);

public int doFoo(boolean flag, int id);

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

Следствие: Не перегружайте методы, кроме как для добавления необязательных аргументов или принятия различных типов аргументов, если метод полиморфный. Если перегруженный метод делает что-то принципиально иное, то дайте ему новое имя.

Методы с параметрами по умолчанию должны быть аннотированы @JvmOverloads (только Kotlin)

Методы и конструкторы с параметрами по умолчанию должны быть аннотированы @JvmOverloads для поддержания двоичной совместимости.

Более подробную информацию см. в разделе Перегрузки функций для значений по умолчанию в официальном руководстве по взаимодействию Kotlin-Java.

class Greeting @JvmOverloads constructor(
  loudness: Int = 5
) {
  @JvmOverloads
  fun sayHello(prefix: String = "Dr.", name: String) = // ...
}

Не удалять значения параметров по умолчанию (только Kotlin)

Если метод поставляется с параметром со значением по умолчанию, удаление значения по умолчанию является нарушением исходного кода.

Наиболее отличительные и идентифицирующие параметры метода должны быть первыми

Если у вас есть метод с несколькими параметрами, поместите наиболее релевантные из них в первую очередь. Параметры, которые определяют флаги и другие опции, менее важны, чем те, которые описывают объект, над которым выполняется действие. Если есть обратный вызов завершения, поместите его в последнюю очередь.

public void openFile(int flags, String name);

public void openFileAsync(OnFileOpenedListener listener, String name, int flags);

public void setFlags(int mask, int flags);
public void openFile(String name, int flags);

public void openFileAsync(String name, int flags, OnFileOpenedListener listener);

public void setFlags(int flags, int mask);

См. также: Помещайте необязательные параметры в конец перегрузок

Строители

Шаблон Builder рекомендуется для создания сложных объектов Java и обычно используется в Android в случаях, когда:

  • Свойства полученного объекта должны быть неизменяемыми.
  • Существует большое количество обязательных свойств, например, множество аргументов конструктора.
  • Между свойствами во время строительства существует сложная взаимосвязь, например, требуется шаг проверки. Обратите внимание, что этот уровень сложности часто указывает на проблемы с удобством использования API.

Подумайте, нужен ли вам конструктор. Конструкторы полезны в API-поверхности, если они используются для:

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

Если у вас есть тип с тремя или менее обязательными параметрами и без дополнительных параметров, вы почти всегда можете пропустить конструктор и использовать простой конструктор.

Классы на основе Kotlin должны отдавать предпочтение конструкторам с аннотацией @JvmOverloads и аргументами по умолчанию вместо Builders, но могут улучшить удобство использования для клиентов Java, также предоставив Builders в случаях, описанных ранее.

class Tone @JvmOverloads constructor(
  val duration: Long = 1000,
  val frequency: Int = 2600,
  val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
  class Builder {
    // ...
  }
}

Классы строителей должны возвращать строителя

Классы Builder должны включать цепочку методов, возвращая объект Builder (например, this ) из каждого метода, кроме build() . Дополнительные построенные объекты должны передаваться как аргументы — не возвращайте builder другого объекта. Например:

public static class Builder {
  public void setDuration(long);
  public void setFrequency(int);
  public DtmfConfigBuilder addDtmfConfig();
  public Tone build();
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}

В редких случаях, когда базовый класс конструктора должен поддерживать расширение, используйте универсальный тип возвращаемого значения:

public abstract class Builder<T extends Builder<T>> {
  abstract T setValue(int);
}

public class TypeBuilder<T extends TypeBuilder<T>> extends Builder<T> {
  T setValue(int);
  T setTypeSpecificValue(long);
}

Классы-строители должны создаваться через конструктор.

Для поддержания согласованного создания конструктора через поверхность API Android все конструкторы должны создаваться через конструктор, а не статический метод-создатель. Для API на основе Kotlin Builder должен быть публичным, даже если ожидается, что пользователи Kotlin будут неявно полагаться на конструктор через механизм создания в стиле метода фабрики/DSL. Библиотеки не должны использовать @PublishedApi internal для выборочного скрытия конструктора класса Builder от клиентов Kotlin.

public class Tone {
  public static Builder builder();
  public static class Builder {
  }
}
public class Tone {
  public static class Builder {
    public Builder();
  }
}

Все аргументы конструкторов-строителей должны быть обязательными (например, @NonNull)

Необязательные аргументы, например @Nullable , следует переместить в методы-сеттеры. Конструктор строителя должен выдать исключение NullPointerException (рассмотрите возможность использования Objects.requireNonNull ), если не указаны какие-либо обязательные аргументы.

Классы-строители должны быть конечными статическими внутренними классами своих построенных типов.

В целях логической организации внутри пакета классы-строители обычно следует представлять как конечные внутренние классы их построенных типов, например Tone.Builder , а не ToneBuilder .

Строители могут включать конструктор для создания нового экземпляра из существующего экземпляра.

Строители могут включать конструктор копирования для создания нового экземпляра строителя из существующего строителя или построенного объекта. Они не должны предоставлять альтернативные методы для создания экземпляров строителя из существующих строителей или объектов сборки.

public class Tone {
  public static class Builder {
    public Builder clone();
  }

  public Builder toBuilder();
}
public class Tone {
  public static class Builder {
    public Builder(Builder original);
    public Builder(Tone original);
  }
}
Сеттеры конструктора должны принимать аргументы @Nullable, если у конструктора есть конструктор копирования.

Сброс необходим, если новый экземпляр строителя может быть создан из существующего экземпляра. Если конструктор копирования недоступен, то строитель может иметь аргументы @Nullable или @NonNullable .

public static class Builder {
  public Builder(Builder original);
  public Builder setObjectValue(@Nullable Object value);
}
Сеттеры Builder могут принимать аргументы @Nullable для необязательных свойств.

Часто проще использовать значение, допускающее значение NULL, для ввода второй степени, особенно в Kotlin, где вместо конструкторов и перегрузок используются аргументы по умолчанию.

Кроме того, сеттеры @Nullable будут сопоставлять их со своими геттерами, которые должны быть @Nullable для необязательных свойств.

Value createValue(@Nullable OptionalValue optionalValue) {
  Value.Builder builder = new Value.Builder();
  if (optionalValue != null) {
    builder.setOptionalValue(optionalValue);
  }
  return builder.build();
}
Value createValue(@Nullable OptionalValue optionalValue) {
  return new Value.Builder()
    .setOptionalValue(optionalValue);
    .build();
}

// Or in other cases:

Value createValue() {
  return new Value.Builder()
    .setOptionalValue(condition ? new OptionalValue() : null);
    .build();
}

Распространенное использование в Kotlin:

fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .apply { optionalValue?.let { setOptionalValue(it) } }
    .build()
fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .setOptionalValue(optionalValue)
    .build()

Значение по умолчанию (если сеттер не вызывается) и значение null должны быть надлежащим образом задокументированы как в сеттере, так и в геттере.

/**
 * ...
 *
 * <p>Defaults to {@code null}, which means the optional value won't be used.
 */

Сеттеры Builder могут быть предоставлены для изменяемых свойств, если сеттеры доступны в построенном классе.

Если ваш класс имеет изменяемые свойства и нуждается в классе Builder , сначала спросите себя , действительно ли ваш класс должен иметь изменяемые свойства.

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

  1. Созданный объект должен быть готов к использованию немедленно, поэтому для всех соответствующих свойств, как изменяемых, так и неизменяемых, должны быть предусмотрены сеттеры.

    map.put(key, new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .setUsefulMutableProperty(usefulValue)
        .build());
    
  2. Возможно, потребуется сделать некоторые дополнительные вызовы, прежде чем созданный объект станет полезным, поэтому для изменяемых свойств не следует предоставлять сеттеры.

    Value v = new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .build();
    v.setUsefulMutableProperty(usefulValue)
    Result r = v.performSomeAction();
    Key k = callSomeMethod(r);
    map.put(k, v);
    

Не смешивайте эти два сценария.

Value v = new Value.Builder(requiredValue)
    .setImmutableProperty(immutableValue)
    .setUsefulMutableProperty(usefulValue)
    .build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);

Строители не должны иметь добытчиков

Getter должен быть на построенном объекте, а не на строителе.

Сеттеры строителя должны иметь соответствующие геттеры в построенном классе.

public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }

  public long getDuration();
  public int getFrequency();
  public @NonNull List<DtmfConfig> getDtmfConfigs();
}

Именование методов строителя

Имена методов конструктора должны использовать стиль setFoo() , addFoo() или clearFoo() .

Классы Builder должны объявлять метод build().

Классы Builder должны объявлять метод build() , который возвращает экземпляр построенного объекта.

Методы build() конструктора должны возвращать объекты @NonNull

Метод build() строителя должен возвращать ненулевой экземпляр сконструированного объекта. В случае, если объект не может быть создан из-за недопустимых параметров, проверка может быть отложена до метода build, и должно быть выдано исключение IllegalStateException .

Не открывайте внутренние замки

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

Вместо этого выполните любую необходимую блокировку внутреннего, закрытого объекта.

public synchronized void doThing() { ... }
private final Object mThingLock = new Object();

public void doThing() {
  synchronized (mThingLock) {
    ...
  }
}

Методы в стиле аксессоров должны следовать рекомендациям Kotlin по свойствам.

При просмотре из исходников Kotlin методы в стиле accessor — те, которые используют префиксы get , set или is , — также будут доступны как свойства Kotlin. Например, int getField() определенный в Java, доступен в Kotlin как свойство val field: Int .

По этой причине и для того, чтобы в целом соответствовать ожиданиям разработчиков относительно поведения метода доступа, методы, использующие префиксы метода доступа, должны вести себя аналогично полям Java. Избегайте использования префиксов в стиле доступа, когда:

  • У метода есть побочные эффекты — предпочитайте более описательное название метода.
  • Метод требует больших вычислительных затрат — предпочитаем compute
  • Метод включает блокировку или иную длительную работу по возврату значения, например, IPC или другой ввод-вывод — предпочтительнее fetch
  • Метод блокирует поток до тех пор, пока он не сможет вернуть значение — предпочтительнее await
  • Метод возвращает новый экземпляр объекта при каждом вызове — предпочтительнее create
  • Метод не может успешно вернуть значение — предпочитайте request

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

Используйте префикс is для булевых методов доступа

Это стандартная конвенция об именах для логических методов и полей в Java. Как правило, логический метод и имена переменных должны быть записаны в виде вопросов, на которые отвечает возвратное значение.

МЕТОДЫ БОЛЕГА ДЖАВА БОЛЕЙСКИЙ ДОПОЛНИТЕЛЬНЫЙ ДОЛЖНЫ СЛЕДУЮЩИЙ set / is ИЗНАРЕНИЯ, и поля должны предпочитать, is в: В:

// Visibility is a direct property. The object "is" visible:
void setVisible(boolean visible);
boolean isVisible();

// Factory reset protection is an indirect property.
void setFactoryResetProtectionEnabled(boolean enabled);
boolean isFactoryResetProtectionEnabled();

final boolean isAvailable;

Использование set / is для методов аксессуара Java или для is Java позволит их использовать в качестве свойств от Kotlin:

obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return

Свойства и методы аксессуаров, как правило, должны использовать положительное именование, например, Enabled , а не Disabled . Использование негативной терминологии инвертирует значение true и false и затрудняет рассуждать о поведении.

// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);

В тех случаях, когда логический описывает включение или владение имуществом, вы можете использовать , а не есть ; Тем не менее, это не будет работать с синтаксисом свойств Kotlin:

// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();

Некоторые альтернативные префиксы, которые могут быть более подходящими, включают в себя и должны :

// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();

// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();

Методы, которые переключают поведение или функции, могут использовать префикс и включенный суффикс:

// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()

Точно так же методы, которые указывают на зависимость от других поведений или функций, могут использовать префикс и поддерживается или требуется суффикс:

// "Supported" describes whether this API would work on devices that support
// multiple users. The API "supports" multi-user:
void setMultiUserSupported(boolean supported)
boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support
// multiple users. The API "requires" multi-user:
void setMultiUserRequired(boolean required)
boolean isMultiUserRequired()

Как правило, имена методов должны быть написаны в виде вопросов, на которые отвечает возвращаемое значение.

Методы свойства Kotlin

Для свойства класса var foo: Foo Kotlin будет генерировать методы get / set , используя согласованное правило: приготовьте get и прописайте первый символ для GetTer, приготовьте set и введу первым символом для сеттера. Декларация недвижимости будет производить методы под названием public Foo getFoo() и public void setFoo(Foo foo) соответственно.

Если свойство имеет Boolean дополнительное правило применяется в генерации имени: если имя свойства начинается с is , то get не сработал для имени метода Getter, в качестве Getter используется само имена свойства. Следовательно, предпочитаю именовать Boolean свойства с префиксом is , чтобы следовать руководству по именованию:

var isVisible: Boolean

Если ваша собственность является одним из вышеупомянутых исключений и начинается с соответствующего префикса, используйте аннотацию @get:JvmName на свойстве, чтобы вручную укажите соответствующее имя:

@get:JvmName("hasTransientState")
var hasTransientState: Boolean

@get:JvmName("canRecord")
var canRecord: Boolean

@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean

Битсаска аксессов

См. Используйте @IntDef для флагов Bitmask для руководящих принципов API, касающихся определения флагов BitMask.

Сеттеры

Должны быть предоставлены два метода сеттера: один, который берет полный битстроинг и перезаписывает все существующие флаги, а другой, который берет на себя пользовательскую битмаску, чтобы обеспечить большую гибкость.

/**
 * Sets the state of all scroll indicators.
 * <p>
 * See {@link #setScrollIndicators(int, int)} for usage information.
 *
 * @param indicators a bitmask of indicators that should be enabled, or
 *                   {@code 0} to disable all indicators
 * @see #setScrollIndicators(int, int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators);

/**
 * Sets the state of the scroll indicators specified by the mask. To change
 * all scroll indicators at once, see {@link #setScrollIndicators(int)}.
 * <p>
 * When a scroll indicator is enabled, it will be displayed if the view
 * can scroll in the direction of the indicator.
 * <p>
 * Multiple indicator types may be enabled or disabled by passing the
 * logical OR of the specified types. If multiple types are specified, they
 * will all be set to the same enabled state.
 * <p>
 * For example, to enable the top scroll indicator:
 * {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
 * <p>
 * To disable the top scroll indicator:
 * {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
 *
 * @param indicators a bitmask of values to set; may be a single flag,
 *                   the logical OR of multiple flags, or 0 to clear
 * @param mask a bitmask indicating which indicator flags to modify
 * @see #setScrollIndicators(int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);

Геттеры

Один полученщик должен быть предоставлен для получения полной битмаски.

/**
 * Returns a bitmask representing the enabled scroll indicators.
 * <p>
 * For example, if the top and left scroll indicators are enabled and all
 * other indicators are disabled, the return value will be
 * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
 * <p>
 * To check whether the bottom scroll indicator is enabled, use the value
 * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
 *
 * @return a bitmask representing the enabled scroll indicators
 */
@ScrollIndicators
public int getScrollIndicators();

Используйте public вместо protected

Всегда предпочитаю public protected в публичном API. Защищенный доступ в конечном итоге в конечном итоге становится болезненным, потому что исполнители должны переопределить, чтобы предоставить общественные аксессуары в тех случаях, когда внешний доступ по умолчанию был бы таким же хорошим.

Помните, что protected видимость не мешает разработчикам называть API - это только делает его немного более отвратительным.

Реализуйте ни один или обоих equals () и hashcode ()

Если вы переопределяете один, вы должны отменить другой.

Реализовать toString () для классов данных

Классу данных рекомендуется переопределить toString() , чтобы помочь разработчикам отладить их код.

Документируйте, является ли вывод для поведения программы или отладки

Решите, хотите ли вы поведение программы полагаться на вашу реализацию или нет. Например, uuid.tostring () и file.tostring () документируют их конкретный формат для использования программ. Если вы разоблачиваете информацию только для отладки, например, намерения , тогда подразумевайте наследуйте документы от Superclass.

Не включайте дополнительную информацию

Вся информация, доступная от toString() , также должна быть доступна через публичный API объекта. В противном случае вы поощряете разработчиков разрабатывать и полагаться на свой выход toString() , что предотвратит будущие изменения. Хорошей практикой является внедрение toString() с использованием только публичного API объекта.

Не поощряйте порядок в выводе отладки

Хотя невозможно помешать разработчикам в зависимости от вывода отладки, включая System.identityHashCode Identityhashcode вашего объекта в результате вывода toString() сделает очень маловероятным, что два разных объекта будут иметь равный выход toString() .

@Override
public String toString() {
  return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}

Это может эффективно отговорить разработчиков от написания тестовых утверждений, таких как assertThat(a.toString()).isEqualTo(b.toString()) на ваших объектах.

Используйте CreateFoo при возвращении вновь созданных объектов

Используйте префикс create , не get или new , для методов, которые будут создавать возвращаемые значения, например, путем построения новых объектов.

Когда метод создаст объект для возврата, проясните его в имени метода.

public FooThing getFooThing() {
  return new FooThing();
}
public FooThing createFooThing() {
  return new FooThing();
}

Методы, принимающие объекты файла, также должны принимать потоки

Места хранения данных на Android не всегда файлы на диске. Например, контент, переданный через границы пользователей, представлен как content:// Uri s. Чтобы включить обработку различных источников данных, API, которые принимают объекты File также должны принимать InputStream , OutputStream или обоих.

public void setDataSource(File file)
public void setDataSource(InputStream stream)

Возьмите и возвращайте необработанные примитивы вместо штучной версии

Если вам нужно сообщить о пропавших или нулевых значениях, рассмотрите возможность использования -1 , Integer.MAX_VALUE или Integer.MIN_VALUE .

public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)

Избегание классовых эквивалентов примитивных типов позволяет избежать накладных расходов на память этих классов, доступа метода к значениям и, что более важно, автобоксинг, который связан с литой между примитивными типами и типами объектов. Избегание этого поведения экономит на памяти и на временные распределения, которые могут привести к дорогостоящим и более частым коллекциям мусора.

Используйте аннотации, чтобы уточнить допустимый параметр и возвращаемые значения

Аннотации разработчика были добавлены, чтобы помочь прояснить допустимые значения в различных ситуациях. Это облегчает для инструментов помогать разработчикам, когда они обеспечивают неправильные значения (например, передача произвольного int , когда для структуры требуется один из конкретных наборов постоянных значений). Используйте любые и все следующие аннотации, когда это необходимо:

Нуль

Явные аннотации Nullabity требуются для API -интерфейсов Java, но концепция нуля является частью языка котлин, и аннотации Nullability никогда не должны использоваться в API Kotlin.

@Nullable : указывает, что данное возвращаемое значение, параметр или поле может быть нулевым:

@Nullable
public String getName()

public void setName(@Nullable String name)

@NonNull : указывает, что данное возвращаемое значение, параметр или поле не могут быть нулевыми. Маркировка вещей как @Nullable является относительно новой для Android, поэтому большинство методов API API Android не последовательно задокументированы. Поэтому у нас есть три государства «Неизвестно, @Nullable , @NonNull », поэтому @NonNull является частью руководящих принципов API:

@NonNull
public String getName()

public void setName(@NonNull String name)

Для документов Android платформы аннотирование параметров вашего метода автоматически генерирует документацию в форме «Это значение может быть нулевым». Если «NULL» явно используется в другом месте в DOC.

Существующие «не очень нулевые» методы: существующие методы в API без объявленной @Nullable аннотации могут быть аннотированы @Nullable если метод может вернуть null при конкретных, очевидных обстоятельствах (например, findViewById() ). Companion @NotNull requireFoo() , которые должны быть добавлены IllegalArgumentException для разработчиков, которые не хотят быть нулевым чеком.

Методы интерфейса: новые API должны добавлять правильную аннотацию при реализации методов интерфейса, таких как Parcelable.writeToParcel() (то есть, этот метод в классе реализации должен быть writeToParcel(@NonNull Parcel, int) , а не writeToParcel(Parcel, int) ); Однако существующие API, которым не хватает аннотаций, не должны быть «фиксированными».

Принудительное правоприменение

В Java рекомендуются выполнять проверку ввода для параметров @NonNull с использованием Objects.requireNonNull() и бросить NullPointerException , когда параметры являются нулевыми. Это автоматически выполняется в Котлине.

Ресурсы

Идентификаторы ресурсов: целочисленные параметры, которые обозначают идентификаторы для конкретных ресурсов, должны быть аннотированы с соответствующим определением типа ресурса. Существует аннотация для каждого типа ресурсов, таких как @StringRes , @ColorRes и @AnimRes , в дополнение к All @AnyRes . Например:

public void setTitle(@StringRes int resId)

@Intdef для постоянных наборов

Волшебные константы: параметры String и int , которые предназначены для получения одного из конечного набора возможных значений, обозначаемых публичными константами, должны быть надлежащим образом аннотированы с @StringDef или @IntDef . Эти аннотации позволяют создавать новую аннотацию, которую вы можете использовать, которая работает как Typedef для допустимых параметров. Например:

/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
  NAVIGATION_MODE_STANDARD,
  NAVIGATION_MODE_LIST,
  NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);

Методы рекомендуются проверить обоснованность аннотированных параметров и бросить IllegalArgumentException , если параметр не является частью @IntDef

@Intdef для флагов Bitmask

Аннотация также может указать, что константы являются флагами, и может быть объединена с & и я:

/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
  FLAG_USE_LOGO,
  FLAG_SHOW_HOME,
  FLAG_HOME_AS_UP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}

@StringDef для строковых постоянных наборов

Существует также аннотация @StringDef , которая точно так же, как @IntDef в предыдущем разделе, но для констант String . Вы можете включить несколько значений «префикс», которые используются для автоматического излучения документации для всех значений.

@Sdkconstant для констант SDK

@SDKConstant Аннотируйте публичные поля, когда они являются одним из этих значений SdkConstant : ACTIVITY_INTENT_ACTION , BROADCAST_INTENT_ACTION , SERVICE_ACTION , INTENT_CATEGORY , FEATURE .

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";

Обеспечить совместимую нулясь для переопределения

Для совместимости API нулевая переопределения должна быть совместима с текущей нуляемостью родителя. Следующая таблица представляет ожидания совместимости. Ясно, что переопределения должны быть такими же ограничительными или более ограничительными, чем элемент, который они переопределяют.

Тип Родитель Ребенок
Тип возврата Безнотируется Безнотированный или непустульный
Тип возврата Нулевой Нулевой или не ненуль
Тип возврата Ноннулл Ноннулл
Веселый аргумент Безнотируется Нездоровый или нулевый
Веселый аргумент Нулевой Нулевой
Веселый аргумент Ноннулл Нулевой или не ненуль

Предпочитают не нулевые (такие как @nonnull) аргументы, где это возможно

Когда методы перегружены, предпочитайте, чтобы все аргументы были недушными.

public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }

Это правило применяется и к перегруженным сеттерам свойств. Основной аргумент должен быть ненуль, а очистка имущества должна быть реализована как отдельный метод. Это предотвращает вызовы «бессмысленных», где разработчик должен установить параметры следа, даже если они не требуются.

public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode)
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading)

// Nonsense call to clear property
setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode)
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading)
public void clearTitleItem()

Предпочитают не нулевые (например, @nonnull) типы возврата для контейнеров

Для типов контейнеров, таких как Bundle или Collection , верните пустой - и неизбежный, где это применимо - контейнер. В тех случаях, когда null будет использоваться для различения доступности контейнера, рассмотрите возможность предоставления отдельного логического метода.

@NonNull
public Bundle getExtras() { ... }

Аннотации с нуля для получения и установленных пар должны согласиться с

Получить и установить пары методов для одного логического свойства всегда должны согласоваться в своих аннотациях. Неспособность следовать этому руководству победит синтаксис свойств Котлина, а добавление не согласных аннотаций нуля в существующие методы свойства является изменением источника для пользователей Kotlin.

@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }

Возвращение значения в условиях сбоя или ошибок

Все API должны разрешить приложениям реагировать на ошибки. Возвращение false , -1 , null или других значений «что -то пошло не так», не рассказывают разработчику достаточно о неспособности установить ожидания пользователей или точно отслеживать надежность своего приложения в этой области. При разработке API представьте, что вы строите приложение. Если вы столкнетесь с ошибкой, дает ли API достаточно информации, чтобы представить ее пользователю или отреагировать надлежащим образом?

  1. Это нормально (и поощрено) включать подробную информацию в сообщение об исключении, но разработчикам не нужно проанализировать ее для правильного обработки ошибки. Коды по ошибкам словесных ошибок или другая информация должны быть обнаружены в качестве методов.
  2. Убедитесь, что выбранная вами опция обработки ошибок дает вам гибкость для представления новых типов ошибок в будущем. Для @IntDef это означает, что включение OTHER или UNKNOWN значения - при возврате нового кода вы можете проверить targetSdkVersion вызывающего абонента, чтобы избежать возврата кода ошибки, о котором приложение не знает. Для исключений есть общий суперкласс, который реализуют ваши исключения, так что любой код, который обрабатывает этот тип, также будет ловить и обрабатывать подтипы.
  3. Разработчику должно быть сложно или невозможно случайно игнорировать ошибку - если ваша ошибка передается, возвращая значение, аннотируйте ваш метод @CheckResult .

Предпочитаете бросить ? extends RuntimeException когда достигается условие сбоя или ошибки из -за чего -то, что разработчик сделал неправильно, например, игнорируя ограничения на входные параметры или не проверяя наблюдаемое состояние.

Сеттер или действие (например, perform ) Методы могут вернуть целочисленный код состояния, если действие может потерпеть неудачу в результате асинхронно обновленного состояния или условий вне контроля разработчика.

Коды статуса должны быть определены в содержащем класс как public static final поля, префикс с ERROR_ и перечислены в аннотации @hide @IntDef .

Названия методов всегда должны начинаться с глагола, а не с предметом

Название метода всегда должно начинаться с глагола (например, get , create , reload и т. Д.), А не с объектом, на котором вы действуете.

public void tableReload() {
  mTable.reload();
}
public void reloadTable() {
  mTable.reload();
}

Предпочитаю коллекцию Типы по массивам в виде возврата или типа параметров

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

Исключение для примитивов

Если элементы являются примитивами, вместо этого предпочитают массивы, чтобы избежать стоимости автоматического бокса. Смотрите и возвращайте необработанные примитивы вместо коробочных версий

Исключение для чувствительного к производительности кода

В определенных сценариях, где API используется в чувствительном к производительности коде (например, графике или в других API-интерфейсах MAKET/DRAING), приемлемо использовать массивы вместо коллекций, чтобы уменьшить ассигнования и отток памяти.

Исключение для Котлина

Массивы Kotlin инвариантны, а язык Kotlin обеспечивает достаточные утилиты вокруг массивов, поэтому массивы находятся на основе List и Collection для API Kotlin, предназначенных для доступа от Kotlin.

Предпочитаю коллекции @nonnull

Всегда предпочитаю @NonNull для объектов сбора. При возврате пустой коллекции используйте соответствующий метод Collections.empty . Empty, чтобы вернуть низкую стоимость, правильно напечатанную и неизменную объект сбора.

Где поддерживаются типовые аннотации, всегда предпочитают @NonNull для элементов сбора.

Вы также должны предпочесть @NonNull при использовании массивов вместо коллекций (см. Предыдущий элемент ). Если распределение объектов является проблемой, создайте постоянную и передайте ее - в конце концов, пустой массив неизменен. Пример:

private static final int[] EMPTY_USER_IDS = new int[0];

@NonNull
public int[] getUserIds() {
  int [] userIds = mService.getUserIds();
  return userIds != null ? userIds : EMPTY_USER_IDS;
}

Коллекция изменчивости

API Kotlin API должны предпочитать, что только для чтения (не Mutable ) типы возврата для коллекций по умолчанию, если только контракт API конкретно требует изменчивого типа возврата.

Java API, однако, должны предпочитать изменяемые типы возврата по умолчанию, потому что реализация API API -интерфейсов Android Platform еще не обеспечивает удобную реализацию неизменных коллекций. Исключением из этого правила является Collections.empty Повторные типы возврата, которые неизменны. В тех случаях, когда клиенты могут использовать изменяемость - нарочно или по ошибке - чтобы сломать предполагаемую модель использования API, API -интерфейсы Java должны настоятельно рассмотреть вопрос о возврате мелкой копии коллекции.

@Nullable
public PermissionInfo[] getGrantedPermissions() {
  return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
  if (mPermissions == null) {
    return Collections.emptySet();
  }
  return new ArraySet<>(mPermissions);
}

Явно изменяемые типы возврата

API, которые возвращают коллекции, в идеале не должны изменять возвращенный объект сбора после возвращения. Если возвращаемая коллекция должна измениться или быть повторно используемым в некотором роде - например, адаптированный представление о изменяемом наборе данных - точное поведение, когда содержимое может изменять, должно быть явно задокументировано или следовать установленным соглашениям об именовании API.

/**
 * Returns a view of this object as a list of [Item]s.
 */
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)

Конвенция Kotlin .asFoo() описана ниже и позволяет коллекции, возвращаемой .asList() изменить, если исходная коллекция изменится.

Изменчивость возвращаемых объектов типа данных

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

val tempResult = DataContainer()

fun add(other: DataContainer): DataContainer {
  tempResult.innerValue = innerValue + other.innerValue
  return tempResult
}
fun add(other: DataContainer): DataContainer {
  return DataContainer(innerValue + other.innerValue)
}

В чрезвычайно ограниченных случаях некоторый чувствительный к производительности код может извлечь выгоду из объединения или повторного использования объектов. Не пишите свою собственную структуру данных пула объектов и не выставляйте повторно используемые объекты в общедоступных API. В любом случае, будьте чрезвычайно осторожны с управлением параллельным доступом.

Использование типа параметра Vararg

Как Kotlin, так и Java API предлагается использовать vararg в тех случаях, когда разработчик может создать массив на месте вызова для единственной цели передачи нескольких связанных параметров одного и того же типа.

public void setFeatures(Feature[] features) { ... }

// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }

// Developer code
setFeatures(Features.A, Features.B, Features.C);

Защитные копии

Реализации как Java, так и Kotlin параметров vararg составляют с одним и тем же массивным байт-кодом и в результате могут быть вызваны из кода Java с изменчивым массивом. Дизайнерам API настоятельно рекомендуется создать оборонительную мелкую копию параметра массива в тех случаях, когда он будет сохраняться в поле или анонимном внутреннем классе.

public void setValues(SomeObject... values) {
   this.values = Arrays.copyOf(values, values.length);
}

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

Предоставьте правильную семантику с параметрами типа сбора или возвращенными типами

List<Foo> - опция по умолчанию, но рассмотрите другие типы, чтобы дать дополнительное значение:

  • Используйте Set<Foo> , если ваш API безразличен к порядку элементов, и это не позволяет дубликаты или дубликаты бессмысленно.

  • Collection<Foo>, если ваш API безразличен к порядку и позволяет дубликатам.

Функции преобразования Kotlin

Котлин часто использует .toFoo() и .asFoo() для получения объекта другого типа от существующего объекта, где Foo - это имя типа возврата преобразования. Это согласуется со знакомым jdk Object.toString() . Котлин берет это дальше, используя его для примитивных преобразований, таких как 25.toFloat() .

Различие между конверсиями по имени .toFoo() и .asFoo() является значительным:

Используйте .tofoo () при создании нового, независимого объекта

Как .toString() , «в» преобразование возвращает новый, независимый объект. Если исходный объект будет изменен позже, новый объект не будет отражать эти изменения. Точно так же, если новый объект будет изменен позже, старый объект не будет отражать эти изменения.

fun Foo.toBundle(): Bundle = Bundle().apply {
    putInt(FOO_VALUE_KEY, value)
}

Используйте .asfoo () при создании зависимой обертки, украшенного объекта или листа

Кастинг в котлине выполняется с использованием ключевого слова as . Это отражает изменение интерфейса , но не изменение идентичности. При использовании в качестве префикса в функции расширения. .asFoo() украшает приемник. Мутация в исходном объекте приемника будет отражена в объекте, возвращаемом asFoo() . Мутация в новом объекте Foo может быть отражена в исходном объекте.

fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
    collect {
        emit(it)
    }
}

Функции преобразования должны быть записаны в качестве функций расширения

Написание функций преобразования вне приемника и определений класса результатов уменьшает связь между типами. Идеальное преобразование требует только публичного API -доступ к исходному объекту. Это доказывает, что разработчик может также написать аналогичные преобразования в свои собственные предпочтительные типы.

Бросьте соответствующие конкретные исключения

Методы не должны бросать общие исключения, такие как java.lang.Exception или java.lang.Throwable , вместо этого необходимо использовать соответствующее конкретное исключение, как java.lang.NullPointerException , чтобы позволить разработчикам обрабатывать исключения, не будучи чрезмерно широкими.

Ошибки, которые не связаны с аргументами, предоставленными непосредственно для публичного метода, должны бросить java.lang.IllegalStateException вместо java.lang.IllegalArgumentException или java.lang.NullPointerException .

Слушатели и обратные вызовы

Это правила вокруг классов и методов, используемых для слушателя и механизмов обратного вызова.

Имена классов обратного вызова должны быть единственными

Используйте MyObjectCallback вместо MyObjectCallbacks .

Имена методов обратного вызова должны быть в формате на

onFooEvent означает, что FooEvent происходит и что обратный вызов должен действовать в ответ.

Прошлое и нынешнее время должно описать поведение времени

Методы обратного вызова, касающиеся событий, должны быть названы, чтобы указать, произошло ли событие или происходит в процессе происхождения.

Например, если метод вызывается после выполнения действия клика:

public void onClicked()

Однако, если метод отвечает за выполнение действия Click:

public boolean onClick()

Регистрация обратного вызова

Когда слушатель или обратный вызов могут быть добавлены или удалены из объекта, соответствующие методы должны быть названы добавлять и удалять или зарегистрировать и не регистрировать. Будьте в соответствии с существующим соглашением, используемым классом или другими классами в том же пакете. Когда такого прецедента не существует, предпочитайте добавить и удалить.

Методы, включающие регистрацию или нерегистрированные обратные вызовы, должны указывать целое имя типа обратного вызова.

public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);

Избегайте обратных вызовов

Не добавляйте методы getFooCallback() . Это заманчивый люк Escape для случаев, когда разработчики могут захотеть подключить существующий обратный вызов вместе со своей собственной заменой, но он хрупкий и затрудняет рассуждение текущего состояния для разработчиков компонентов. Например,

  • Разработчик A Calls setFooCallback(a)
  • Разработчик B Calls setFooCallback(new B(getFooCallback()))
  • Разработчик A желает удалить свой обратный вызов a и не может сделать это без знания типа B , и B был создан, чтобы позволить такие модификации его обернутого обратного вызова.

Принять исполнителя для управления отправкой обратного вызова

При регистрации обратных вызовов, которые не имеют явных ожиданий потоков (в значительной степени от того, где за пределами инструментария пользовательского интерфейса), настоятельно рекомендуется включить параметр Executor в рамках регистрации, чтобы позволить разработчику указать поток, на который будут вызовы обратные вызовы.

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

В качестве исключения из наших обычных рекомендаций о дополнительных параметрах приемлемо предоставить перегрузку, пропуская Executor , даже если это не является окончательным аргументом в списке параметров. Если Executor не предоставлен, обратный вызов должен быть вызван в основном потоке с использованием Looper.getMainLooper() , и это должно быть задокументировано на связанном перегруженном методе.

/**
 * ...
 * Note that the callback will be executed on the main thread using
 * {@link Looper.getMainLooper()}. To specify the execution thread, use
 * {@link registerFooCallback(Executor, FooCallback)}.
 * ...
 */
public void registerFooCallback(
    @NonNull FooCallback callback)

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Реализация Executor GotChas: Обратите внимание, что следующее приведено действительный исполнитель!

public class SynchronousExecutor implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

Это означает, что при внедрении API, которые принимают эту форму, ваш входящий реализация объекта Binder на стороне процесса приложения должна вызвать Binder.clearCallingIdentity() прежде чем призвать обратный вызов приложения на исполненном Executor . Таким образом, любой код приложения, который использует идентификацию связующего (например, Binder.getCallingUid() ) для проверки разрешения, правильно приписывает код, работающий в приложение, а не в системный процесс, вызывая в приложение. Если пользователи вашего API хотят получить информацию об UID или PID вызывающего абонента, то это должно быть явной частью вашей поверхности API, а не подразумевающимся на основе того, где работал Executor , который они поставляли.

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

Если приложение собирается запустить дорогой код где -то в своем собственном процессе, пусть они . Обходные пути, которые разработчики приложений найдут для преодоления ваших ограничений, будут гораздо сложнее поддержать в долгосрочной перспективе.

Исключение для единого обратного вызова: когда природа сообщаемых событий требует только одного экземпляра обратного вызова, используйте следующий стиль:

public void setFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

public void clearFooCallback()

Используйте исполнителя вместо обработчика

Handler Android был использован в качестве стандарта для перенаправления выполнения обратного вызова в конкретный поток Looper в прошлом. Этот стандарт был изменен, чтобы предпочитать Executor , так как большинство разработчиков приложений управляют своими пулами потоков, что делает основной поток или пользовательский поток единственным потоком Looper , доступным для приложения. Используйте Executor , чтобы дать разработчикам контроль, необходимый для повторного использования своих существующих/предпочтительных контекстов выполнения.

Современные библиотеки параллелизма, такие как kotlinx.coroutines или rxjava, предоставляют свои собственные механизмы планирования, которые при необходимости выполняют свою собственную отправку, что делает важной для обеспечения возможности использования прямого исполнителя (например, Runnable::run ), чтобы избежать задержки из двойных прыжков потока. Например, один прыжок, чтобы опубликовать в Looper потоке, используя Handler за которым следует еще один прыжок из структуры парарокоза приложения.

Исключения из этого руководства редки. Общие апелляции на исключение включают:

Я должен использовать Looper , потому что мне нужно, чтобы epoll для этого события нуждается в Looper . Этот запрос на исключение предоставляется, поскольку преимущества Executor не могут быть реализованы в этой ситуации.

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

Handler локально согласуется с другими подобными API в одном классе. Этот запрос на исключение предоставляется в ситуации. Предпочтение для добавления перегрузки на основе Executor , миграции реализаций Handler для использования новой реализации Executor . ( myHandler::post -действующий Executor !) В зависимости от размера класса, количества существующих методов Handler и вероятности того, что разработчикам необходимо будет использовать существующие методы на основе Handler наряду с новым методом, исключение может быть предоставлено для добавления нового метода на основе Handler .

Симметрия в регистрации

Если есть способ добавить или зарегистрировать что -то, также должен быть способ удалить/не регистрировать его. Метод

registerThing(Thing)

должен иметь соответствующий

unregisterThing(Thing)

Предоставьте идентификатор запроса

Если для разработчика разумно повторно использовать обратный вызов, предоставьте объект идентификатора, чтобы связать обратный вызов с запросом.

class RequestParameters {
  public int getId() { ... }
}

class RequestExecutor {
  public void executeRequest(
    RequestParameters parameters,
    Consumer<RequestParameters> onRequestCompletedListener) { ... }
}

Несколько методов обратного вызова объектов

Несколько методов обратных вызовов должны предпочитать interface и использовать методы default при добавлении к ранее выпущенным интерфейсам. Ранее это руководство рекомендовало abstract class из -за отсутствия методов default в Java 7.

public interface MostlyOptionalCallback {
  void onImportantAction();
  default void onOptionalInformation() {
    // Empty stub, this method is optional.
  }
}

Используйте android.os.outcomereceiver при моделировании вызова неблокирующей функции

OutcomeReceiver<R,E> сообщает о значении результата R при успешном или E : Throwable иначе - то же самое, что может сделать простым вызовом метода. Используйте OutcomeReceiver в качестве типа обратного вызова при преобразовании метода блокировки, который возвращает результат или бросает исключение из неблокирующего асинхронного метода:

interface FooType {
  // Before:
  public FooResult requestFoo(FooRequest request);

  // After:
  public void requestFooAsync(FooRequest request, Executor executor,
      OutcomeReceiver<FooResult, Throwable> callback);
}

Асинхронные методы, преобразованные таким образом, всегда возвращают void . В вместо этого сообщается, что любой результат, который requestFoo , сообщается в requestFooAsync от callback Parameter's OutcomeReceiver.onResult , позвонив по его предоставленному executor . Любое исключение, которое будет выбросить requestFoo , сообщается методу OutcomeReceiver.onError таким же образом.

Использование OutcomeReceiver для сообщений о результатах Async Mepale также дает Kotlin suspend fun Wrapper для Async Methods androidx.core:core-ktx используя Continuation.asOutcomeReceiver

suspend fun FooType.requestFoo(request: FooRequest): FooResult =
  suspendCancellableCoroutine { continuation ->
    requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
  }

Подобные расширения позволяют клиентам Kotlin называть неблокирующие асинхронные методы с удобством простых вызовов функции, не блокируя вызову. Эти расширения 1-1 для API платформы могут быть предложены в рамках androidx.core:core-ktx в JetPack в сочетании со стандартными проверками совместимости версий и соображениями. См. Документацию для Asoutcomereceiver для получения дополнительной информации, соображений отмены и образцов.

Асинхронные методы, которые не соответствуют семантике метода, возвращающего результат или бросают исключение, когда его работа не завершена, не должны использовать OutcomeReceiver в качестве типа обратного вызова. Вместо этого рассмотрим один из других вариантов, перечисленных в следующем разделе.

Предпочитают функциональные интерфейсы, а не создание новых типов отдельных абстрактных методов (SAM)

Уровень API 24 добавил типы java.util.function.* ( Справочные документы ), которые предлагают общие интерфейсы SAM, такие как Consumer<T> , которые подходят для использования в качестве Lambdas обратного вызова. Во многих случаях создание новых интерфейсов SAM обеспечивает небольшую ценность с точки зрения безопасности типа или намерения связи, в то же время излишне расширяя площадь поверхности API API.

Подумайте об использовании этих общих интерфейсов, а не создание новых:

Размещение параметров SAM

Параметры SAM должны быть размещены последними, чтобы включить идиоматическое использование из Kotlin, даже если метод перегружен дополнительными параметрами.

public void schedule(Runnable runnable)

public void schedule(int delay, Runnable runnable)

Документы

Это правила об общественных документах (Javadoc) для API.

Все публичные API должны быть задокументированы.

Все публичные API должны иметь достаточную документацию, чтобы объяснить, как разработчик будет использовать API. Предположим, что разработчик обнаружил метод, использующий автозаполнение или во время просмотра через эталонные документы API и имеет минимальное количество контекста с соседней поверхности API (например, в том же классе).

Методы

Параметры метода и возвращаемые значения должны быть задокументированы с использованием аннотаций Docs @param и @return . Отформатируйте тело Javadoc, как будто ему предшествует «этот метод ...».

В тех случаях, когда метод не принимает параметров, не имеет особых соображений и возвращает то, что говорится в названии метода, вы можете опустить @return и написать документы, аналогичные:

/**
 * Returns the priority of the thread.
 */
@IntRange(from = 1, to = 10)
public int getPriority() { ... }

Документы должны связаться с другими документами для связанных константов, методов и других элементов. Используйте теги Javadoc (например, @see и {@link foo} ), а не просто текстовые слова.

Для следующего примера источника:

public static final int FOO = 0;
public static final int BAR = 1;

Не используйте необработанный текст или шрифт кода:

/**
 * Sets value to one of FOO or <code>BAR</code>.
 *
 * @param value the value being set, one of FOO or BAR
 */
public void setValue(int value) { ... }

Вместо этого используйте ссылки:

/**
 * Sets value to one of {@link #FOO} or {@link #BAR}.
 *
 * @param value the value being set
 */
public void setValue(@ValueType int value) { ... }

Обратите внимание, что использование аннотации IntDef такого как @ValueType на параметре, автоматически генерирует документацию с указанием разрешенных типов. См. Руководство по аннотациям для получения дополнительной информации об IntDef .

Запустите Update-API или Docs Target при добавлении Javadoc

Это правило особенно важно при добавлении тегов @link или @see , и убедитесь, что вывод выглядит так, как и ожидалось. Вывод ошибок в Javadoc часто связан с плохими ссылками. Либо update-api , либо docs Make Target выполняет эту проверку, но цель docs может быть быстрее, если вы меняете только Javadoc, и в противном случае не нужно запускать цель update-api .

Используйте {@code foo}, чтобы отличить значения Java

Оберните значения Java, такие как true , false и null с {@code...} чтобы отличить их от текста документации.

При написании документации в источниках Kotlin вы можете обернуть код закулисными такими, как вы, для уценки.

@param and @return summaries should be a single sentence fragment

Parameter and return value summaries should start with a lowercase character and contain only a single sentence fragment. If you have additional information that extends beyond a single sentence, move it to the method Javadoc body:

/**
 * @param e The element to be appended to the list. This must not be
 *       null. If the list contains no entries, this element will
 *       be added at the beginning.
 * @return This method returns true on success.
 */

Should be changed to:

/**
 * @param e element to be appended to this list, must be non-{@code null}
 * @return {@code true} on success, {@code false} otherwise
 */

Docs annotations need explanations

Document why annotations @hide and @removed are hidden from the public API. Include instructions for how to replace API elements marked with the @deprecated annotation.

Use @throws to document exceptions

If a method throws a checked exception, for example IOException , document the exception with @throws . For Kotlin-sourced APIs intended for use by Java clients, annotate functions with @Throws .

If a method throws an unchecked exception indicating a preventable error, for example IllegalArgumentException or IllegalStateException , document the exception with an explanation of why the exception is thrown. The thrown exception should also indicate why it was thrown.

Certain cases of unchecked exception are considered implicit and don't need to be documented, such as NullPointerException or IllegalArgumentException where an argument doesn't match an @IntDef or similar annotation that embeds the API contract into the method signature:

/**
 * ...
 * @throws IOException If it cannot find the schema for {@code toVersion}
 * @throws IllegalStateException If the schema validation fails
 */
public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
    boolean validateDroppedTables, Migration... migrations) throws IOException {
  // ...
  if (!dbPath.exists()) {
    throw new IllegalStateException("Cannot find the database file for " + name
        + ". Before calling runMigrations, you must first create the database "
        + "using createDatabase.");
  }
  // ...

Or, in Kotlin:

/**
 * ...
 * @throws IOException If something goes wrong reading the file, such as a bad
 *                     database header or missing permissions
 */
@Throws(IOException::class)
fun readVersion(databaseFile: File): Int {
  // ...
  val read = input.read(buffer)
    if (read != 4) {
      throw IOException("Bad database header, unable to read 4 bytes at " +
          "offset 60")
    }
  }
  // ...

If the method invokes asynchronous code that might throw exceptions, consider how the developer finds out about and responds to such exceptions. Typically this involves forwarding the exception to a callback and documenting the exceptions thrown on the method that receives them. Asynchronous exceptions shouldn't be documented with @throws unless they're actually rethrown from the annotated method.

End the first sentence of docs with a period

The Doclava tool parses docs simplistically, ending the synopsis doc (the first sentence, used in the quick description at the top of the class docs) as soon as it sees a period (.) followed by a space. This causes two problems:

  • If a short doc doesn't end with a period, and if that member has inherited docs that are picked up by the tool, then the synopsis also picks up those inherited docs. For example, see actionBarTabStyle in the R.attr docs , which has the description of the dimension added into the synopsis.
  • Avoid "eg" in the first sentence for the same reason, because Doclava ends the synopsis docs after "g.". For example, see TEXT_ALIGNMENT_CENTER in View.java . Note that Metalava automatically corrects this error by inserting a nonbreaking space after the period; however, don't make this mistake in the first place.

Format docs to be rendered in HTML

Javadoc is rendered in HTML, so format these docs accordingly:

  • Line breaks should use an explicit <p> tag. Don't add a closing </p> tag.

  • Don't use ASCII to render lists or tables.

  • Lists should use <ul> or <ol> for unordered and ordered, respectively. Each item should begin with an <li> tag, but doesn't need a closing </li> tag. A closing </ul> or </ol> tag is required after the last item.

  • Tables should use <table> , <tr> for rows, <th> for headers, and <td> for cells. All table tags require matching closing tags. You can use class="deprecated" on any tag to denote deprecation.

  • To create inline code font, use {@code foo} .

  • To create code blocks, use <pre> .

  • All text inside a <pre> block is parsed by the browser, so be careful with brackets <> . You can escape them with &lt; and &gt; HTML entities.

  • <pre>{@code <manifest>}</pre>
    

Follow the API reference style guide

To provide consistency in the style for class summaries, method descriptions, parameter descriptions, and other items, follow the recommendations in the official Java language guidelines at How to Write Doc Comments for the Javadoc Tool .

Android Framework-specific rules

These rules are about APIs, patterns, and data structures that are specific to APIs and behaviors built into the Android framework (for example, Bundle or Parcelable ).

Intent builders should use the create*Intent() pattern

Creators for intents should use methods named createFooIntent() .

Используйте Bundle вместо создания новых структур данных общего назначения

Avoid creating new general-purpose data structures to represent arbitrary key to typed value mappings. Instead, consider using Bundle .

This typically comes up when writing platform APIs that serve as communication channels between nonplatform apps and services, where the platform doesn't read the data sent across the channel and the API contract may be partially defined outside of the platform (for example, in a Jetpack library).

In cases where the platform does read the data, avoid using Bundle and prefer a strongly typed data class.

Parcelable implementations must have public CREATOR field

Parcelable inflation is exposed through CREATOR , not raw constructors. If a class implements Parcelable , then its CREATOR field must also be a public API and the class constructor taking a Parcel argument must be private.

Use CharSequence for UI strings

When a string is presented in a user interface, use CharSequence to allow for Spannable instances.

If it's just a key or some other label or value that isn't visible to users, String is fine.

Avoid using Enums

IntDef must be used over enums in all platform APIs, and should be strongly considered in unbundled, library APIs. Use enums only when you're certain that new values won't be added.

Benefits of IntDef :

  • Enables adding values over time
    • Kotlin when statements can fail at runtime if they become no-longer-exhaustive due to an added enum value in platform.
  • No classes or objects used at runtime, only primitives
    • While R8 or minfication can avoid this cost for unbundled library APIs, this optimization can't affect platform API classes.

Benefits of Enum

  • Idiomatic language feature of Java, Kotlin
  • Enables exhaustive switch, when statement usage
    • Note - values must not change over time, see previous list
  • Clearly scoped, and discoverable naming
  • Enables compile time verification
    • For example, a when statement in Kotlin that returns a value
  • Is a functioning class that can implement interfaces, have static helpers, expose member or extension methods, and expose fields.

Follow Android package layering hierarchy

The android.* package hierarchy has an implicit ordering, where lower-level packages can't depend on higher-level packages.

Avoid referring to Google, other companies, and their products

The Android platform is an open-source project and aims to be vendor neutral. The API should be generic and equally usable by system integrators or apps with the requisite permissions.

Parcelable implementations should be final

Parcelable classes defined by the platform are always loaded from framework.jar , so it is invalid for an app to try overriding a Parcelable implementation.

If the sending app extends a Parcelable , the receiving app won't have the sender's custom implementation to unpack with. Note about backward compatibility: if your class historically wasn't final, but didn't have a publicly available constructor, you still can mark it final .

Methods calling into system process should rethrow RemoteException as RuntimeException

RemoteException is typically thrown by internal AIDL, and indicates that the system process has died, or the app is trying to send too much data. In both cases, public API should rethrow as a RuntimeException to prevent apps from persisting security or policy decisions.

If you know the other side of a Binder call is the system process, this boilerplate code is the best-practice:

try {
    ...
} catch (RemoteException e) {
    throw e.rethrowFromSystemServer();
}

Throw specific exceptions for API changes

Public API behaviors might change across API levels and cause app crashes (for instance to enforce new security policies).

When the API needs to throw for a request that was previously valid, throw a new specific exception instead of a generic one. For example, ExportedFlagRequired instead of SecurityException (and ExportedFlagRequired can extend SecurityException ).

This will help app developers and tools detect API behavior changes.

Implement copy constructor instead of clone

Use of the Java clone() method is strongly discouraged due to the lack of API contracts provided by the Object class and difficulties inherent in extending classes that use clone() . Instead, use a copy constructor that takes an object of the same type.

/**
 * Constructs a shallow copy of {@code other}.
 */
public Foo(Foo other)

Classes that rely on a Builder for construction should consider adding a Builder copy constructor to allow modifications to the copy.

public class Foo {
    public static final class Builder {
        /**
         * Constructs a Foo builder using data from {@code other}.
         */
        public Builder(Foo other)

Use ParcelFileDescriptor over FileDescriptor

The java.io.FileDescriptor object has a poor definition of ownership, which can result in obscure use-after-close bugs. Instead, APIs should return or accept ParcelFileDescriptor instances. Legacy code can convert between PFD and FD if needed using dup() or getFileDescriptor() .

Avoid using odd-sized numerical values

Avoid using short or byte values directly, because they often limit how you might be able to evolve the API in the future.

Avoid using BitSet

java.util.BitSet is great for implementation but not for public API. It's mutable, requires an allocation for high-frequency method calls, and doesn't provide semantic meaning for what each bit represents.

For high-performance scenarios, use an int or long with @IntDef . For low-performance scenarios, consider a Set<EnumType> . For raw binary data, use byte[] .

Prefer android.net.Uri

android.net.Uri is the preferred encapsulation for URIs in Android APIs.

Avoid java.net.URI , because it is overly strict in parsing URIs, and never use java.net.URL , because its definition of equality is severely broken.

Hide annotations marked as @IntDef, @LongDef, or @StringDef

Annotations marked as @IntDef , @LongDef , or @StringDef denote a set of valid constants that can be passed to an API. However, when they are exported as APIs themselves, the compiler inlines the constants and only the (now useless) values remain in the annotation's API stub (for the platform) or JAR (for libraries).

As such, usages of these annotations must be marked with the @hide docs annotation in the platform or @RestrictTo.Scope.LIBRARY) code annotation in libraries. They must be marked @Retention(RetentionPolicy.SOURCE) in both cases to prevent them from appearing in API stubs or JARs.

@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
  STREAM_TYPE_FULL_IMAGE_DATA,
  STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}

When building the platform SDK and library AARs, a tool extracts the annotations and bundles them separately from the compiled sources. Android Studio reads this bundled format and enforces the type definitions.

Don't add new setting provider keys

Don't expose new keys from Settings.Global , Settings.System , or Settings.Secure .

Instead, add a proper getter and setter Java API in a relevant class, which is typically a "manager" class. Add a listener mechanism or a broadcast to notify clients of changes as needed.

SettingsProvider settings have a number of problems compared to getters/setters:

  • No type safety.
  • No unified way to provide a default value.
  • No proper way to customize permissions.
    • For example, it's not possible to protect your setting with a custom permission.
  • No proper way to add custom logic properly.
    • For example, it's not possible to change setting A's value depending on setting B's value.

Example: Settings.Secure.LOCATION_MODE has existed for a long time, but the location team has deprecated it for a proper Java API LocationManager.isLocationEnabled() and the MODE_CHANGED_ACTION broadcast, which gave the team a lot more flexibility, and the semantics of the APIs are a lot clearer now.

Don't extend Activity and AsyncTask

AsyncTask is an implementation detail. Instead, expose a listener or, in androidx, a ListenableFuture API instead.

Activity subclasses are impossible to compose. Extending activity for your feature makes it incompatible with other features that require users to do the same. Instead, rely on composition by using tools such as LifecycleObserver .

Use the Context's getUser()

Classes bound to a Context , such as anything returned from Context.getSystemService() should use the user bound to the Context instead of exposing members that target specific users.

class FooManager {
  Context mContext;

  void fooBar() {
    mIFooBar.fooBarForUser(mContext.getUser());
  }
}
class FooManager {
  Context mContext;

  Foobar getFoobar() {
    // Bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBarForUser(Process.myUserHandle());
  }

  Foobar getFoobar() {
    // Also bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBar();
  }

  Foobar getFoobarForUser(UserHandle user) {
    mIFooBar.fooBarForUser(user);
  }
}

Exception: A method may accept a user argument if it accepts values that don't represent a single user, such as UserHandle.ALL .

Use UserHandle instead of plain ints

UserHandle is preferred to provide type safety and avoid conflating user IDs with uids.

Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);

Where unavoidable, an int representing a user ID must be annotated with @UserIdInt .

Foobar getFoobarForUser(@UserIdInt int user);

Prefer listeners or callbacks to broadcast intents

Broadcast intents are very powerful, but they've resulted in emergent behaviors that can negatively impact system health, and so new broadcast intents should be added judiciously.

Here are some specific concerns which result in us discouraging the introduction of new broadcast intents:

  • When sending broadcasts without the FLAG_RECEIVER_REGISTERED_ONLY flag, they force-start any apps that aren't already running. While this can sometimes be an intended outcome, it can result in stampeding of dozens of apps, negatively impacting system health. We'd recommend using alternative strategies, such as JobScheduler , to better coordinate when various preconditions are met.

  • When sending broadcasts, there is little ability to filter or adjust the content delivered to apps. This makes it difficult or impossible to respond to future privacy concerns, or introduce behavior changes based on the target SDK of the receiving app.

  • Since broadcast queues are a shared resource, they can become overloaded and may not result in timely delivery of your event. We've observed several broadcast queues in the wild which have an end-to-end latency of 10 minutes or longer.

For these reasons, we encourage new features to consider using listeners or callbacks or other facilities such as JobScheduler instead of broadcast intents.

In cases where broadcast intents still remain the ideal design, here are some best-practices that should be considered:

  • If possible, use Intent.FLAG_RECEIVER_REGISTERED_ONLY to limit your broadcast to apps that are already running. For example, ACTION_SCREEN_ON uses this design to avoid waking up apps.
  • If possible, use Intent.setPackage() or Intent.setComponent() to target the broadcast at a specific app of interest. For example, ACTION_MEDIA_BUTTON uses this design to focus on the current app handling playback controls.
  • If possible, define your broadcast as a <protected-broadcast> to prevent malicious apps from impersonating the OS.

Intents in system-bound developer services

Services that are intended to be extended by the developer and bound by the system, for example abstract services like NotificationListenerService , may respond to an Intent action from the system. Such services should meet the following criteria:

  1. Define a SERVICE_INTERFACE string constant on the class containing the fully-qualified class name of the service. This constant must be annotated with @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) .
  2. Document on the class that a developer must add an <intent-filter> to their AndroidManifest.xml in order to receive Intents from the platform.
  3. Strongly consider adding a system-level permission to prevent rogue apps from sending Intent s to developer services.

Kotlin-Java interop

See the official Android Kotlin-Java interop guide for a full list of guidelines. Select guidelines have been copied to this guide to improve discoverability.

API visibility

Some Kotlin APIs, like suspend fun s, aren't intended to be used by Java developers; however, don't attempt to control language-specific visibility using @JvmSynthetic as it has side-effects on how the API is presented in debuggers that make debugging more difficult.

See the Kotlin-Java interop guide or Async guide for specific guidance.

Companion objects

Kotlin uses companion object to expose static members. In some cases, these will show up from Java on an inner class named Companion rather than on the containing class. Companion classes may show as empty classes in API text files -- that is working as intended.

To maximize compatibility with Java, annotate companion objects' non-constant fields with @JvmField and public functions with @JvmStatic to expose them directly on the containing class.

companion object {
  @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
  @JvmStatic fun fromPointF(pointf: PointF) {
    /* ... */
  }
}

Evolution of Android platform APIs

This section describes policies regarding what types of changes you can make to existing Android APIs and how you should implement those changes to maximize compatibility with existing apps and codebases.

Binary-breaking changes

Avoid binary-breaking changes in finalized public API surfaces. These types of changes generally raise errors when running make update-api , but there might be edge cases that Metalava's API check doesn't catch. When in doubt, refer to the Eclipse Foundation's Evolving Java-based APIs guide for a detailed explanation of what types of API changes are compatible in Java. Binary-breaking changes in hidden (for example, system) APIs should follow the deprecate/replace cycle.

Source-breaking changes

We discourage source-breaking changes even if they aren't binary breaking. One example of a binary compatible but source-breaking change is adding a generic to an existing class, which is binary compatible but can introduce compilation errors due to inheritance or ambiguous references. Source-breaking changes won't raise errors when running make update-api , so you must take care to understand the impact of changes to existing API signatures.

In some cases, source-breaking changes become necessary to improve the developer experience or code correctness. For example, adding nullability annotations to Java sources improves interoperability with Kotlin code and reduces the likelihood of errors, but often requires changes -- sometimes significant changes -- to source code.

Изменения в частных API

You can change APIs annotated with @TestApi at any time.

You must preserve APIs annotated with @SystemApi for three years. You must remove or refactor a system API on the following schedule:

  • API y - Added
  • API y+1 - Deprecation
    • Mark the code with @Deprecated .
    • Add replacements, and link to the replacement in the Javadoc for the deprecated code using the @deprecated docs annotation.
    • During the development cycle, file bugs against internal users telling them the API is being deprecated. This helps validate that the replacement APIs are adequate.
  • API y+2 - Soft removal
    • Mark the code with @removed .
    • Optionally, throw or no-op for apps that target the current SDK level for the release.
  • API y+3 - Hard removal
    • Completely remove the code from the source tree.

Устаревание

We consider deprecation an API change, and it can occur in a major (such as letter) release. Use the @Deprecated source annotation and @deprecated <summary> docs annotation together when deprecating APIs. Your summary must include a migration strategy. This strategy might link to a replacement API or explain why you shouldn't use the API:

/**
 * Simple version of ...
 *
 * @deprecated Use the {@link androidx.fragment.app.DialogFragment}
 *             class with {@link androidx.fragment.app.FragmentManager}
 *             instead.
 */
@Deprecated
public final void showDialog(int id)

You must also deprecate APIs defined in XML and exposed in Java, including attributes and styleable properties exposed in the android.R class, with a summary:

<!-- Attribute whether the accessibility service ...
     {@deprecated Not used by the framework}
 -->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />

When to deprecate an API

Deprecations are most useful for discouraging adoption of an API in new code .

We also require that you mark APIs as @deprecated before they're @removed , but this doesn't provide strong motivation for developers to migrate away from an API they're already using.

Before deprecating an API, consider the impact on developers. The effects of deprecating an API include:

  • javac emits a warning during compilation.
    • Deprecation warnings can't be suppressed globally or baselined, so developers using -Werror need to individually fix or suppress every usage of a deprecated API before they can update their compile SDK version.
    • Deprecation warnings on imports of deprecated classes can't be suppressed. As a result, developers need to inline the fully qualified class name for every usage of a deprecated class before they can update their compile SDK version.
  • Documentation on d.android.com shows a deprecation notice.
  • IDEs like Android Studio show a warning at the API usage site.
  • IDEs might down-rank or hide the API from auto-complete.

As a result, deprecating an API can discourage the developers who are the most concerned about code health (those using -Werror ) from adopting new SDKs. Developers who aren't concerned about warnings in their existing code are likely to ignore deprecations altogether.

An SDK that introduces a large number of deprecations makes both of these cases worse.

For this reason, we recommend deprecating APIs only in cases where:

  • We plan to @remove the API in a future release.
  • API use leads to incorrect or undefined behavior that we can't fix without breaking compatibility.

When you deprecate an API and replace it with a new API, we strongly recommend adding a corresponding compatibility API to a Jetpack library like androidx.core to simplify supporting both old and new devices.

We don't recommend deprecating APIs that work as intended in current and future releases:

/**
 * ...
 * @deprecated Use {@link #doThing(int, Bundle)} instead.
 */
@Deprecated
public void doThing(int action) {
  ...
}

public void doThing(int action, @Nullable Bundle extras) {
  ...
}

Deprecation is appropriate in cases where APIs can no longer maintain their documented behaviors:

/**
 * ...
 * @deprecated No longer displayed in the status bar as of API 21.
 */
@Deprecated
public RemoteViews tickerView;

Changes to deprecated APIs

You must maintain the behavior of deprecated APIs. This means test implementations must remain the same, and tests must continue to pass after you have deprecated the API. If the API doesn't have tests, you should add tests.

Don't expand deprecated API surfaces in future releases. You can add lint correctness annotations (for example, @Nullable ) to an existing deprecated API, but shouldn't add new APIs.

Don't add new APIs as deprecated. If any APIs were added and subsequently deprecated within a prerelease cycle (thus would initially enter the public API surface as deprecated), you must remove them before finalizing the API.

Soft removal

Soft removal is a source-breaking change, and you should avoid it in public APIs unless the API Council explicitly approves it. For system APIs, you must deprecate the API for the duration of a major release before a soft removal. Remove all docs references to the APIs and use the @removed <summary> docs annotation when soft-removing APIs. Your summary must include the reason for removal and can include a migration strategy, as we explained in Deprecation .

The behavior of soft-removed APIs can be maintained as is, but more importantly must be preserved such that existing callers won't crash when calling the API. In some cases, that might mean preserving behavior.

Test coverage must be maintained, but the content of the tests might need to change to accommodate for behavioral changes. Tests must still validate that existing callers don't crash at run time. You can maintain the behavior of soft-removed APIs as is, but more importantly, you must preserve it such that existing callers won't crash when calling the API. In some cases, that might mean preserving behavior.

You must maintain test coverage, but the content of the tests might need to change to accommodate behavioral changes. Tests must still validate that existing callers don't crash at run time.

At a technical level, we remove the API from the SDK stub JAR and compile-time classpath using the @remove Javadoc annotation, but it still exists on the run-time classpath -- similar to @hide APIs:

/**
 * Ringer volume. This is ...
 *
 * @removed Not functional since API 2.
 */
public static final String VOLUME_RING = ...

From an app developer perspective, the API no longer appears in auto-complete and source code that references the API won't compile when the compileSdk is equal to or later than the SDK at which the API was removed; however, source code continues to compile successfully against earlier SDKs and binaries that reference the API continue to work.

Certain categories of API must not be soft removed. You must not soft remove certain categories of API.

Abstract methods

You must not soft remove abstract methods on classes that developers might extend. Doing so makes it impossible for developers to successfully extend the class across all SDK levels.

In rare cases where it was never and won't be possible for developers to extend a class, you can still soft remove abstract methods.

Жесткое удаление

Hard removal is a binary-breaking change and should never occur in public APIs.

Discouraged annotation

We use the @Discouraged annotation to indicate that we don't recommend an API in most (>95%) cases. Discouraged APIs differ from deprecated APIs in that there exists a narrow critical use case that prevents deprecation. When you mark an API as discouraged, you must provide an explanation and an alternative solution:

@Discouraged(message = "Use of this function is discouraged because resource
                        reflection makes it harder to perform build
                        optimizations and compile-time verification of code. It
                        is much more efficient to retrieve resources by
                        identifier (such as `R.foo.bar`) than by name (such as
                        `getIdentifier()`)")
public int getIdentifier(String name, String defType, String defPackage) {
    return mResourcesImpl.getIdentifier(name, defType, defPackage);
}

You shouldn't add new APIs as discouraged.

Changes to the behavior of existing APIs

In some cases, you might want to change the implementation behavior of an existing API. For example, in Android 7.0 we improved DropBoxManager to clearly communicate when developers tried posting events that were too large to send across Binder .

However, to avoid causing problems for existing apps, we strongly recommend preserving a safe behavior for older apps. We've historically guarded these behavior changes based on the ApplicationInfo.targetSdkVersion of the app, but we've recently migrated to require using the App Compatibility Framework. Here's an example of how to implement a behavior change using this new framework:

import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;

public class MyClass {
  @ChangeId
  // This means the change will be enabled for target SDK R and higher.
  @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
  // Use a bug number as the value, provide extra detail in the bug.
  // FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
  static final long FOO_NOW_DOES_X = 123456789L;

  public void doFoo() {
    if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
      // do the new thing
    } else {
      // do the old thing
    }
  }
}

Using this App Compatibility Framework design enables developers to temporarily disable specific behavior changes during preview and beta releases as part of debugging their apps, instead of forcing them to adjust to dozens of behavior changes simultaneously.

Forward compatibility

Forward compatibility is a design characteristic that allows a system to accept input intended for a later version of itself. In the case of API design, you must pay special attention to the initial design as well as future changes because developers expect to write code once, test it once, and have it run everywhere without issue.

The following cause the most common forward-compatibility issues in Android:

  • Adding new constants to a set (such as @IntDef or enum ) previously assumed to be complete (for example, where switch has a default that throws an exception).
  • Adding support for a feature that isn't captured directly in the API surface (for example, support for assigning ColorStateList -type resources in XML where previously only <color> resources were supported).
  • Loosening restrictions on run-time checks, for example removing a requireNotNull() check that was present on lower versions.

In all of these cases, developers find out that something is wrong only at run time. Worse, they might find out as a result of crash reports from older devices in the field.

Additionally, these cases are all technically valid API changes. They don't break binary or source compatibility and API lint won't catch any of these issues.

As a result, API designers must pay careful attention when modifying existing classes. Ask the question, "Is this change going to cause code that's written and tested only against the latest version of the platform to fail on lower versions?"

XML schemas

If an XML schema serves as a stable interface between components, that schema must be explicitly specified and must evolve in a backward-compatible manner, similar to other Android APIs. For example, the structure of XML elements and attributes must be preserved similar to how methods and variables are maintained on other Android API surfaces.

XML deprecation

If you'd like to deprecate an XML element or attribute, you can add the xs:annotation marker, but you must continue to support any existing XML files by following the typical @SystemApi evolution lifecycle.

<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

Element types must be preserved

Schemas support the sequence element, choice element and all elements as child elements of complexType element. However, these child elements differ in the number and order of their child elements, so modifying an existing type would be an incompatible change.

If you want to modify an existing type, the best-practice is to deprecate the old type and introduce a new type to replace it.

<!-- Original "sequence" value -->
<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<!-- New "choice" value -->
<xs:element name="fooChoice">
    <xs:complexType>
        <xs:choice>
            <xs:element name="name" type="xs:string"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

Mainline-specific patterns

Mainline is a project to allow updating subsystems ("mainline modules") of the Android OS individually, rather than updating the whole system image.

Mainline modules have to be "unbundled" from the core platform, which means all the interactions between each module and the rest of the world have to be done using formal (public or system) APIs.

There are certain design patterns mainline modules should follow. This section describes them.

The <Module>FrameworkInitializer pattern

If a mainline module needs to exposes @SystemService classes (for example, JobScheduler ) then use the following pattern:

  • Expose a <YourModule>FrameworkInitializer class from your module. This class needs to be in $BOOTCLASSPATH . Example: StatsFrameworkInitializer

  • Mark it with @SystemApi(client = MODULE_LIBRARIES) .

  • Add a public static void registerServiceWrappers() method to it.

  • Use SystemServiceRegistry.registerContextAwareService() to register a service manager class when it needs a reference to a Context .

  • Use SystemServiceRegistry.registerStaticService() to register a service manager class when it doesn't need a reference to a Context .

  • Call the registerServiceWrappers() method from SystemServiceRegistry 's static initializer.

The <Module>ServiceManager pattern

Normally, in order to register system service binder objects or get references to them, one would use ServiceManager , but mainline modules can't use it because it's hidden. This class is hidden because mainline modules aren't supposed to register or refer to system service binder objects exposed by the static platform or by other modules.

Mainline modules can use the following pattern instead to be able to register and get references to binder services that are implemented inside the module.

  • Create a <YourModule>ServiceManager class, following the design of TelephonyServiceManager

  • Expose the class as @SystemApi . If you only need to access it from $BOOTCLASSPATH classes or system server classes, you can use @SystemApi(client = MODULE_LIBRARIES) ; otherwise @SystemApi(client = PRIVILEGED_APPS) would work.

  • This class would consists of:

    • A hidden constructor, so only the static platform code can instantiate it.
    • Public getter methods that return a ServiceRegisterer instance for a specific name. If you have one binder object, then you need one getter method. If you have two, then you need two getters.
    • In ActivityThread.initializeMainlineModules() , instantiate this class, and pass it to a static method exposed by your module. Normally, you add a static @SystemApi(client = MODULE_LIBRARIES) API in your FrameworkInitializer class that takes it.

This pattern would prevent other mainline modules from accessing these APIs because there's no way for other modules to get an instance of <YourModule>ServiceManager , even though the get() and register() APIs are visible to them.

Here is how telephony gets a reference to the telephony service: code search link .

If your implements a service binder object in native code, you use the AServiceManager native APIs . These APIs correspond to the ServiceManager Java APIs but the native ones are directly exposed to mainline modules. Don't use them to register or refer to binder objects that aren't owned by your module. If you expose a binder object from native, your <YourModule>ServiceManager.ServiceRegisterer doesn't need a register() method.

Permission definitions in Mainline modules

Mainline modules containing APKs may define (custom) permissions in their APK AndroidManifest.xml in the same way as a regular APK.

If the defined permission is only used internally within a module, its permission name should be prefixed with the APK package name, for example:

<permission
    android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
    android:protectionLevel="signature" />

If the defined permission is to be provided as part of an updatable platform API to other apps, its permission name should be prefixed with "android.permission." (like any static platform permission) plus the module package name, to signal it's a platform API from a module while avoiding any naming conflicts, for example:

<permission
    android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"
    android:label="@string/active_calories_burned_read_content_description"
    android:protectionLevel="dangerous"
    android:permissionGroup="android.permission-group.HEALTH" />

Then the module can expose this permission name as an API constant in its API surface, for example HealthPermissions.READ_ACTIVE_CALORIES_BURNED .