Diese Seite soll Entwicklern als Leitfaden dienen, um die allgemeinen Prinzipien zu verstehen, die der API-Konsens bei API-Überprüfungen durchsetzt.
Neben diesen Richtlinien sollten Entwickler beim Erstellen von APIs das Tool API Lint ausführen. Dieses Tool codiert viele dieser Regeln in Prüfungen, die an APIs ausgeführt werden.
Sie können diesen Artikel als Leitfaden für die Regeln betrachten, die von diesem Lint-Tool befolgt werden, sowie als allgemeine Hinweise zu Regeln, die in diesem Tool nicht mit hoher Genauigkeit codiert werden können.
API Lint-Tool
API Lint ist in das statische Analysetool Metalava eingebunden und wird während der Validierung in CI automatisch ausgeführt. Sie können ihn manuell über eine lokale Plattform-Checkout-Datei mit m
checkapi
oder eine lokale AndroidX-Checkout-Datei mit ./gradlew :path:to:project:checkApi
ausführen.
API-Regeln
Die Android-Plattform und viele Jetpack-Bibliotheken gab es schon vor der Erstellung dieser Richtlinien. Die Richtlinien, die weiter unten auf dieser Seite aufgeführt sind, werden ständig weiterentwickelt, um den Anforderungen des Android-Ökosystems gerecht zu werden.
Daher entsprechen einige vorhandene APIs möglicherweise nicht den Richtlinien. In anderen Fällen kann es für App-Entwickler eine bessere Nutzererfahrung bieten, wenn eine neue API mit vorhandenen APIs übereinstimmt, anstatt sich strikt an die Richtlinien zu halten.
Wenn Sie schwierige Fragen zu einer API haben, die geklärt werden müssen, oder Richtlinien aktualisiert werden müssen, wenden Sie sich an den API-Rat.
API-Grundlagen
Diese Kategorie bezieht sich auf die Hauptaspekte einer Android-API.
Alle APIs müssen implementiert sein
Unabhängig von der Zielgruppe einer API (z. B. öffentlich oder @SystemApi
) müssen alle API-Oberflächen implementiert werden, wenn sie zusammengeführt oder als API freigegeben werden. Fügen Sie API-Stubs nicht zu einer Implementierung zusammen, die erst später erfolgt.
API-Oberflächen ohne Implementierungen haben mehrere Probleme:
- Es kann nicht garantiert werden, dass eine korrekte oder vollständige Oberfläche freigelegt wurde. Solange eine API nicht von Kunden getestet oder verwendet wird, gibt es keine Möglichkeit, zu überprüfen, ob ein Kunde die entsprechenden APIs hat, um die Funktion nutzen zu können.
- APIs ohne Implementierung können in Entwicklervorschauen nicht getestet werden.
- APIs ohne Implementierung können nicht in CTS getestet werden.
Alle APIs müssen getestet werden
Dies entspricht den CTS-Anforderungen der Plattform, den AndroidX-Richtlinien und der allgemeinen Vorstellung, dass APIs implementiert werden müssen.
Durch das Testen von API-Oberflächen können wir sicherstellen, dass die API-Oberfläche nutzbar ist und wir die erwarteten Anwendungsfälle berücksichtigt haben. Es reicht nicht aus, nur die Existenz zu prüfen. Das Verhalten der API selbst muss getestet werden.
Eine Änderung, durch die eine neue API hinzugefügt wird, sollte entsprechende Tests im selben CL- oder Gerrit-Thema enthalten.
APIs sollten außerdem testbar sein. Sie sollten die Frage beantworten können: „Wie testen App-Entwickler Code, der Ihre API verwendet?“
Alle APIs müssen dokumentiert sein
Die Dokumentation ist ein wichtiger Bestandteil der API-Usability. Die Syntax einer API-Oberfläche mag zwar offensichtlich erscheinen, aber neue Clients verstehen die Semantik, das Verhalten oder den Kontext der API nicht.
Alle generierten APIs müssen den Richtlinien entsprechen.
Von Tools generierte APIs müssen denselben API-Richtlinien wie handgeschriebener Code entsprechen.
Von der Verwendung der folgenden Tools für die Generierung von APIs wird abgeraten:
AutoValue
: Verstößt auf verschiedene Weise gegen die Richtlinien. Beispielsweise gibt es keine Möglichkeit, Klassen mit endgültigen Werten oder endgültige Builder mit der Funktionsweise von AutoValue zu implementieren.
Codestil
Diese Kategorie bezieht sich auf den allgemeinen Codestil, den Entwickler verwenden sollten, insbesondere beim Schreiben öffentlicher APIs.
Beachten Sie die standardmäßigen Codierungskonventionen, sofern nicht anders angegeben.
Die Android-Programmierkonventionen für externe Mitwirkende sind hier dokumentiert:
https://source.android.com/source/code-style.html
Im Allgemeinen folgen wir den Standard-Java- und Kotlin-Programmierkonventionen.
Akronyme dürfen in Methodennamen nicht großgeschrieben werden.
Beispiel: Der Methodenname sollte runCtsTests
und nicht runCTSTests
lauten.
Namen dürfen nicht auf „Impl“ enden.
Dadurch werden Implementierungsdetails offengelegt. Das sollten Sie vermeiden.
Klassen
In diesem Abschnitt werden Regeln zu Klassen, Schnittstellen und Vererbung beschrieben.
Neue öffentliche Klassen von der entsprechenden Basisklasse erben
Durch die Vererbung werden API-Elemente in Ihrer Unterklasse freigegeben, die möglicherweise nicht geeignet sind.
Eine neue öffentliche Unterklasse von FrameLayout
sieht beispielsweise aus wie FrameLayout
, aber mit den neuen Verhaltensweisen und API-Elementen. Wenn diese übergeordnete API für Ihren Anwendungsfall nicht geeignet ist, können Sie von einer Klasse weiter oben im Stammbaum ableiten, z. B. ViewGroup
oder View
.
Wenn Sie versucht sind, Methoden aus der Basisklasse zu überschreiben, um UnsupportedOperationException
zu werfen, sollten Sie die verwendete Basisklasse noch einmal überdenken.
Basissammlungsklassen verwenden
Verwenden Sie immer die Basisklasse anstelle der spezifischen Implementierung, wenn Sie eine Sammlung als Argument übergeben oder als Wert zurückgeben (z. B. List<Foo>
statt ArrayList<Foo>
zurückgeben).
Verwenden Sie eine Basisklasse, die die entsprechenden Einschränkungen für die API enthält. Verwenden Sie beispielsweise List
für eine API, deren Sammlung sortiert werden muss, und Set
für eine API, deren Sammlung aus eindeutigen Elementen bestehen muss.
Verwenden Sie in Kotlin unveränderliche Sammlungen. Weitere Informationen finden Sie unter Veränderbarkeit von Sammlungen.
Abstrakte Klassen und Schnittstellen im Vergleich
Java 8 unterstützt Standardmethoden für Schnittstellen. So können API-Entwickler Schnittstellen Methoden hinzufügen und gleichzeitig die Binärkompatibilität beibehalten. Plattformcode und alle Jetpack-Bibliotheken sollten auf Java 8 oder höher ausgerichtet sein.
Wenn die Standardimplementierung zustandslos ist, sollten API-Designer Schnittstellen abstrakten Klassen vorziehen. Das bedeutet, dass Standardschnittstellenmethoden als Aufrufe anderer Schnittstellenmethoden implementiert werden können.
Wenn für die Standardimplementierung ein Konstruktor oder ein interner Zustand erforderlich ist, müssen abstrakte Klassen verwendet werden.
In beiden Fällen können API-Designer eine einzelne Methode abstrakt lassen, um die Verwendung als Lambda zu vereinfachen:
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) { }
}
Klassennamen sollten widerspiegeln, was sie erweitern
Klassen, die Service
erweitern, sollten beispielsweise FooService
heißen, um für Klarheit zu sorgen:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
Generische Suffixe
Verwenden Sie keine generischen Klassennamenendungen wie Helper
und Util
für Sammlungen von Dienstmethoden. Platzieren Sie die Methoden stattdessen direkt in den zugehörigen Klassen oder in Kotlin-Erweiterungsfunktionen.
Wenn Methoden mehrere Klassen verbinden, geben Sie der enthaltenden Klasse einen aussagekräftigen Namen, der ihre Funktion beschreibt.
In sehr begrenzten Fällen kann das Suffix Helper
geeignet sein:
- Wird für die Zusammensetzung des Standardverhaltens verwendet
- Kann die Delegierung bestehender Verhaltensweisen an neue Klassen beinhalten
- Möglicherweise ist ein dauerhafter Status erforderlich.
- Umfasst in der Regel
View
Wenn für das Backporten von Kurzinfos beispielsweise der mit einer View
verknüpfte Status beibehalten und mehrere Methoden auf der View
aufgerufen werden müssen, um den Backport zu installieren, wäre TooltipHelper
ein zulässiger Klassenname.
Stellen Sie IDL-generierten Code nicht direkt als öffentliche APIs bereit.
Behalten Sie den von IDL generierten Code als Implementierungsdetails. Dazu gehören Protobuf, Sockets, FlatBuffers oder andere nicht-Java- und nicht-NDK-API-Oberflächen. Die meisten IDL-Dateien in Android sind jedoch in AIDL geschrieben. Daher liegt der Schwerpunkt dieser Seite auf AIDL.
Die generierten AIDL-Klassen erfüllen nicht die Anforderungen des API-Styleguides (z. B. kann keine Überladung verwendet werden) und das AIDL-Tool ist nicht explizit für die Kompatibilität mit der Sprach-API konzipiert. Sie können sie also nicht in eine öffentliche API einbetten.
Fügen Sie stattdessen eine öffentliche API-Ebene über der AIDL-Schnittstelle hinzu, auch wenn es sich anfangs um einen einfachen Wrapper handelt.
Binder-Schnittstellen
Wenn die Binder
-Schnittstelle ein Implementierungsdetail ist, kann sie in Zukunft frei geändert werden. Die öffentliche Schicht ermöglicht die erforderliche Abwärtskompatibilität. Möglicherweise müssen Sie den internen Aufrufen beispielsweise neue Argumente hinzufügen oder den IPC-Traffic durch Batching oder Streaming, durch gemeinsamen Speicher oder ähnliches optimieren. Keines dieser Dinge ist möglich, wenn Ihre AIDL-Schnittstelle auch die öffentliche API ist.
Stellen Sie FooService
beispielsweise nicht direkt als öffentliche API bereit:
// BAD: Public API generated from IFooService.aidl
public class IFooService {
public void doFoo(String foo);
}
Wickeln Sie die Binder
-Schnittstelle stattdessen in einen Manager oder eine andere Klasse ein:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo);
}
public IFooManager {
public void doFoo(String foo) {
mFooService.doFoo(foo);
}
}
Wenn später ein neues Argument für diesen Aufruf erforderlich ist, kann die interne Schnittstelle minimiert und der öffentlichen API praktische Überladungen hinzugefügt werden. Sie können die Ummantelungsebene auch verwenden, um andere Probleme mit der Abwärtskompatibilität zu beheben, die sich im Laufe der Implementierung ergeben:
/**
* @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);
}
}
Bei Binder
-Schnittstellen, die nicht Teil der Android-Plattform sind (z. B. eine Dienstschnittstelle, die von Google Play-Diensten für Apps exportiert wird), ist es aufgrund der Anforderung an eine stabile, veröffentlichte und versionierte IPC-Schnittstelle viel schwieriger, die Schnittstelle selbst weiterzuentwickeln. Es ist jedoch sinnvoll, eine Wrapper-Ebene um sie herum zu haben, um anderen API-Richtlinien zu entsprechen und die Verwendung derselben öffentlichen API für eine neue Version der IPC-Schnittstelle zu erleichtern, falls dies erforderlich wird.
Keine Roh-Binder-Objekte in der öffentlichen API verwenden
Ein Binder
-Objekt hat für sich genommen keine Bedeutung und sollte daher nicht in der öffentlichen API verwendet werden. Ein häufiger Anwendungsfall ist die Verwendung von Binder
oder IBinder
als Token, da sie Identitätssemantik haben. Verwende stattdessen eine Wrapper-Token-Klasse, anstatt ein rohes Binder
-Objekt zu verwenden.
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() {...}
}
Managerklassen müssen final sein
Managerklassen sollten als final
deklariert werden. Managerklassen kommunizieren mit Systemdiensten und sind der zentrale Interaktionspunkt. Eine Anpassung ist nicht erforderlich. Geben Sie daher final
an.
Verwenden Sie keine CompletableFuture- oder Future-Objekte.
java.util.concurrent.CompletableFuture
hat eine große API-Oberfläche, die eine beliebige Änderung des Werts in der Zukunft ermöglicht, und fehleranfällige Standardeinstellungen.
Umgekehrt fehlt java.util.concurrent.Future
die nicht blockierende Überwachung, was die Verwendung mit asynchronem Code erschwert.
Verwenden Sie in Plattformcode und Low-Level-Bibliotheks-APIs, die sowohl von Kotlin als auch von Java verwendet werden, eine Kombination aus einem Abschluss-Callback, Executor
, und, sofern die API die Stornierung unterstützt, CancellationSignal
.
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
Executor callbackExecutor,
android.os.OutcomeReceiver<FooResult, Throwable> callback);
Wenn Sie Ihre App auf Kotlin ausrichten, sollten Sie suspend
-Funktionen verwenden.
suspend fun asyncLoadFoo(): Foo
In Java-spezifischen Integrationsbibliotheken können Sie die ListenableFuture
von Guava verwenden.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
Verwenden Sie nicht „Optional“.
Optional
kann zwar auf einigen API-Oberflächen Vorteile haben, ist aber nicht mit der vorhandenen Android API-Oberfläche kompatibel. @Nullable
und @NonNull
bieten Toolunterstützung für die null
-Sicherheit und Kotlin erzwingt Nullbarkeitsverträge auf Compilerebene, wodurch Optional
unnötig wird.
Verwenden Sie für optionale Primitive die gekoppelten Methoden has
und get
. Wenn der Wert nicht festgelegt ist (has
gibt false
zurück), sollte die get
-Methode eine IllegalStateException
auslösen.
public boolean hasAzimuth() { ... }
public int getAzimuth() {
if (!hasAzimuth()) {
throw new IllegalStateException("azimuth is not set");
}
return azimuth;
}
Private Konstruktoren für nicht instantiierbare Klassen verwenden
Klassen, die nur von Builder
s erstellt werden können, Klassen, die nur Konstanten oder statische Methoden enthalten, oder Klassen, die anderweitig nicht instantiiert werden können, sollten mindestens einen privaten Konstruktor enthalten, um die Instanziierung mit dem standardmäßigen Konstruktor ohne Argumente zu verhindern.
public final class Log {
// Not instantiable.
private Log() {}
}
Singletons
Singletons werden nicht empfohlen, da sie die folgenden testbezogenen Nachteile haben:
- Die Erstellung wird von der Klasse verwaltet, um die Verwendung von gefälschten Daten zu verhindern.
- Tests können aufgrund der statischen Natur eines Singletons nicht hermetisch sein
- Um diese Probleme zu umgehen, müssen Entwickler entweder die internen Details des Singletons kennen oder einen Wrapper darum erstellen.
Verwenden Sie das Muster der einzelnen Instanz, das auf einer abstrakten Basisklasse basiert, um diese Probleme zu beheben.
Einzelne Instanz
Klassen mit einer einzelnen Instanz verwenden eine abstrakte Basisklasse mit einem private
- oder internal
-Konstruktor und stellen eine statische getInstance()
-Methode bereit, um eine Instanz abzurufen. Die getInstance()
-Methode muss bei nachfolgenden Aufrufen dasselbe Objekt zurückgeben.
Das von getInstance()
zurückgegebene Objekt sollte eine private Implementierung der abstrakten Basisklasse sein.
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
}
}
}
Eine einzelne Instanz unterscheidet sich von einem Singleton dadurch, dass Entwickler eine gefälschte Version von SingleInstance
erstellen und ihr eigenes Dependency Injection-Framework verwenden können, um die Implementierung zu verwalten, ohne einen Wrapper erstellen zu müssen. Alternativ kann die Bibliothek auch einen eigenen Fake in einem -testing
-Artefakt bereitstellen.
Klassen, die Ressourcen freigeben, sollten AutoCloseable implementieren.
Klassen, die Ressourcen über close
, release
, destroy
oder ähnliche Methoden freigeben, sollten java.lang.AutoCloseable
implementieren, damit Entwickler diese Ressourcen automatisch bereinigen können, wenn sie einen try-with-resources
-Block verwenden.
Vermeiden Sie die Einführung neuer View-Unterklassen in Android.*
Füge der öffentlichen API der Plattform (android.*
) keine neuen Klassen hinzu, die direkt oder indirekt von android.view.View
abgeleitet sind.
Das Android-UI-Toolkit ist jetzt Compose-first. Neue von der Plattform bereitgestellte UI-Funktionen sollten als APIs niedrigerer Ebene bereitgestellt werden, mit denen Entwickler Jetpack Compose und optional datenbankbasierte UI-Komponenten in Jetpack-Bibliotheken implementieren können. Wenn Sie diese Komponenten in Bibliotheken anbieten, können Sie Backport-Implementierungen verwenden, wenn Plattformfunktionen nicht verfügbar sind.
Felder
Diese Regeln beziehen sich auf öffentliche Felder in Klassen.
Rohfelder nicht freigeben
Java-Klassen sollten keine Felder direkt freigeben. Felder sollten privat sein und nur über öffentliche Getter und Setter zugänglich sein, unabhängig davon, ob diese Felder final sind oder nicht.
Seltene Ausnahmen sind einfache Datenstrukturen, bei denen das Verhalten beim Angeben oder Abrufen eines Felds nicht verbessert werden muss. In solchen Fällen sollten die Felder gemäß den Standardkonventionen für Variablennamen benannt werden, z. B. Point.x
und Point.y
.
Kotlin-Klassen können Eigenschaften freigeben.
Freigegebene Felder sollten als „Endgültig“ gekennzeichnet sein.
Von der Verwendung von Rohdatenfeldern wird dringend abgeraten (siehe Rohdatenfelder nicht freigeben). In seltenen Fällen, in denen ein Feld als öffentliches Feld freigegeben wird, markieren Sie es mit final
.
Interne Felder sollten nicht freigegeben werden.
Verweisen Sie nicht auf interne Feldnamen in der öffentlichen API.
public int mFlags;
„Öffentlich“ anstelle von „Geschützt“ verwenden
@see Öffentliche statt geschützte Streams verwenden
Konstanten
Dies sind Regeln für öffentliche Konstanten.
Flagkonstanten dürfen sich nicht mit Int- oder Long-Werten überschneiden.
Flags sind Bits, die zu einem Union-Wert kombiniert werden können. Andernfalls sollten Sie die Variable oder Konstante nicht flag
nennen.
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;
Weitere Informationen zum Definieren öffentlicher Flagkonstanten finden Sie unter @IntDef
für Bitmasken-Flags.
Für statische finale Konstanten sollte die Namenskonvention in Großbuchstaben mit Unterstrichen verwendet werden.
Alle Wörter in der Konstante müssen großgeschrieben werden und mehrere Wörter müssen durch _
getrennt werden. Beispiel:
public static final int fooThing = 5
public static final int FOO_THING = 5
Standardpräfixe für Konstanten verwenden
Viele der in Android verwendeten Konstanten beziehen sich auf Standardelemente wie Flags, Schlüssel und Aktionen. Diese Konstanten sollten Standardpräfixe haben, damit sie leichter als solche erkannt werden.
Intent-Extras sollten beispielsweise mit EXTRA_
beginnen. Intent-Aktionen müssen mit ACTION_
beginnen. Konstanten, die mit Context.bindService()
verwendet werden, sollten mit BIND_
beginnen.
Namen und Bereiche von Schlüsselkonstanten
Die Werte von Stringkonstanten sollten mit dem Namen der Konstante übereinstimmen und sollten im Allgemeinen auf das Paket oder die Domain beschränkt sein. Beispiel:
public static final String FOO_THING = "foo"
weder einheitlich benannt noch angemessen eingegrenzt ist. Stattdessen sollten Sie Folgendes beachten:
public static final String FOO_THING = "android.fooservice.FOO_THING"
Präfixe von android
in Stringkonstanten mit Bereichsbeschränkung sind für das Android Open Source Project reserviert.
Intent-Aktionen und Extras sowie Bundle-Einträge sollten den Namen des Pakets haben, in dem sie definiert sind.
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"
}
„Öffentlich“ anstelle von „Geschützt“ verwenden
@see Öffentliche statt geschützte Streams verwenden
Verwenden Sie einheitliche Präfixe.
Verwandte Konstanten sollten alle mit demselben Präfix beginnen. Beispiel für eine Reihe von Konstanten, die mit Flag-Werten verwendet werden sollen:
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 Standardpräfixe für Konstanten verwenden
Konsistente Ressourcennamen verwenden
Öffentliche IDs, Attribute und Werte müssen gemäß der CamelCase-Namenskonvention benannt werden, z. B. @id/accessibilityActionPageUp
oder @attr/textAppearance
, ähnlich wie öffentliche Felder in Java.
In einigen Fällen enthält eine öffentliche Kennung oder ein öffentliches Attribut ein gemeinsames Präfix, das durch einen Unterstrich getrennt ist:
- Plattformkonfigurationswerte wie
@string/config_recentsComponentName
in config.xml - Layoutspezifische Ansichtsattribute wie
@attr/layout_marginStart
in attrs.xml
Öffentliche Themen und Stile müssen der hierarchischen PascalCase-Benennungskonvention folgen, z. B. @style/Theme.Material.Light.DarkActionBar
oder @style/Widget.Material.SearchView.ActionBar
, ähnlich wie verschachtelte Klassen in Java.
Layout- und drawable-Ressourcen sollten nicht als öffentliche APIs freigegeben werden. Wenn sie jedoch freigegeben werden müssen, müssen öffentliche Layouts und Drawables gemäß der Namenskonvention „unterstrichen“ benannt werden, z. B. layout/simple_list_item_1.xml
oder drawable/title_bar_tall.xml
.
Konstanten, die sich ändern können, dynamisch gestalten
Der Compiler kann Konstantenwerte inline einfügen. Daher gilt es als Teil des API-Vertrags, die Werte unverändert zu lassen. Wenn sich der Wert einer MIN_FOO
- oder MAX_FOO
-Konstante in Zukunft ändern könnte, sollten Sie stattdessen dynamische Methoden verwenden.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
Berücksichtigen Sie die Zukunftssicherheit für Callbacks
In zukünftigen API-Versionen definierte Konstanten sind für Apps, die auf ältere APIs ausgerichtet sind, nicht bekannt. Aus diesem Grund sollten an Apps übermittelte Konstanten die Ziel-API-Version der App berücksichtigen und neuere Konstanten einem einheitlichen Wert zuordnen. Stellen Sie sich folgendes Szenario vor:
Hypothetische SDK-Quelle:
// 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;
Hypothetische App mit targetSdkVersion="22"
:
if (result == STATUS_FAILURE) {
// Oh no!
} else {
// Success!
}
In diesem Fall wurde die App unter Berücksichtigung der Einschränkungen von API-Level 22 entwickelt und es wurde davon ausgegangen, dass es nur zwei mögliche Status gibt. Wenn die App jedoch die neu hinzugefügte STATUS_FAILURE_RETRY
empfängt, wird dies als Erfolg interpretiert.
Bei Methoden, die Konstanten zurückgeben, können solche Fälle sicher behandelt werden, indem die Ausgabe auf das API-Level beschränkt wird, auf das die App ausgerichtet ist:
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;
}
Entwickler können nicht vorhersehen, ob sich eine Liste von Konstanten in Zukunft ändern könnte. Wenn Sie eine API mit einer UNKNOWN
- oder UNSPECIFIED
-Konstante definieren, die wie ein Allrounder aussieht, gehen Entwickler davon aus, dass die veröffentlichten Konstanten beim Erstellen ihrer App vollständig sind. Wenn Sie diese Erwartung nicht festlegen möchten, sollten Sie noch einmal darüber nachdenken, ob eine Allzweckkonstante für Ihre API sinnvoll ist.
Außerdem können Bibliotheken keine eigene targetSdkVersion
getrennt von der App angeben. Die Verarbeitung von targetSdkVersion
-Verhaltensänderungen aus Bibliothekscode ist kompliziert und fehleranfällig.
Ganzzahl oder Stringkonstante
Verwenden Sie Ganzzahlkonstanten und @IntDef
, wenn der Namespace für Werte nicht außerhalb Ihres Pakets erweitert werden kann. Verwenden Sie Stringkonstanten, wenn der Namespace gemeinsam genutzt wird oder durch Code außerhalb Ihres Pakets erweitert werden kann.
Datenklassen
Datenklassen stellen eine Reihe unveränderlicher Eigenschaften dar und bieten eine kleine und gut definierte Reihe von Dienstprogrammfunktionen zur Interaktion mit diesen Daten.
Verwenden Sie data class
nicht in öffentlichen Kotlin-APIs, da der Kotlin-Compiler keine Sprach-API- oder Binärkompatibilität für generierten Code garantiert. Implementieren Sie stattdessen die erforderlichen Funktionen manuell.
Instanziierung
In Java sollten Datenklassen einen Konstruktor bereitstellen, wenn es nur wenige Eigenschaften gibt, oder das Builder
-Muster verwenden, wenn es viele Eigenschaften gibt.
In Kotlin sollten Datenklassen unabhängig von der Anzahl der Eigenschaften einen Konstruktor mit Standardargumenten bereitstellen. Bei in Kotlin definierten Datenklassen kann es auch von Vorteil sein, einen Builder bereitzustellen, wenn Sie Java-Clients anvisieren.
Änderungen und Kopieren
Wenn Daten geändert werden müssen, geben Sie entweder eine Builder
-Klasse mit einem Kopierkonstruktor (Java) oder eine copy()
-Mitgliedsfunktion (Kotlin) an, die ein neues Objekt zurückgibt.
Wenn Sie in Kotlin eine copy()
-Funktion angeben, müssen die Argumente mit dem Konstruktor der Klasse übereinstimmen und die Standardwerte müssen mit den aktuellen Werten des Objekts ausgefüllt werden:
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
)
}
Zusätzliche Verhaltensweisen
Datenklassen sollten sowohl equals()
als auch hashCode()
implementieren. Bei der Implementierung dieser Methoden muss jede Property berücksichtigt werden.
Datenklassen können toString()
in einem empfohlenen Format implementieren, das der Implementierung der Datenklasse in Kotlin entspricht, z. B. User(var1=Alex, var2=42)
.
Methoden
Dies sind Regeln zu verschiedenen Details in Methoden, zu Parametern, Methodennamen, Rückgabetypen und Zugriffsspezifizierern.
Uhrzeit
Diese Regeln decken ab, wie Zeitkonzepte wie Datumsangaben und Zeiträume in APIs ausgedrückt werden sollten.
Verwenden Sie nach Möglichkeit java.time.*-Typen.
java.time.Duration
, java.time.Instant
und viele andere java.time.*
-Typen sind auf allen Plattformversionen durch Entfernen von Zucker verfügbar und sollten bevorzugt verwendet werden, wenn Zeit in API-Parametern oder Rückgabewerten angegeben wird.
Stellen Sie vorzugsweise nur Varianten einer API bereit, die java.time.Duration
oder java.time.Instant
akzeptieren oder zurückgeben, und lassen Sie primitive Varianten mit demselben Anwendungsfall aus, es sei denn, die API-Domain ist eine, bei der die Objektzuweisung bei den beabsichtigten Nutzungsmustern eine unzumutbare Leistungsbeeinträchtigung zur Folge hätte.
Methoden, die Zeiträume ausdrücken, sollten den Namen „duration“ haben.
Wenn ein Zeitwert die Dauer der Zeit angibt, nennen Sie den Parameter „duration“ (Dauer), nicht „time“ (Uhrzeit).
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
Ausnahmen:
„timeout“ ist geeignet, wenn sich die Dauer speziell auf einen Zeitüberschreitungswert bezieht.
„time“ mit dem Typ java.time.Instant
ist geeignet, wenn es sich um einen bestimmten Zeitpunkt handelt, nicht um eine Dauer.
Methoden, die Zeiträume oder Zeit als Primitive ausdrücken, sollten mit ihrer Zeiteinheit benannt werden und „long“ verwenden.
Bei Methoden, die Zeiträume als primitiven Typ akzeptieren oder zurückgeben, sollte der Methodenname mit den zugehörigen Zeiteinheiten (z. B. Millis
, Nanos
, Seconds
) enden, um den unverzierten Namen für die Verwendung mit java.time.Duration
zu reservieren. Siehe Uhrzeit.
Methoden sollten auch mit ihrer Einheit und Zeitbasis gekennzeichnet sein:
@CurrentTimeMillisLong
: Der Wert ist ein nicht negativer Zeitstempel, gemessen als Anzahl der Millisekunden seit dem 01.01.1970 00:00:00 UTC.@CurrentTimeSecondsLong
: Der Wert ist ein nicht negativer Zeitstempel, gemessen als Anzahl der Sekunden seit dem 01.01.1970 00:00:00 UTC.@DurationMillisLong
: Der Wert ist eine nicht negative Dauer in Millisekunden.@ElapsedRealtimeLong
: Der Wert ist ein nicht negativer Zeitstempel in der ZeitbasisSystemClock.elapsedRealtime()
.@UptimeMillisLong
: Der Wert ist ein positiver Zeitstempel in der ZeitbasisSystemClock.uptimeMillis()
.
Für primitive Zeitparameter oder Rückgabewerte sollte long
und nicht int
verwendet werden.
ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);
Bei Methoden, die Zeiteinheiten ausdrücken, sollte die Langform der Einheit verwendet werden.
public void setIntervalNs(long intervalNs);
public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);
public void setTimeoutMicros(long timeoutMicros);
Argumente für lange Zeiträume annotieren
Die Plattform enthält mehrere Anmerkungen, um die Typisierung von Zeiteinheiten vom Typ long
zu verbessern:
@CurrentTimeMillisLong
: Der Wert ist ein nicht negativer Zeitstempel, gemessen als Anzahl der Millisekunden seit1970-01-01T00:00:00Z
, also in der ZeitbasisSystem.currentTimeMillis()
.@CurrentTimeSecondsLong
: Wert ist ein nicht negativer Zeitstempel, gemessen als Anzahl der Sekunden seit1970-01-01T00:00:00Z
.@DurationMillisLong
: Der Wert ist eine nicht negative Dauer in Millisekunden.@ElapsedRealtimeLong
: Der Wert ist ein nicht negativer Zeitstempel in der ZeitbasisSystemClock#elapsedRealtime()
.@UptimeMillisLong
: Der Wert ist ein nicht negativer Zeitstempel in der ZeitbasisSystemClock#uptimeMillis()
.
Maßeinheiten
Verwenden Sie für alle Methoden, die eine andere Maßeinheit als Zeit ausdrücken, vorzugsweise SI-Einheiten mit Bindestrichschreibweise.
public long[] getFrequenciesKhz();
public float getStreamVolumeDb();
Optionale Parameter am Ende von Überladungen platzieren
Wenn Sie Überladungen einer Methode mit optionalen Parametern haben, sollten Sie diese Parameter am Ende und in der gleichen Reihenfolge wie die anderen Parameter angeben:
public int doFoo(boolean flag);
public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);
public int doFoo(boolean flag, int id);
Wenn Sie Überladungen für optionale Argumente hinzufügen, sollte sich das Verhalten der einfacheren Methoden genau so verhalten, als wären den komplexeren Methoden Standardargumente angegeben worden.
Folgerung: Überladen Sie Methoden nur, um optionale Argumente hinzuzufügen oder verschiedene Arten von Argumenten zu akzeptieren, wenn die Methode polymorph ist. Wenn die überladene Methode etwas grundlegend anderes tut, geben Sie ihr einen neuen Namen.
Methoden mit Standardparametern müssen mit @JvmOverloads annotiert sein (nur Kotlin)
Methoden und Konstruktoren mit Standardparametern müssen mit @JvmOverloads
annotiert werden, um die Binärkompatibilität beizubehalten.
Weitere Informationen finden Sie im offiziellen Leitfaden zur Kotlin-Java-Interoperabilität unter Funktionsüberladungen für Standardwerte.
class Greeting @JvmOverloads constructor(
loudness: Int = 5
) {
@JvmOverloads
fun sayHello(prefix: String = "Dr.", name: String) = // ...
}
Standardparameterwerte nicht entfernen (nur Kotlin)
Wenn eine Methode mit einem Parameter mit einem Standardwert ausgeliefert wurde, ist das Entfernen des Standardwerts eine änderung, die die Funktionsweise der Quelle beeinträchtigt.
Die prägnantesten und aussagekräftigsten Methodenparameter sollten zuerst stehen.
Wenn eine Methode mehrere Parameter hat, sollten Sie die relevantesten zuerst nennen. Parameter, die Flags und andere Optionen angeben, sind weniger wichtig als diejenigen, die das Objekt beschreiben, auf das eine Aktion angewendet wird. Wenn es einen Abschluss-Callback gibt, platzieren Sie ihn an letzter Stelle.
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);
Siehe auch: Optionale Parameter in Überladungen am Ende platzieren
Builders
Das Builder-Muster wird für die Erstellung komplexer Java-Objekte empfohlen und wird in Android häufig in folgenden Fällen verwendet:
- Die Eigenschaften des resultierenden Objekts sollten unveränderlich sein.
- Es gibt eine große Anzahl erforderlicher Eigenschaften, z. B. viele Konstruktorargumente.
- Zwischen den Properties besteht beim Erstellen eine komplexe Beziehung, z. B. ist ein Bestätigungsschritt erforderlich. Diese Komplexität weist oft auf Probleme mit der Nutzerfreundlichkeit der API hin.
Überlegen Sie, ob Sie einen Builder benötigen. Builder sind in einer API-Oberfläche nützlich, wenn sie für Folgendes verwendet werden:
- Nur einige der potenziell vielen optionalen Erstellungsparameter konfigurieren
- Viele verschiedene optionale oder erforderliche Erstellungsparameter konfigurieren, manchmal von ähnlichen oder übereinstimmenden Typen, bei denen Aufruf-Websites sonst schwer zu lesen oder fehleranfällig beim Schreiben werden könnten
- Sie können die Erstellung eines Objekts inkrementell konfigurieren, wobei mehrere verschiedene Konfigurationscode-Abschnitte jeweils den Builder als Implementierungsdetails aufrufen.
- Einen Typ erweitern, indem in zukünftigen API-Versionen zusätzliche optionale Erstellungsparameter hinzugefügt werden
Wenn Sie einen Typ mit drei oder weniger erforderlichen Parametern und keinen optionalen Parametern haben, können Sie fast immer einen Builder überspringen und einen einfachen Konstruktor verwenden.
Bei Kotlin-Klassen sollten Sie @JvmOverloads
-annotierte Konstruktoren mit Standardargumenten gegenüber Builders bevorzugen. Sie können jedoch die Nutzerfreundlichkeit für Java-Clients verbessern, indem Sie in den oben beschriebenen Fällen auch Builders bereitstellen.
class Tone @JvmOverloads constructor(
val duration: Long = 1000,
val frequency: Int = 2600,
val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
class Builder {
// ...
}
}
Builder-Klassen müssen den Builder zurückgeben
Builder-Klassen müssen die Methodenabfolge ermöglichen, indem sie das Builder-Objekt (z. B. this
) von jeder Methode mit Ausnahme von build()
zurückgeben. Zusätzliche erstellte Objekte sollten als Argumente übergeben werden. Geben Sie nicht den Konstruktor eines anderen Objekts zurück.
Beispiel:
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();
}
}
In seltenen Fällen, in denen eine Basis-Builder-Klasse die Erweiterung unterstützen muss, verwenden Sie einen generischen Rückgabetyp:
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);
}
Builder-Klassen müssen über einen Konstruktor erstellt werden.
Damit die Erstellung von Buildern über die Android API-Oberfläche einheitlich erfolgt, müssen alle Builder über einen Konstruktor und nicht über eine statische Erstellungsmethode erstellt werden. Bei Kotlin-basierten APIs muss Builder
öffentlich sein, auch wenn Kotlin-Nutzer den Builder implizit über einen Erstellungsmechanismus im Stil einer Fabrikmethode/DSL verwenden sollen. In Bibliotheken darf @PublishedApi internal
nicht verwendet werden, um den Builder
-Klassenkonstruktor selektiv vor Kotlin-Clients auszublenden.
public class Tone {
public static Builder builder();
public static class Builder {
}
}
public class Tone {
public static class Builder {
public Builder();
}
}
Alle Argumente für Builder-Konstruktoren müssen erforderlich sein (z. B. @NonNull).
Optionale Argumente wie @Nullable
sollten in Setzermethoden verschoben werden.
Der Builder-Konstruktor sollte eine NullPointerException
auswerfen (Objects.requireNonNull
wird empfohlen), wenn erforderliche Argumente nicht angegeben sind.
Builder-Klassen sollten finale statische innere Klassen ihrer erstellten Typen sein.
Aus Gründen der logischen Organisation innerhalb eines Pakets sollten Builder-Klassen in der Regel als finale innere Klassen ihrer erstellten Typen freigegeben werden, z. B. Tone.Builder
anstelle von ToneBuilder
.
Erbauer können einen Konstruktor enthalten, um eine neue Instanz aus einer vorhandenen Instanz zu erstellen.
Erbauer können einen Kopierkonstruktor enthalten, um eine neue Erbauerinstanz aus einem vorhandenen Erbauer oder einem erstellten Objekt zu erstellen. Sie sollten keine alternativen Methoden zum Erstellen von Builder-Instanzen aus vorhandenen Buildern oder Build-Objekten bereitstellen.
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);
}
}
Builder-Setter sollten @Nullable-Argumente annehmen, wenn der Builder einen Kopierkonstruktor hat.
Das Zurücksetzen ist unerlässlich, wenn eine neue Instanz eines Builders aus einer vorhandenen Instanz erstellt werden kann. Wenn kein Kopierkonstruktor verfügbar ist, kann der Builder entweder @Nullable
- oder @NonNullable
-Argumente haben.
public static class Builder {
public Builder(Builder original);
public Builder setObjectValue(@Nullable Object value);
}
Builder-Setter können @Nullable-Argumente für optionale Properties annehmen
Es ist oft einfacher, einen Nullwert für Eingaben vom zweiten Grad zu verwenden, insbesondere in Kotlin, wo Standardargumente anstelle von Buildern und Überladungen verwendet werden.
Außerdem werden @Nullable
-Setter mit ihren Gettern abgeglichen, die für optionale Properties @Nullable
sein müssen.
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();
}
Gängige Verwendung in Kotlin:
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.apply { optionalValue?.let { setOptionalValue(it) } }
.build()
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.setOptionalValue(optionalValue)
.build()
Der Standardwert (falls der Setter nicht aufgerufen wird) und die Bedeutung von null
müssen sowohl im Setter als auch im Getter korrekt dokumentiert sein.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
Für veränderliche Eigenschaften können Builder-Setter bereitgestellt werden, wenn Setter in der erstellten Klasse verfügbar sind.
Wenn Ihre Klasse mutable Properties hat und eine Builder
-Klasse benötigt, sollten Sie sich zuerst fragen, ob Ihre Klasse tatsächlich mutable Properties haben sollte.
Wenn Sie sicher sind, dass Sie veränderbare Properties benötigen, entscheiden Sie als Nächstes, welches der folgenden Szenarien für Ihren erwarteten Anwendungsfall besser geeignet ist:
Das erstellte Objekt sollte sofort verwendet werden können. Daher sollten für alle relevanten Eigenschaften Setter bereitgestellt werden, unabhängig davon, ob sie veränderbar oder unveränderlich sind.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
Möglicherweise müssen einige zusätzliche Aufrufe erfolgen, bevor das erstellte Objekt nützlich ist. Daher sollten für veränderliche Eigenschaften keine Setter bereitgestellt werden.
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .build(); v.setUsefulMutableProperty(usefulValue) Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
Mischen Sie die beiden Szenarien nicht.
Value v = new Value.Builder(requiredValue)
.setImmutableProperty(immutableValue)
.setUsefulMutableProperty(usefulValue)
.build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);
Erbauer sollten keine Getter haben
Getter sollten sich im erstellten Objekt befinden, nicht im Builder.
Die Builder-Setter müssen entsprechende Getter in der erstellten Klasse haben.
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();
}
Benennung der Builder-Methode
Die Namen von Builder-Methoden sollten den Stil setFoo()
, addFoo()
oder clearFoo()
haben.
Builder-Klassen müssen eine build()-Methode deklarieren.
Builder-Klassen sollten eine build()
-Methode deklarieren, die eine Instanz des erstellten Objekts zurückgibt.
Builder-build()-Methoden müssen @NonNull-Objekte zurückgeben
Die build()
-Methode eines Builders sollte eine Nicht-NULL-Instanz des erstellten Objekts zurückgeben. Falls das Objekt aufgrund ungültiger Parameter nicht erstellt werden kann, kann die Validierung an die Build-Methode übergeben und eine IllegalStateException
-Ausnahme ausgelöst werden.
Interne Sperren nicht offenlegen
Das Keyword synchronized
darf in Methoden der öffentlichen API nicht verwendet werden. Dieses Keyword bewirkt, dass Ihr Objekt oder Ihre Klasse als Sperre verwendet wird. Da es für andere sichtbar ist, können unerwartete Nebenwirkungen auftreten, wenn anderer Code außerhalb Ihrer Klasse es zu Sperrzwecken verwendet.
Führen Sie stattdessen alle erforderlichen Sperrungen für ein internes, privates Objekt aus.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
Methoden im Accessor-Stil müssen den Kotlin-Richtlinien für Eigenschaften entsprechen
In Kotlin-Quellen sind Methoden im Accessor-Stil, die die Präfixe get
, set
oder is
verwenden, auch als Kotlin-Properties verfügbar.
Beispielsweise ist int getField()
, das in Java definiert ist, in Kotlin als Property val field: Int
verfügbar.
Aus diesem Grund und um die Erwartungen der Entwickler an das Verhalten von Zugriffsmethoden allgemein zu erfüllen, sollten sich Methoden mit Präfixen für Zugriffsmethoden ähnlich wie Java-Felder verhalten. Verwenden Sie keine Präfixe im Stil von Zugriffsfunktionen, wenn:
- Die Methode hat Nebenwirkungen – verwenden Sie einen aussagekräftigeren Methodennamen.
- Die Methode ist rechenintensiv – verwenden Sie stattdessen
compute
. - Die Methode umfasst blockierende oder anderweitig lang andauernde Arbeit, um einen Wert zurückzugeben, z. B. IPC oder andere E/A. Verwenden Sie
fetch
. - Die Methode blockiert den Thread, bis ein Wert zurückgegeben werden kann. Verwenden Sie stattdessen
await
. - Die Methode gibt bei jedem Aufruf eine neue Objektinstanz zurück. Verwenden Sie stattdessen
create
. - Die Methode gibt möglicherweise keinen Wert zurück. Verwenden Sie stattdessen
request
.
Beachten Sie, dass das Ausführen einer rechenintensiven Arbeit einmal und das Caching des Werts für nachfolgende Aufrufe immer noch als rechenintensive Arbeit gilt. Ruckler werden nicht über Frames hinweg abgebaut.
Verwenden Sie das Präfix „is“ für boolesche Zugriffsmethoden.
Das ist die Standardbenennungskonvention für boolesche Methoden und Felder in Java. Im Allgemeinen sollten boolesche Methoden- und Variablennamen als Fragen formuliert werden, die durch den Rückgabewert beantwortet werden.
Java-Boolesche Zugriffsmethoden sollten einem set
/is
-Namensschema folgen und Felder sollten vorzugsweise is
verwenden, z. B.:
// 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;
Wenn Sie set
/is
für Java-Zugriffsmethoden oder is
für Java-Felder verwenden, können sie als Eigenschaften aus Kotlin verwendet werden:
obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return
Für Properties und Zugriffsmethoden sollten im Allgemeinen positive Namen verwendet werden, z. B. Enabled
anstelle von Disabled
. Wenn Sie negative Terminologie verwenden, wird die Bedeutung von true
und false
umgekehrt und es wird schwieriger, das Verhalten zu analysieren.
// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);
Wenn die Boolesche Variable die Zugehörigkeit oder den Besitz einer Property beschreibt, können Sie anstelle von is auch has verwenden. Dies funktioniert jedoch nicht mit der Kotlin-Property-Syntax:
// 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();
Einige alternative Präfixe, die möglicherweise besser geeignet sind, sind kann und sollte:
// "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();
Für Methoden, mit denen Verhaltensweisen oder Funktionen aktiviert oder deaktiviert werden, können das Präfix is und das Suffix Enabled verwendet werden:
// "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()
Bei Methoden, die die Abhängigkeit von anderen Verhaltensweisen oder Funktionen angeben, kann das Präfix is und das Suffix Supported oder Required verwendet werden:
// "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()
Im Allgemeinen sollten Methodennamen als Fragen formuliert werden, die durch den Rückgabewert beantwortet werden.
Kotlin-Property-Methoden
Für eine Klasseneigenschaft var foo: Foo
generiert Kotlin get
/set
-Methoden mit einer einheitlichen Regel: Vorangestelltes get
und Großschreibung des ersten Zeichens für den Getter sowie vorangestelltes set
und Großschreibung des ersten Zeichens für den Setter. Die Deklaration der Property führt zu Methoden mit den Namen public Foo getFoo()
und public void setFoo(Foo foo)
.
Wenn die Property vom Typ Boolean
ist, gilt bei der Namensgenerierung eine zusätzliche Regel: Wenn der Property-Name mit is
beginnt, wird dem Namen der Get-Methode nicht get
vorangestellt. Stattdessen wird der Property-Name selbst als Get-Methode verwendet.
Benennen Sie Boolean
-Properties daher vorzugsweise mit dem Präfix is
, um der Benennungsrichtlinie zu folgen:
var isVisible: Boolean
Wenn Ihre Property zu einer der oben genannten Ausnahmen gehört und mit einem geeigneten Präfix beginnt, können Sie den entsprechenden Namen manuell mithilfe der Anmerkung @get:JvmName
für die Property angeben:
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
Bitmasken-Accessors
API-Richtlinien zum Definieren von Bitmasken-Flags finden Sie unter @IntDef
für Bitmasken-Flags verwenden.
Setter
Es sollten zwei Setzermethoden bereitgestellt werden: eine, die einen vollständigen Bitstring annimmt und alle vorhandenen Flags überschreibt, und eine andere, die eine benutzerdefinierte Bitmaske annimmt, um mehr Flexibilität zu ermöglichen.
/**
* 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);
Getter
Es sollte ein Getter bereitgestellt werden, um die vollständige Bitmaske abzurufen.
/**
* 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();
„Öffentlich“ anstelle von „Geschützt“ verwenden
Verwenden Sie in der öffentlichen API immer public
anstelle von protected
. Der geschützte Zugriff ist auf lange Sicht mühsam, da die Implementierer öffentliche Zugriffsmethoden überschreiben müssen, wenn ein externer Zugriff standardmäßig genauso gut gewesen wäre.
Denken Sie daran, dass die protected
Sichtbarkeit nicht verhindert, dass Entwickler eine API aufrufen. Sie macht es nur etwas aufdringlicher.
Weder equals() noch hashCode() implementieren
Wenn Sie eine überschreiben, müssen Sie auch die andere überschreiben.
toString() für Datenklassen implementieren
Bei Datenklassen wird empfohlen, toString()
zu überschreiben, um Entwicklern bei der Fehlerbehebung zu helfen.
Dokumentieren Sie, ob die Ausgabe sich auf das Programmverhalten oder das Debuggen bezieht.
Entscheiden Sie, ob das Programmverhalten von Ihrer Implementierung abhängen soll oder nicht. Beispielsweise dokumentieren UUID.toString() und File.toString() ihr spezifisches Format für Programme. Wenn Sie Informationen nur zum Debuggen freigeben, z. B. Intent, sollten Sie angeben, dass Dokumente von der Superklasse übernommen werden.
Fügen Sie keine zusätzlichen Informationen hinzu.
Alle Informationen, die über toString()
verfügbar sind, sollten auch über die öffentliche API des Objekts verfügbar sein. Andernfalls werden Entwickler dazu angehalten, Ihre toString()
-Ausgabe zu parsen und zu verwenden, was zukünftige Änderungen verhindert. Es empfiehlt sich, toString()
nur mit der öffentlichen API des Objekts zu implementieren.
Auf die Debug-Ausgabe nicht zu sehr verlassen
Es ist zwar nicht möglich, zu verhindern, dass Entwickler auf die Debug-Ausgabe angewiesen sind, aber wenn Sie die System.identityHashCode
Ihres Objekts in die toString()
-Ausgabe aufnehmen, ist es sehr unwahrscheinlich, dass zwei verschiedene Objekte dieselbe toString()
-Ausgabe haben.
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}
Das kann Entwickler davon abhalten, Testaussagen wie assertThat(a.toString()).isEqualTo(b.toString())
für Ihre Objekte zu schreiben.
„createFoo“ verwenden, um neu erstellte Objekte zurückzugeben
Verwenden Sie das Präfix create
, nicht get
oder new
, für Methoden, die Rückgabewerte erstellen, z. B. durch Erstellen neuer Objekte.
Wenn die Methode ein Objekt zum Zurückgeben erstellt, muss dies im Methodennamen klar ersichtlich sein.
public FooThing getFooThing() {
return new FooThing();
}
public FooThing createFooThing() {
return new FooThing();
}
Methoden, die Dateiobjekte akzeptieren, sollten auch Streams akzeptieren
Speicherorte für Daten auf Android-Geräten sind nicht immer Dateien auf dem Laufwerk. Beispielsweise werden Inhalte, die über Nutzergrenzen hinweg übergeben werden, als content://
Uri
dargestellt. Damit verschiedene Datenquellen verarbeitet werden können, sollten APIs, die File
-Objekte akzeptieren, auch InputStream
-, OutputStream
-Objekte oder beide akzeptieren.
public void setDataSource(File file)
public void setDataSource(InputStream stream)
Rohe Primitive statt Boxversionen entgegennehmen und zurückgeben
Wenn Sie fehlende oder Nullwerte angeben möchten, können Sie -1
, Integer.MAX_VALUE
oder Integer.MIN_VALUE
verwenden.
public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)
Wenn Sie Klassenäquivalente primitiver Typen vermeiden, vermeiden Sie den Arbeitsspeicher-Overhead dieser Klassen, den Methodenzugriff auf Werte und vor allem das Autoboxing, das durch das Casting zwischen primitiven und Objekttypen entsteht. Wenn Sie diese Verhaltensweisen vermeiden, sparen Sie Arbeitsspeicher und temporäre Zuweisungen, die zu teuren und häufigeren Garbage Collection-Vorgängen führen können.
Anmerkungen verwenden, um gültige Parameter- und Rückgabewerte zu verdeutlichen
Es wurden Anmerkungen für Entwickler hinzugefügt, um zulässige Werte in verschiedenen Situationen zu verdeutlichen. So können Tools Entwicklern leichter helfen, wenn sie falsche Werte angeben, z. B. einen beliebigen int
übergeben, wenn das Framework einen bestimmten Satz von Konstantenwerten erfordert. Verwenden Sie nach Bedarf alle folgenden Anmerkungen:
Null-Zulässigkeit
Für Java-APIs sind explizite Anmerkungen zur Nullbarkeit erforderlich. Das Konzept der Nullbarkeit ist jedoch Teil der Kotlin-Sprache und Anmerkungen zur Nullbarkeit sollten in Kotlin-APIs niemals verwendet werden.
@Nullable
:Gibt an, dass ein bestimmter Rückgabewert, Parameter oder ein Feld null sein kann:
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
:Gibt an, dass ein bestimmter Rückgabewert, Parameter oder ein Feld nicht null sein darf. Die Kennzeichnung von Elementen als @Nullable
ist relativ neu bei Android. Daher sind die meisten API-Methoden von Android nicht einheitlich dokumentiert. Daher haben wir drei Status: „unbekannt“, „@Nullable
“ und „@NonNull
“. Deshalb ist @NonNull
Teil der API-Richtlinien:
@NonNull
public String getName()
public void setName(@NonNull String name)
Wenn Sie die Methodenparameter in der Dokumentation für die Android-Plattform annotieren, wird automatisch eine Dokumentation in der Form „Dieser Wert kann null sein“ generiert, es sei denn, „null“ wird an anderer Stelle in der Parameterdokumentation explizit verwendet.
Bestehende Methoden, die „nicht wirklich null“ sind:Vorhandene Methoden in der API ohne deklarierte @Nullable
-Anmerkung können mit @Nullable
annotiert werden, wenn die Methode unter bestimmten, offensichtlichen Umständen (z. B. findViewById()
) null
zurückgeben kann. Für Entwickler, die keine Null-Prüfung durchführen möchten, sollten zugehörige @NotNull requireFoo()
-Methoden hinzugefügt werden, die IllegalArgumentException
auswerfen.
Schnittstellenmethoden:Neue APIs sollten bei der Implementierung von Schnittstellenmethoden wie Parcelable.writeToParcel()
die richtige Anmerkung hinzufügen. Das bedeutet, dass die Methode in der implementierenden Klasse writeToParcel(@NonNull Parcel,
int)
und nicht writeToParcel(Parcel, int)
sein sollte. Vorhandene APIs, bei denen die Anmerkungen fehlen, müssen jedoch nicht „repariert“ werden.
Erzwingung der Null-Zulässigkeit
In Java wird empfohlen, Methoden zur Eingabevalidierung für @NonNull
-Parameter mit Objects.requireNonNull()
auszuführen und eine NullPointerException
zu werfen, wenn die Parameter null sind. In Kotlin geschieht dies automatisch.
Ressourcen
Ressourcen-IDs:Ganzzahlparameter, die IDs für bestimmte Ressourcen angeben, sollten mit der entsprechenden Ressourcentypdefinition versehen werden.
Neben dem Allzweck-Label @AnyRes
gibt es eine Annotationsanfrage für jeden Ressourcentyp, z. B. @StringRes
, @ColorRes
und @AnimRes
. Beispiel:
public void setTitle(@StringRes int resId)
@IntDef für Konstantenmengen
Magische Konstanten:String
- und int
-Parameter, die einen von einer endlichen Anzahl möglicher Werte erhalten sollen, die durch öffentliche Konstanten angegeben werden, müssen entsprechend mit @StringDef
oder @IntDef
annotiert werden. Mit diesen Anmerkungen können Sie eine neue Anmerkung erstellen, die als Typdefinition für zulässige Parameter verwendet werden kann. Beispiel:
/** @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);
Es wird empfohlen, die Gültigkeit der annotierten Parameter zu prüfen und eine IllegalArgumentException
auszugeben, wenn der Parameter nicht Teil der @IntDef
ist.
@IntDef für Bitmasken-Flags
In der Anmerkung kann auch angegeben werden, dass die Konstanten Flags sind und mit „&“ und „I“ kombiniert werden können:
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_USE_LOGO,
FLAG_SHOW_HOME,
FLAG_HOME_AS_UP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}
@StringDef für Sets von Stringkonstanten
Außerdem gibt es die Anmerkung @StringDef
, die genau wie @IntDef
im vorherigen Abschnitt funktioniert, aber für String
-Konstanten. Sie können mehrere „prefix“-Werte angeben, anhand derer automatisch Dokumentationen für alle Werte generiert werden.
@SdkConstant für SDK-Konstanten
@SdkConstant: Fügen Sie öffentliche Felder mit einem der folgenden SdkConstant
-Werte an: ACTIVITY_INTENT_ACTION
, BROADCAST_INTENT_ACTION
, SERVICE_ACTION
, INTENT_CATEGORY
und FEATURE
.
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";
Kompatible Nullbarkeit für Überschreibungen bereitstellen
Aus Gründen der API-Kompatibilität muss die Nullbarkeit von Überschreibungen mit der aktuellen Nullbarkeit des übergeordneten Elements übereinstimmen. In der folgenden Tabelle sind die Kompatibilitätsanforderungen aufgeführt. Überschreibungen sollten nur so restriktiv oder restriktiver sein als das Element, das sie überschreiben.
Eingeben | Elternteil | Kind |
---|---|---|
Rückgabetyp | Ohne Anmerkungen | Nicht kommentiert oder nicht null |
Rückgabetyp | Nullwerte zulässig | „Nullable“ oder „Nonnull“ |
Rückgabetyp | Nonnull | Nonnull |
Spaßiges Argument | Ohne Anmerkungen | Nicht annotiert oder nullable |
Spaßiges Argument | Nullwerte zulässig | Nullwerte zulässig |
Spaßiges Argument | Nonnull | „Nullable“ oder „Nonnull“ |
Verwenden Sie nach Möglichkeit Argumente, die nicht null sein dürfen (z. B. @NonNull).
Bei überladenen Methoden sollten alle Argumente nicht null sein.
public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
Diese Regel gilt auch für überladene Property-Setter. Das primäre Argument darf nicht null sein und das Löschen der Property sollte als separate Methode implementiert werden. So werden sinnlose Aufrufe verhindert, bei denen der Entwickler nachstellte Parameter festlegen muss, obwohl diese nicht erforderlich sind.
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()
Verwenden Sie für Container Rückgabetypen, die nicht null sein können (z. B. @NonNull).
Gib für Containertypen wie Bundle
oder Collection
einen leeren und gegebenenfalls unveränderlichen Container zurück. Wenn null
verwendet wird, um die Verfügbarkeit eines Containers zu unterscheiden, sollten Sie eine separate boolesche Methode angeben.
@NonNull
public Bundle getExtras() { ... }
Anmerkungen zur Nullbarkeit für Get- und Set-Paare müssen übereinstimmen
Die Anmerkungen zur Nullbarkeit von Methodenpaaren für eine einzelne logische Property müssen immer übereinstimmen. Wenn Sie diese Richtlinie nicht einhalten, wird die Property-Syntax von Kotlin beeinträchtigt. Das Hinzufügen von unterschiedlichen Anmerkungen zur Nullbarkeit zu vorhandenen Property-Methoden ist daher eine änderungsbedingte Änderung für Kotlin-Nutzer.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
Rückgabewert bei Fehlern
Alle APIs sollten es Apps ermöglichen, auf Fehler zu reagieren. Wenn false
, -1
, null
oder andere allgemeine Werte wie „Es ist ein Fehler aufgetreten“ zurückgegeben werden, erfahren Entwickler nicht genug darüber, ob die Erwartungen der Nutzer nicht erfüllt wurden oder ob die Zuverlässigkeit ihrer App in der Praxis nicht korrekt erfasst wurde. Stellen Sie sich beim Entwerfen einer API vor, dass Sie eine App entwickeln. Wenn ein Fehler auftritt, erhalten Sie von der API genügend Informationen, um ihn dem Nutzer zu präsentieren oder entsprechend zu reagieren?
- Es ist in Ordnung (und wird empfohlen), detaillierte Informationen in eine Ausnahmemeldung aufzunehmen. Entwickler sollten sie jedoch nicht analysieren müssen, um den Fehler angemessen zu behandeln. Ausführliche Fehlercodes oder andere Informationen sollten als Methoden freigegeben werden.
- Die von Ihnen ausgewählte Option zur Fehlerbehandlung sollte Ihnen die Flexibilität bieten, in Zukunft neue Fehlertypen einzuführen. Bei
@IntDef
bedeutet das, dass einOTHER
- oderUNKNOWN
-Wert angegeben werden muss. Wenn Sie einen neuen Code zurückgeben, können Sie dentargetSdkVersion
des Aufrufers prüfen, um zu vermeiden, dass ein Fehlercode zurückgegeben wird, den die App nicht kennt. Verwenden Sie für Ausnahmen einen gemeinsamen Supertyp, den Ihre Ausnahmen implementieren, damit jeder Code, der diesen Typ verarbeitet, auch Untertypen abfangen und verarbeiten kann. - Es sollte für Entwickler schwierig oder unmöglich sein, einen Fehler versehentlich zu ignorieren. Wenn der Fehler durch das Zurückgeben eines Werts mitgeteilt wird, kennzeichnen Sie Ihre Methode mit
@CheckResult
.
Sie sollten ? extends RuntimeException
vorzugsweise dann werfen, wenn ein Fehler auftritt, weil der Entwickler etwas falsch gemacht hat, z. B. Einschränkungen für Eingabeparameter ignoriert oder den beobachtbaren Status nicht geprüft hat.
Setter- oder Aktionsmethoden (z. B. perform
) können einen Ganzzahlstatuscode zurückgeben, wenn die Aktion aufgrund eines asynchron aktualisierten Status oder von Bedingungen fehlschlägt, die außerhalb der Kontrolle des Entwicklers liegen.
Statuscodes sollten in der übergeordneten Klasse als public static final
-Felder definiert, mit ERROR_
vorangestellt und in einer @hide
-@IntDef
-Anmerkung aufgezählt werden.
Methodennamen sollten immer mit dem Verb beginnen, nicht mit dem Subjekt.
Der Name der Methode sollte immer mit dem Verb beginnen (z. B. get
, create
, reload
usw.), nicht mit dem Objekt, auf das Sie eine Aktion ausführen.
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
Verwenden Sie Sammlungstypen anstelle von Arrays als Rückgabe- oder Parametertyp.
Generisch typisierte Sammlungsschnittstellen bieten gegenüber Arrays mehrere Vorteile, darunter strengere API-Verträge in Bezug auf Eindeutigkeit und Sortierung, Unterstützung für generische Typen und eine Reihe von entwicklungsfreundlichen Methoden.
Ausnahme für primitive Typen
Wenn die Elemente primitive Typen sind, verwenden Sie stattdessen Arrays, um die Kosten für das automatische Boxen zu vermeiden. Weitere Informationen finden Sie unter Roh-Primitive statt Boxed-Versionen aufnehmen und zurückgeben.
Ausnahme für leistungsempfindlichen Code
In bestimmten Fällen, in denen die API in leistungssensiblem Code verwendet wird (z. B. Grafiken oder andere APIs für Messung, Layout oder Zeichnen), ist es akzeptabel, Arrays anstelle von Sammlungen zu verwenden, um Zuweisungen und Speicherbelegung zu reduzieren.
Ausnahme für Kotlin
Kotlin-Arrays sind unveränderlich und die Kotlin-Sprache bietet zahlreiche Dienstprogramm-APIs für Arrays. Daher sind Arrays für Kotlin-APIs, auf die von Kotlin aus zugegriffen werden soll, mit List
und Collection
vergleichbar.
@NonNull-Sammlungen bevorzugen
Verwenden Sie für Sammlungsobjekte immer @NonNull
. Wenn Sie eine leere Sammlung zurückgeben, verwenden Sie die entsprechende Collections.empty
-Methode, um ein kostengünstiges, korrekt typisiertes und unveränderliches Sammlungsobjekt zurückzugeben.
Wenn Typanmerkungen unterstützt werden, verwenden Sie für Sammlungselemente immer @NonNull
.
Sie sollten @NonNull
auch verwenden, wenn Sie Arrays anstelle von Sammlungen verwenden (siehe vorheriger Punkt). Wenn Sie Bedenken bezüglich der Objektzuweisung haben, erstellen Sie eine Konstante und geben Sie sie weiter. Ein leeres Array ist schließlich unveränderlich. Beispiel:
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;
}
Änderbarkeit der Sammlung
In Kotlin-APIs sollten standardmäßig nur lesezugriffsfähige Rückgabetypen (nicht Mutable
) für Sammlungen verwendet werden, es sei denn, der API-Vertrag erfordert ausdrücklich einen veränderbaren Rückgabetyp.
Bei Java-APIs sollten jedoch standardmäßig änderbare Rückgabetypen verwendet werden, da die Android-Plattformimplementierung von Java-APIs noch keine praktische Implementierung unveränderlicher Sammlungen bietet. Eine Ausnahme von dieser Regel sind Collections.empty
-Rückgabetypen, die unveränderlich sind. Wenn die Mutabilität von Clients absichtlich oder versehentlich ausgenutzt werden könnte, um das beabsichtigte Nutzungsmuster der API zu brechen, sollten Java-APIs eine flache Kopie der Sammlung zurückgeben.
@Nullable
public PermissionInfo[] getGrantedPermissions() {
return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
if (mPermissions == null) {
return Collections.emptySet();
}
return new ArraySet<>(mPermissions);
}
Explizit veränderbare Rückgabetypen
APIs, die Sammlungen zurückgeben, sollten das zurückgegebene Sammlungsobjekt nach der Rückgabe idealerweise nicht ändern. Wenn sich die zurückgegebene Sammlung ändern oder auf irgendeine Weise wiederverwendet werden muss, z. B. eine angepasste Ansicht eines veränderbaren Datensatzes, muss das genaue Verhalten festgelegt werden, wann sich die Inhalte ändern können, oder es müssen etablierte API-Namenskonventionen befolgt werden.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
Die Kotlin-Konvention für .asFoo()
wird unten beschrieben. Sie ermöglicht es, dass sich die von .asList()
zurückgegebene Sammlung ändert, wenn sich die ursprüngliche Sammlung ändert.
Veränderbarkeit der zurückgegebenen Datentypobjekte
Ähnlich wie bei APIs, die Sammlungen zurückgeben, sollten APIs, die Objekte vom Typ „Datentyp“ zurückgeben, die Eigenschaften des zurückgegebenen Objekts nach der Rückgabe idealerweise nicht ändern.
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)
}
In äußerst seltenen Fällen kann bei leistungskritischem Code ein Vorteil durch Objekt-Pooling oder ‑Wiederverwendung erzielt werden. Erstellen Sie keine eigene Objektpool-Datenstruktur und stellen Sie wiederverwendete Objekte nicht in öffentlichen APIs bereit. In beiden Fällen sollten Sie den gleichzeitigen Zugriff mit äußerster Sorgfalt verwalten.
Verwendung des Parametertyps „vararg“
Sowohl in Kotlin- als auch in Java-APIs wird empfohlen, vararg
zu verwenden, wenn der Entwickler wahrscheinlich ein Array an der Aufrufstelle erstellt, um nur mehrere, verwandte Parameter desselben Typs zu übergeben.
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);
Defensive Kopien
Sowohl Java- als auch Kotlin-Implementierungen von vararg
-Parametern werden in denselben arraybasierten Bytecode kompiliert und können daher aus Java-Code mit einem veränderbaren Array aufgerufen werden. API-Entwicklern wird dringend empfohlen, eine defensive flache Kopie des Arrayparameters zu erstellen, wenn er in einem Feld oder einer anonymen inneren Klasse gespeichert wird.
public void setValues(SomeObject... values) {
this.values = Arrays.copyOf(values, values.length);
}
Das Erstellen einer defensiven Kopie bietet keinen Schutz vor gleichzeitigen Änderungen zwischen dem ursprünglichen Methodenaufruf und dem Erstellen der Kopie. Außerdem bietet es keinen Schutz vor der Mutation der im Array enthaltenen Objekte.
Korrekte Semantik mit Parametern für Sammlungstypen oder Rückgabetypen angeben
List<Foo>
ist die Standardoption. Es gibt aber auch andere Typen, die zusätzliche Bedeutungen haben:
Verwenden Sie
Set<Foo>
, wenn die Reihenfolge der Elemente in Ihrer API nicht wichtig ist und Duplikate nicht zulässig sind oder keine Bedeutung haben.Collection<Foo>,
, wenn Ihre API die Reihenfolge nicht berücksichtigt und Duplikate zulässt.
Kotlin-Konversionsfunktionen
In Kotlin werden .toFoo()
und .asFoo()
häufig verwendet, um ein Objekt eines anderen Typs aus einem vorhandenen Objekt abzurufen. Dabei ist Foo
der Name des Rückgabetyps der Umwandlung. Dies entspricht dem bekannten JDKObject.toString()
. In Kotlin wird dies noch weitergeführt und auch für primitive Konvertierungen wie 25.toFloat()
verwendet.
Der Unterschied zwischen Conversions mit den Namen .toFoo()
und .asFoo()
ist erheblich:
.toFoo() zum Erstellen eines neuen, unabhängigen Objekts verwenden
Wie bei .toString()
wird bei einer „zu“-Conversion ein neues, unabhängiges Objekt zurückgegeben. Wenn das ursprüngliche Objekt später geändert wird, werden diese Änderungen nicht auf das neue Objekt übertragen.
Wenn das neue Objekt später geändert wird, werden diese Änderungen nicht im alten Objekt übernommen.
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
.asFoo() zum Erstellen eines abhängigen Wrappers, eines verzierten Objekts oder eines Typs verwenden
In Kotlin wird das Casting mit dem Schlüsselwort as
durchgeführt. Sie spiegelt eine Änderung der Benutzeroberfläche, aber keine Änderung der Identität wider. Wenn .asFoo()
als Präfix in einer Erweiterungsfunktion verwendet wird, wird der Empfänger damit dekoriert. Eine Mutation im ursprünglichen Empfängerobjekt wird im von asFoo()
zurückgegebenen Objekt berücksichtigt.
Eine Mutation im neuen Foo
-Objekt wird möglicherweise im ursprünglichen Objekt übernommen.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
Konversionsfunktionen sollten als Erweiterungsfunktionen geschrieben werden
Wenn Sie Conversion-Funktionen außerhalb der Definitionen der Empfänger- und Ergebnisklasse schreiben, wird die Kopplung zwischen den Typen reduziert. Für eine ideale Umwandlung ist nur öffentlicher API-Zugriff auf das Originalobjekt erforderlich. Das zeigt, dass Entwickler auch analoge Conversions für ihre eigenen bevorzugten Typen erstellen können.
Entsprechende spezifische Ausnahmen auslösen
Methoden dürfen keine generischen Ausnahmen wie java.lang.Exception
oder java.lang.Throwable
auslösen. Stattdessen muss eine geeignete spezifische Ausnahme wie java.lang.NullPointerException
verwendet werden, damit Entwickler Ausnahmen behandeln können, ohne zu allgemein zu sein.
Bei Fehlern, die nicht mit den Argumenten zusammenhängen, die direkt an die öffentlich aufgerufene Methode übergeben werden, sollte java.lang.IllegalStateException
statt java.lang.IllegalArgumentException
oder java.lang.NullPointerException
geworfen werden.
Listener und Callbacks
Dies sind die Regeln für die Klassen und Methoden, die für Listener- und Rückrufmechanismen verwendet werden.
Callback-Klassennamen müssen im Singular stehen.
Verwenden Sie MyObjectCallback
anstelle von MyObjectCallbacks
.
Die Namen von Callback-Methoden müssen das Format on haben.
onFooEvent
bedeutet, dass FooEvent
auftritt und dass der Callback entsprechend reagieren soll.
Präteritum und Präsens sollten das Timing beschreiben
Callback-Methoden für Ereignisse sollten so benannt sein, dass sie angeben, ob das Ereignis bereits stattgefunden hat oder gerade stattfindet.
Wenn die Methode beispielsweise nach einer Klickaktion aufgerufen wird:
public void onClicked()
Wenn die Methode jedoch für die Ausführung der Klickaktion verantwortlich ist, gilt Folgendes:
public boolean onClick()
Callback-Registrierung
Wenn einem Objekt ein Listener oder Rückruf hinzugefügt oder daraus entfernt werden kann, sollten die zugehörigen Methoden „Hinzufügen“ und „Entfernen“ oder „Registrieren“ und „Abmelden“ heißen. Sie müssen der bestehenden Konvention entsprechen, die von der Klasse oder anderen Klassen im selben Paket verwendet wird. Wenn kein solcher Präzedenzfall vorliegt, verwenden Sie vorzugsweise „Hinzufügen“ und „Entfernen“.
Bei Methoden, bei denen Callbacks registriert oder deregistriert werden, muss der vollständige Name des Callback-Typs angegeben werden.
public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);
Vermeiden Sie Getter für Rückrufe
Fügen Sie getFooCallback()
-Methoden nicht hinzu. Dies ist eine verlockende Notlösung für Fälle, in denen Entwickler einen vorhandenen Rückruf mit ihrem eigenen Ersatz verknüpfen möchten. Sie ist jedoch fehleranfällig und erschwert es Komponentenentwicklern, den aktuellen Status zu ermitteln. Beispiel:
- Entwickler A ruft
setFooCallback(a)
an - Entwickler B ruft
setFooCallback(new B(getFooCallback()))
an - Entwickler A möchte seinen Rückruf
a
entfernen, kann dies aber nur tun, wenn er den Typ vonB
kennt.B
wurde jedoch so entwickelt, dass solche Änderungen an seinem verpackten Rückruf zulässig sind.
Executor akzeptieren, um den Callback-Versand zu steuern
Bei der Registrierung von Callbacks, für die keine expliziten Anforderungen an die Threadverwaltung gelten (praktisch überall außerhalb des UI-Toolkits), wird dringend empfohlen, bei der Registrierung einen Executor
-Parameter anzugeben, damit der Entwickler den Thread angeben kann, in dem die Callbacks aufgerufen werden.
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
Als Ausnahme zu unseren üblichen Richtlinien zu optionalen Parametern ist es zulässig, eine Überladung anzugeben, bei der Executor
weggelassen wird, auch wenn es sich nicht um das letzte Argument in der Parameterliste handelt. Wenn Executor
nicht angegeben ist, sollte der Rückruf im Hauptthread mit Looper.getMainLooper()
aufgerufen werden. Dies sollte in der zugehörigen überladenen Methode dokumentiert werden.
/**
* ...
* 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
Tücken bei der Implementierung:Beachten Sie, dass das Folgende ein gültiger Executor ist.
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
Das bedeutet, dass bei der Implementierung von APIs dieses Typs die Implementierung des eingehenden Binder-Objekts auf App-Prozessseite Binder.clearCallingIdentity()
aufrufen muss, bevor der Callback der App auf der von der App bereitgestellten Executor
aufgerufen wird. So wird jeder App-Code, der die Binder-Identität (z. B. Binder.getCallingUid()
) für Berechtigungsprüfungen verwendet, der App und nicht dem Systemprozess zugeordnet, der die App aufruft. Wenn Nutzer Ihrer API die UID- oder PID-Informationen des Aufrufers benötigen, sollten diese explizit Teil Ihrer API-Oberfläche sein und nicht implizit davon abhängen, wo die von ihnen angegebene Binder.getCallingUid()
ausgeführt wurde.Executor
Die Angabe eines Executor
sollte von Ihrer API unterstützt werden. In leistungskritischen Fällen müssen Apps möglicherweise Code entweder sofort oder synchron mit dem Feedback Ihrer API ausführen. Das Akzeptieren einer Executor
ermöglicht dies.
Wenn Sie aus defensiven Gründen eine zusätzliche HandlerThread
oder ähnliches als Trampolin erstellen, wird dieser wünschenswerte Anwendungsfall untergraben.
Wenn eine App irgendwo in ihrem eigenen Prozess ressourcenintensiven Code ausführen soll, lassen Sie es zu. Die Umgehungslösungen, die App-Entwickler finden, um Ihre Einschränkungen zu umgehen, sind langfristig viel schwieriger zu unterstützen.
Ausnahme für einzelnen Callback:Wenn die Art der gemeldeten Ereignisse nur die Unterstützung einer einzelnen Callback-Instanz erfordert, verwenden Sie den folgenden Stil:
public void setFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
public void clearFooCallback()
Executor anstelle von Handler verwenden
Handler
von Android wurde in der Vergangenheit als Standard für die Weiterleitung der Rückrufausführung an einen bestimmten Looper
-Thread verwendet. Dieser Standard wurde geändert, um Executor
zu bevorzugen, da die meisten App-Entwickler ihre eigenen Threadpools verwalten. Dadurch ist der Haupt- oder UI-Thread der einzige Looper
-Thread, der für die App verfügbar ist. Mit Executor
erhalten Entwickler die Kontrolle, die sie benötigen, um ihre vorhandenen/bevorzugten Ausführungskontexte wiederzuverwenden.
Moderne Bibliotheken für die Parallelität wie kotlinx.coroutines oder RxJava bieten eigene Planungsmechanismen, die bei Bedarf eine eigene Zuweisung ausführen. Daher ist es wichtig, die Möglichkeit zu bieten, einen direkten Executor (z. B. Runnable::run
) zu verwenden, um Latenzen durch doppelte Thread-Hops zu vermeiden. Beispiel: Ein Hop, um mit einem Handler
in einem Looper
-Thread zu posten, gefolgt von einem weiteren Hop vom Concurrentitäts-Framework der App.
Ausnahmen von dieser Richtlinie sind selten. Häufige Gründe für eine Ausnahme:
Ich muss eine Looper
verwenden, weil ich für die Veranstaltung eine Looper
bis epoll
benötige.
Dieser Ausnahmeantrag wird gewährt, da die Vorteile von Executor
in dieser Situation nicht genutzt werden können.
Ich möchte nicht, dass der App-Code die Veröffentlichung des Ereignisses in meinem Thread blockiert. Diese Ausnahmeanfrage wird in der Regel nicht für Code gewährt, der in einem App-Prozess ausgeführt wird. Apps, die dies falsch machen, schaden sich selbst, haben aber keine Auswirkungen auf die allgemeine Systemgesundheit. Bei Apps, die richtig implementiert sind oder ein gängiges Concurrent-Execution-Framework verwenden, sollten keine zusätzlichen Latenzprobleme auftreten.
Handler
ist lokal mit anderen ähnlichen APIs in derselben Klasse konsistent.
Diese Ausnahmegenehmigung wird je nach Situation gewährt. Es wird empfohlen, Executor
-basierte Überladungen hinzuzufügen und Handler
-Implementierungen auf die neue Executor
-Implementierung umzustellen. (myHandler::post
ist ein gültiger Executor
!) Je nach Größe der Klasse, Anzahl der vorhandenen Handler
-Methoden und Wahrscheinlichkeit, dass Entwickler neben der neuen Methode vorhandene Handler
-basierte Methoden verwenden müssen, kann eine Ausnahme gewährt werden, um eine neue Handler
-basierte Methode hinzuzufügen.
Symmetrie bei der Registrierung
Wenn es eine Möglichkeit gibt, etwas hinzuzufügen oder zu registrieren, sollte es auch eine Möglichkeit geben, es zu entfernen oder zu unregisteren. Die Methode
registerThing(Thing)
sollte eine übereinstimmende
unregisterThing(Thing)
Geben Sie eine Anfrage-ID an.
Wenn es für einen Entwickler sinnvoll ist, einen Callback wiederzuverwenden, geben Sie ein Identifier-Objekt an, um den Callback an die Anfrage zu binden.
class RequestParameters {
public int getId() { ... }
}
class RequestExecutor {
public void executeRequest(
RequestParameters parameters,
Consumer<RequestParameters> onRequestCompletedListener) { ... }
}
Callback-Objekte mit mehreren Methoden
Bei Callbacks mit mehreren Methoden sollten Sie interface
bevorzugen und default
-Methoden verwenden, wenn Sie sie zu zuvor veröffentlichten Schnittstellen hinzufügen. Bisher wurde in dieser Richtlinie abstract class
empfohlen, da es in Java 7 keine default
-Methoden gab.
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
android.os.OutcomeReceiver beim Modellieren eines nicht blockierenden Funktionsaufrufs verwenden
OutcomeReceiver<R,E>
meldet bei Erfolg den Ergebniswert R
oder andernfalls E : Throwable
– genau wie ein einfacher Methodenaufruf. Verwenden Sie OutcomeReceiver
als Rückgabetyp, wenn Sie eine blockierende Methode, die ein Ergebnis zurückgibt oder eine Ausnahme auslöst, in eine nicht blockierende asynchrone Methode umwandeln:
interface FooType {
// Before:
public FooResult requestFoo(FooRequest request);
// After:
public void requestFooAsync(FooRequest request, Executor executor,
OutcomeReceiver<FooResult, Throwable> callback);
}
Auf diese Weise konvertierte asynchrone Methoden geben immer void
zurück. Alle Ergebnisse, die requestFoo
zurückgeben würde, werden stattdessen an den OutcomeReceiver.onResult
des callback
-Parameters von requestFooAsync
gemeldet, indem er mit der angegebenen executor
aufgerufen wird.
Alle Ausnahmen, die von requestFoo
ausgelöst würden, werden stattdessen auf die gleiche Weise an die Methode OutcomeReceiver.onError
gemeldet.
Wenn Sie OutcomeReceiver
zum Melden von Ergebnissen asynchroner Methoden verwenden, können Sie auch einen Kotlin-suspend fun
-Wrapper für asynchrone Methoden mit der Continuation.asOutcomeReceiver
-Erweiterung von androidx.core:core-ktx
verwenden:
suspend fun FooType.requestFoo(request: FooRequest): FooResult =
suspendCancellableCoroutine { continuation ->
requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
}
Mit solchen Erweiterungen können Kotlin-Clients nicht blockierende asynchrone Methoden mit der Bequemlichkeit eines einfachen Funktionsaufrufs aufrufen, ohne den aufrufenden Thread zu blockieren. Diese 1:1-Erweiterungen für Plattform-APIs können im Rahmen des androidx.core:core-ktx
-Artefakts in Jetpack angeboten werden, wenn sie mit den Kompatibilitätstests und -überlegungen der Standardversion kombiniert werden. Weitere Informationen, Hinweise zur Kündigung und Beispiele findest du in der Dokumentation zu asOutcomeReceiver.
Bei asynchronen Methoden, die nicht der Semantik einer Methode entsprechen, die ein Ergebnis zurückgibt oder eine Ausnahme auslöst, wenn die Arbeit abgeschlossen ist, sollte OutcomeReceiver
nicht als Callback-Typ verwendet werden. Verwenden Sie stattdessen eine der anderen Optionen, die im folgenden Abschnitt aufgeführt sind.
Funktionsschnittstellen statt neuer SAM-Typen (Single Abstract Method) verwenden
Mit API-Ebene 24 wurden die Typen java.util.function.*
(Referenzdokumente) hinzugefügt. Sie bieten generische SAM-Schnittstellen wie Consumer<T>
, die sich als Callback-Lambdas verwenden lassen. In vielen Fällen bietet das Erstellen neuer SAM-Schnittstellen wenig Mehrwert in Bezug auf die Typensicherheit oder die Kommunikation von Absichten, während die Android API-Oberfläche unnötig erweitert wird.
Verwenden Sie stattdessen diese generischen Schnittstellen, anstatt neue zu erstellen:
Runnable
:() -> Unit
Supplier<R>
:() -> R
Consumer<T>
:(T) -> Unit
Function<T,R>
:(T) -> R
Predicate<T>
:(T) -> Boolean
- Viele weitere in den Referenzdokumenten verfügbar
Platzierung von SAM-Parametern
SAM-Parameter sollten an letzter Stelle stehen, um eine idiomatische Verwendung in Kotlin zu ermöglichen, auch wenn die Methode mit zusätzlichen Parametern überladen ist.
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
Docs
Dies sind Regeln für die öffentlichen API-Dokumente (Javadoc).
Alle öffentlichen APIs müssen dokumentiert sein
Alle öffentlichen APIs müssen eine ausreichende Dokumentation enthalten, die erklärt, wie ein Entwickler die API verwenden würde. Angenommen, der Entwickler hat die Methode über die automatische Vervollständigung oder beim Durchsuchen der API-Referenzdokumente gefunden und hat nur einen minimalen Kontext aus der benachbarten API-Oberfläche (z. B. dieselbe Klasse).
Methoden
Methodenparameter und Rückgabewerte müssen jeweils mit @param
- und @return
-Docs-Anmerkungen dokumentiert werden. Formatieren Sie den Javadoc-Text so, als würde er von „Diese Methode…“ eingeleitet.
Wenn eine Methode keine Parameter annimmt, keine besonderen Überlegungen erfordert und das zurückgibt, was der Methodenname verspricht, können Sie @return
auslassen und die Dokumentation so verfassen:
/**
* Returns the priority of the thread.
*/
@IntRange(from = 1, to = 10)
public int getPriority() { ... }
Verwenden Sie in Javadoc immer Links.
Dokumente sollten Links zu anderen Dokumenten für zugehörige Konstanten, Methoden und andere Elemente enthalten. Verwenden Sie Javadoc-Tags (z. B. @see
und {@link foo}
) und nicht nur reine Textwörter.
Für das folgende Quellbeispiel:
public static final int FOO = 0;
public static final int BAR = 1;
Verwenden Sie keinen Rohtext oder Code-Schriftschnitt:
/**
* 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) { ... }
Verwenden Sie stattdessen Links:
/**
* Sets value to one of {@link #FOO} or {@link #BAR}.
*
* @param value the value being set
*/
public void setValue(@ValueType int value) { ... }
Wenn Sie eine IntDef
-Anmerkung wie @ValueType
auf einen Parameter anwenden, wird automatisch eine Dokumentation mit den zulässigen Typen generiert. Weitere Informationen zu IntDef
finden Sie in der Anleitung zu Anmerkungen.
„update-api“ oder „docs“ als Ziel ausführen, wenn Javadoc hinzugefügt wird
Diese Regel ist besonders wichtig, wenn du @link
- oder @see
-Tags hinzufügst und dafür sorgen möchtest, dass die Ausgabe wie erwartet aussieht. ERROR-Ausgaben in Javadoc sind oft auf fehlerhafte Links zurückzuführen. Diese Prüfung wird entweder vom update-api
- oder vom docs
-Make-Ziel durchgeführt. Das docs
-Ziel ist jedoch möglicherweise schneller, wenn Sie nur Javadoc ändern und das update-api
-Ziel sonst nicht ausführen müssen.
{@code foo} verwenden, um Java-Werte zu unterscheiden
Umschließen Sie Java-Werte wie true
, false
und null
in {@code...}
, um sie vom Dokumentationstext zu unterscheiden.
Wenn Sie Dokumentationen in Kotlin-Quellen schreiben, können Sie Code wie bei Markdown in Backticks einschließen.
Zusammenfassungen für @param und @return sollten aus einem einzigen Satz bestehen.
Zusammenfassungen von Parametern und Rückgabewerten sollten mit einem Kleinbuchstaben beginnen und nur ein einziges Satzfragment enthalten. Wenn Sie zusätzliche Informationen haben, die über einen einzelnen Satz hinausgehen, verschieben Sie sie in den Javadoc-Textkörper der Methode:
/**
* @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.
*/
Sollte zu Folgendem geändert werden:
/**
* @param e element to be appended to this list, must be non-{@code null}
* @return {@code true} on success, {@code false} otherwise
*/
Anmerkungen in Google Docs müssen Erläuterungen enthalten
Beschreiben Sie, warum Anmerkungen @hide
und @removed
in der öffentlichen API ausgeblendet sind.
Fügen Sie eine Anleitung zum Ersetzen von API-Elementen hinzu, die mit der @deprecated
-Anmerkung gekennzeichnet sind.
Ausnahmen mit @throws dokumentieren
Wenn eine Methode eine geprüfte Ausnahme auslöst, z. B. IOException
, dokumentieren Sie die Ausnahme mit @throws
. Für Kotlin-APIs, die für Java-Clients bestimmt sind, müssen Funktionen mit @Throws
kommentiert werden.
Wenn eine Methode eine nicht geprüfte Ausnahme auslöst, die auf einen vermeidbaren Fehler hinweist, z. B. IllegalArgumentException
oder IllegalStateException
, dokumentieren Sie die Ausnahme und erläutern Sie, warum sie ausgelöst wird. Die geworfene Ausnahme sollte auch angeben, warum sie geworfen wurde.
Bestimmte Fälle von nicht geprüften Ausnahmen gelten als implizit und müssen nicht dokumentiert werden, z. B. NullPointerException
oder IllegalArgumentException
, wenn ein Argument nicht mit einer @IntDef
oder einer ähnlichen Anmerkung übereinstimmt, die den API-Vertrag in die Methodensignatur einbettet:
/**
* ...
* @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.");
}
// ...
Oder 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")
}
}
// ...
Wenn die Methode asynchronen Code aufruft, der möglicherweise Ausnahmen auslöst, überlegen Sie, wie der Entwickler von solchen Ausnahmen erfährt und darauf reagiert. Normalerweise bedeutet das, die Ausnahme an einen Rückruf weiterzuleiten und die in der empfangenden Methode geworfenen Ausnahmen zu dokumentieren. Asynchrone Ausnahmen sollten nur mit @throws
dokumentiert werden, wenn sie tatsächlich aus der kommentierten Methode neu geworfen werden.
Den ersten Satz von Dokumenten mit einem Punkt beenden
Das Doclava-Tool analysiert Dokumente auf einfache Weise und beendet das Synopsis-Dokument (den ersten Satz, der in der Kurzbeschreibung oben in den Kursdokumenten verwendet wird), sobald ein Punkt (.) gefolgt von einem Leerzeichen erkannt wird. Das führt zu zwei Problemen:
- Wenn ein kurzes Dokument nicht mit einem Punkt endet und dieses Mitglied übernommene Dokumente hat, die vom Tool erfasst werden, werden diese übernommenen Dokumente auch in der Zusammenfassung berücksichtigt. Sehen Sie sich beispielsweise
actionBarTabStyle
in denR.attr
-Dokumenten an. Dort ist die Beschreibung der Dimension in der Zusammenfassung enthalten. - Vermeiden Sie aus demselben Grund „z.B.“ im ersten Satz, da Doclava die Synopsis-Dokumente nach „g.“ beendet. Beispiel:
TEXT_ALIGNMENT_CENTER
inView.java
Metalava korrigiert diesen Fehler automatisch, indem nach dem Punkt ein nicht trennendes Leerzeichen eingefügt wird. Versuchen Sie jedoch, diesen Fehler gar nicht erst zu machen.
Dokumente so formatieren, dass sie in HTML gerendert werden
Javadoc wird in HTML gerendert. Formatieren Sie diese Dokumente daher entsprechend:
Für Zeilenumbrüche sollte ein explizites
<p>
-Tag verwendet werden. Fügen Sie kein abschließendes</p>
-Tag hinzu.Verwenden Sie keine ASCII-Zeichen, um Listen oder Tabellen zu rendern.
Für Listen sollten Sie
<ul>
bzw.<ol>
für unsortierte und sortierte Listen verwenden. Jedes Element sollte mit einem<li>
-Tag beginnen, aber kein schließendes</li>
-Tag haben. Nach dem letzten Element ist ein schließendes</ul>
- oder</ol>
-Tag erforderlich.Für Tabellen sollten
<table>
,<tr>
für Zeilen,<th>
für Überschriften und<td>
für Zellen verwendet werden. Alle Tabellen-Tags erfordern übereinstimmende End-Tags. Sie könnenclass="deprecated"
in jedem Tag verwenden, um die Einstellung zu kennzeichnen.Verwenden Sie
{@code foo}
, um einen Inline-Code-Schriftschnitt zu erstellen.Verwenden Sie
<pre>
, um Codeblöcke zu erstellen.Der gesamte Text in einem
<pre>
-Block wird vom Browser geparst. Seien Sie also vorsichtig mit Klammern<>
. Sie können sie mit den HTML-Entitäten<
und>
maskieren.Alternativ können Sie rohe Klammern
<>
in Ihrem Code-Snippet belassen, wenn Sie die betreffenden Abschnitte in{@code foo}
einschließen. Beispiel:<pre>{@code <manifest>}</pre>
Stilrichtlinien für die API-Referenz einhalten
Um für Klassenzusammenfassungen, Methodenbeschreibungen, Parameterbeschreibungen und andere Elemente einen einheitlichen Stil zu verwenden, folgen Sie den Empfehlungen in den offiziellen Java-Sprachrichtlinien unter How to Write Doc Comments for the Javadoc Tool (So schreiben Sie Doc-Kommentare für das Javadoc-Tool).
Android-Framework-spezifische Regeln
Diese Regeln beziehen sich auf APIs, Muster und Datenstrukturen, die für APIs und Verhaltensweisen spezifisch sind, die in das Android-Framework eingebunden sind (z. B. Bundle
oder Parcelable
).
Intent-Builder sollten das Muster „create*Intent()“ verwenden
Entwickler von Intents sollten Methoden mit dem Namen createFooIntent()
verwenden.
Verwenden Sie Bundles, anstatt neue allgemeine Datenstrukturen zu erstellen.
Erstellen Sie keine neuen allgemeinen Datenstrukturen, um beliebige Zuordnungen von Schlüsseln zu typisierten Werten darzustellen. Verwenden Sie stattdessen Bundle
.
Dies tritt in der Regel beim Erstellen von Plattform-APIs auf, die als Kommunikationskanäle zwischen nicht plattformgebundenen Apps und Diensten dienen. Dabei liest die Plattform die über den Kanal gesendeten Daten nicht und der API-Vertrag kann teilweise außerhalb der Plattform definiert sein (z. B. in einer Jetpack-Bibliothek).
Wenn die Plattform die Daten liest, sollten Sie Bundle
nicht verwenden, sondern eine stark typisierte Datenklasse bevorzugen.
Implementierungen von Parcelable müssen ein öffentliches CREATOR-Feld haben
Die Infragestellung von Parcelable erfolgt über CREATOR
, nicht über Rohkonstruktoren. Wenn eine Klasse Parcelable
implementiert, muss ihr CREATOR
-Feld auch eine öffentliche API sein und der Klassenkonstruktor, der ein Parcel
-Argument annimmt, muss privat sein.
CharSequence für UI-Strings verwenden
Wenn ein String in einer Benutzeroberfläche angezeigt wird, verwenden Sie CharSequence
, um Spannable
-Instanzen zuzulassen.
Wenn es sich nur um einen Schlüssel oder ein anderes Label oder einen anderen Wert handelt, der für Nutzer nicht sichtbar ist, ist String
in Ordnung.
Vermeiden Sie die Verwendung von Enums
IntDef
muss in allen Plattform-APIs anstelle von Enumerationen verwendet werden und sollte bei nicht gebündelten Bibliotheks-APIs in Betracht gezogen werden. Verwenden Sie Enumerationen nur, wenn Sie sicher sind, dass keine neuen Werte hinzugefügt werden.
Vorteile vonIntDef
:
- Ermöglicht das Hinzufügen von Werten im Zeitverlauf
- Kotlin-
when
-Anweisungen können bei der Laufzeit fehlschlagen, wenn sie aufgrund eines hinzugefügten Enum-Werts auf der Plattform nicht mehr vollständig sind.
- Kotlin-
- Bei der Laufzeit werden keine Klassen oder Objekte, sondern nur primitive Typen verwendet.
- Mit R8 oder Minimierung können Sie diese Kosten für nicht gebündelte Bibliotheks-APIs vermeiden. Diese Optimierung kann jedoch nicht auf Plattform-API-Klassen angewendet werden.
Vorteile von Enum
- Idiomatische Sprachfunktion von Java, Kotlin
- Aktiviert die Verwendung von Switch- und
when
-Anweisungen mit vollständiger Fallabdeckung.- Hinweis: Die Werte dürfen sich im Laufe der Zeit nicht ändern (siehe vorherige Liste).
- Klar umrissene und leicht auffindbare Namen
- Aktiviert die Überprüfung zur Kompilierungszeit.
- Beispiel: Eine
when
-Anweisung in Kotlin, die einen Wert zurückgibt
- Beispiel: Eine
- Eine funktionsfähige Klasse, die Schnittstellen implementieren, statische Hilfsfunktionen haben, Mitglieds- oder Erweiterungsmethoden und Felder freigeben kann.
Beachten Sie die Android-Paket-Beschichtungshierarchie
Die android.*
-Pakethierarchie hat eine implizite Reihenfolge, bei der Pakete auf niedrigerer Ebene nicht von Paketen auf höherer Ebene abhängen können.
Vermeiden Sie Verweise auf Google, andere Unternehmen und deren Produkte.
Die Android-Plattform ist ein Open-Source-Projekt und soll anbieterunabhängig sein. Die API sollte generisch sein und von Systemintegratoren oder Apps mit den erforderlichen Berechtigungen gleichermaßen verwendet werden können.
Implementierungen von Parcelable-Objekten sollten endgültig sein
Von der Plattform definierte Parcelable-Klassen werden immer von framework.jar
geladen. Daher ist es unzulässig, wenn eine App versucht, eine Parcelable
-Implementierung zu überschreiben.
Wenn die sendende App eine Parcelable
erweitert, hat die empfangende App nicht die benutzerdefinierte Implementierung des Absenders, mit der sie entpackt werden kann. Hinweis zur Abwärtskompatibilität: Wenn Ihre Klasse bisher nicht final war, aber keinen öffentlich zugänglichen Konstruktor hatte, können Sie sie trotzdem als final
kennzeichnen.
Methoden, die einen Systemprozess aufrufen, sollten RemoteException als RuntimeException wiedergeben.
RemoteException
wird in der Regel von der internen AIDL ausgelöst und gibt an, dass der Systemprozess beendet wurde oder die App versucht, zu viele Daten zu senden. In beiden Fällen sollte die öffentliche API als RuntimeException
neu gesendet werden, um zu verhindern, dass Apps Sicherheits- oder Richtlinienentscheidungen beibehalten.
Wenn Sie wissen, dass die andere Seite eines Binder
-Aufrufs der Systemprozess ist, ist dieser Boilerplate-Code die Best Practice:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Bestimmte Ausnahmen für API-Änderungen auslösen
Das Verhalten öffentlicher APIs kann sich zwischen API-Ebenen ändern und zu App-Abstürzen führen, z. B. wenn neue Sicherheitsrichtlinien erzwungen werden.
Wenn die API für eine zuvor gültige Anfrage eine Ausnahme auslösen muss, werfen Sie eine neue spezifische Ausnahme anstelle einer generischen Ausnahme. Beispiel: ExportedFlagRequired
anstelle von SecurityException
(ExportedFlagRequired
kann SecurityException
überlappen).
So können App-Entwickler und -Tools Änderungen am API-Verhalten erkennen.
Kopierkonstruktor anstelle von Klon implementieren
Die Verwendung der Java-Methode clone()
wird aufgrund des Fehlens von API-Verträgen durch die Klasse Object
und der Schwierigkeiten bei der Erweiterung von Klassen, die clone()
verwenden, dringend abgeraten. Verwenden Sie stattdessen einen Kopierkonstruktor, der ein Objekt desselben Typs annimmt.
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
Klassen, die für die Erstellung auf einen Builder angewiesen sind, sollten einen Builder-Kopienkonstruktor hinzufügen, um Änderungen an der Kopie zu ermöglichen.
public class Foo {
public static final class Builder {
/**
* Constructs a Foo builder using data from {@code other}.
*/
public Builder(Foo other)
ParcelFileDescriptor anstelle von FileDescriptor verwenden
Die Inhaberschaft des java.io.FileDescriptor
-Objekts ist nicht klar definiert, was zu schwer zu findenden Fehlern vom Typ „Verwendung nach dem Schließen“ führen kann. Stattdessen sollten APIs ParcelFileDescriptor
-Instanzen zurückgeben oder akzeptieren. Legacy-Code kann bei Bedarf mithilfe von dup() oder getFileDescriptor() zwischen PFD und FD konvertieren.
Vermeiden Sie die Verwendung von Zahlenwerten mit ungerader Größe.
Vermeiden Sie es, short
- oder byte
-Werte direkt zu verwenden, da sie häufig einschränken, wie Sie die API in Zukunft weiterentwickeln können.
BitSet vermeiden
java.util.BitSet
eignet sich hervorragend für die Implementierung, aber nicht für öffentliche APIs. Sie ist veränderlich, erfordert eine Zuweisung für häufige Methodenaufrufe und bietet keine semantische Bedeutung für die einzelnen Bits.
Verwenden Sie für Szenarien mit hoher Leistung int
oder long
mit @IntDef
. Für Szenarien mit geringer Leistung sollten Sie eine Set<EnumType>
in Betracht ziehen. Verwenden Sie byte[]
für Roh-Binärdaten.
Verwenden Sie vorzugsweise android.net.Uri.
android.net.Uri
ist die bevorzugte Kapselung für URIs in Android APIs.
Vermeiden Sie java.net.URI
, da es beim Parsen von URIs zu streng ist, und verwenden Sie niemals java.net.URL
, da die Definition von Gleichheit hier stark gestört ist.
Anmerkungen vom Typ @IntDef, @LongDef oder @StringDef ausblenden
Anmerkungen, die als @IntDef
, @LongDef
oder @StringDef
gekennzeichnet sind, geben eine Reihe gültiger Konstanten an, die an eine API übergeben werden können. Wenn sie jedoch selbst als APIs exportiert werden, fügt der Compiler die Konstanten ein und nur die (jetzt nutzlosen) Werte bleiben im API-Stub (für die Plattform) oder JAR (für Bibliotheken) der Anmerkung.
Daher müssen die Verwendungen dieser Anmerkungen auf der Plattform mit der @hide
-Anmerkung für Dokumente oder in Bibliotheken mit der @RestrictTo.Scope.LIBRARY)
-Codeanmerkung gekennzeichnet werden. In beiden Fällen müssen sie mit @Retention(RetentionPolicy.SOURCE)
gekennzeichnet werden, damit sie nicht in API-Stubs oder JARs erscheinen.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
Beim Erstellen der AARs für das Plattform-SDK und die Bibliothek werden die Anmerkungen von einem Tool extrahiert und separat von den kompilierten Quellen gebündelt. Android Studio liest dieses gebündelte Format und erzwingt die Typdefinitionen.
Keine neuen Schlüssel für Anbieter von Einstellungen hinzufügen
Neue Schlüssel von Settings.Global
, Settings.System
oder Settings.Secure
dürfen nicht freigegeben werden.
Fügen Sie stattdessen eine ordnungsgemäße Java-API für Getter und Setter in einer entsprechenden Klasse hinzu, in der Regel eine „Manager“-Klasse. Fügen Sie einen Listener-Mechanismus oder eine Übertragung hinzu, um Clients bei Bedarf über Änderungen zu benachrichtigen.
SettingsProvider
-Einstellungen haben im Vergleich zu Gettern/Settern eine Reihe von Problemen:
- Keine Typsicherheit.
- Es gibt keine einheitliche Möglichkeit, einen Standardwert anzugeben.
- Es gibt keine Möglichkeit, Berechtigungen anzupassen.
- So ist es beispielsweise nicht möglich, Ihre Einstellung mit einer benutzerdefinierten Berechtigung zu schützen.
- Es gibt keine Möglichkeit, benutzerdefinierte Logik richtig hinzuzufügen.
- Es ist beispielsweise nicht möglich, den Wert von Einstellung A in Abhängigkeit vom Wert von Einstellung B zu ändern.
Beispiel: Settings.Secure.LOCATION_MODE
gibt es schon lange, aber das Standortteam hat es zugunsten einer richtigen Java API LocationManager.isLocationEnabled()
und der MODE_CHANGED_ACTION
-Übertragung eingestellt. Das Team hat dadurch viel mehr Flexibilität gewonnen und die Semantik der APIs ist jetzt viel klarer.
Activity und AsyncTask nicht erweitern
AsyncTask
ist ein Implementierungsdetail. Stellen Sie stattdessen einen Listener oder in androidx eine ListenableFuture
API bereit.
Activity
Unterklassen können nicht erstellt werden. Wenn Sie die Aktivität für Ihre Funktion verlängern, ist sie nicht mit anderen Funktionen kompatibel, bei denen Nutzer dasselbe tun müssen. Verwenden Sie stattdessen Tools wie LifecycleObserver, um die Zusammensetzung zu steuern.
getUser() des Kontexts verwenden
Für Klassen, die an eine Context
gebunden sind, z. B. Elemente, die von Context.getSystemService()
zurückgegeben werden, sollte der an die Context
gebundene Nutzer verwendet werden, anstatt Mitglieder zu präsentieren, die auf bestimmte Nutzer ausgerichtet sind.
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);
}
}
Ausnahme: Eine Methode kann ein Nutzerargument akzeptieren, wenn sie Werte akzeptiert, die keinen einzelnen Nutzer repräsentieren, z. B. UserHandle.ALL
.
UserHandle anstelle von Ganzzahlen verwenden
UserHandle
wird bevorzugt, um für Typsicherheit zu sorgen und Verwechslungen von Nutzer-IDs mit uids zu vermeiden.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Wenn es nicht anders geht, muss ein int
, das eine Nutzer-ID darstellt, mit @UserIdInt
gekennzeichnet werden.
Foobar getFoobarForUser(@UserIdInt int user);
Verwenden Sie Listener oder Callbacks anstelle von Intents zur Übertragung
Broadcast-Intents sind sehr leistungsfähig, haben aber zu neuen Verhaltensweisen geführt, die sich negativ auf die Systemintegrität auswirken können. Daher sollten neue Broadcast-Intents mit Bedacht hinzugefügt werden.
Hier sind einige konkrete Bedenken, die uns dazu veranlassen, die Einführung neuer Intents für die Übertragung zu erschweren:
Wenn Übertragungen ohne das Flag
FLAG_RECEIVER_REGISTERED_ONLY
gesendet werden, werden alle Apps erzwungen gestartet, die noch nicht ausgeführt werden. Das kann zwar manchmal beabsichtigt sein, aber auch dazu führen, dass Dutzende von Apps gleichzeitig gestartet werden, was sich negativ auf die Systemintegrität auswirken kann. Wir empfehlen, alternative Strategien wieJobScheduler
zu verwenden, um besser zu koordinieren, wann verschiedene Voraussetzungen erfüllt sind.Beim Senden von Übertragungen können die an Apps gesendeten Inhalte nur eingeschränkt gefiltert oder angepasst werden. Dies erschwert oder verhindert es, auf zukünftige Datenschutzbedenken zu reagieren oder Verhaltensänderungen basierend auf dem Ziel-SDK der empfangenden App vorzunehmen.
Da Broadcast-Warteschlangen eine gemeinsame Ressource sind, können sie überlastet werden und es kann zu Verzögerungen bei der Übermittlung des Ereignisses kommen. Wir haben mehrere Broadcast-Warteschlangen in der Praxis beobachtet, die eine End-to-End-Latenz von 10 Minuten oder länger haben.
Aus diesen Gründen empfehlen wir, für neue Funktionen Listener oder Callbacks oder andere Funktionen wie JobScheduler
anstelle von Broadcast-Intents zu verwenden.
Wenn Broadcast-Intents weiterhin das ideale Design sind, sollten Sie die folgenden Best Practices beachten:
- Verwenden Sie nach Möglichkeit
Intent.FLAG_RECEIVER_REGISTERED_ONLY
, um Ihren Stream auf bereits laufende Apps zu beschränken.ACTION_SCREEN_ON
verwendet dieses Design beispielsweise, um das Aktivieren von Apps zu vermeiden. - Verwenden Sie nach Möglichkeit
Intent.setPackage()
oderIntent.setComponent()
, um die Auslieferung auf eine bestimmte App auszurichten. InACTION_MEDIA_BUTTON
wird dieses Design beispielsweise verwendet, um den Fokus auf die Wiedergabesteuerung der aktuellen App zu legen. - Definieren Sie Ihre Übertragung nach Möglichkeit als
<protected-broadcast>
, um zu verhindern, dass schädliche Apps sich als Betriebssystem ausgeben.
Intents in systemgebundenen Entwicklerdiensten
Dienste, die vom Entwickler erweitert und vom System gebunden werden sollen, z. B. abstrakte Dienste wie NotificationListenerService
, können auf eine Intent
-Aktion des Systems reagieren. Solche Dienste sollten die folgenden Kriterien erfüllen:
- Definieren Sie in der Klasse eine
SERVICE_INTERFACE
-Stringkonstante mit dem voll qualifizierten Klassennamen des Dienstes. Diese Konstante muss mit@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
annotiert sein. - Erläutern Sie in der Klasse, dass Entwickler ihrer
AndroidManifest.xml
eine<intent-filter>
hinzufügen müssen, um Intents von der Plattform zu erhalten. - Wir empfehlen Ihnen dringend, eine Berechtigung auf Systemebene hinzuzufügen, um zu verhindern, dass schädliche Apps
Intent
s an Entwicklerdienste senden.
Kotlin-Java-Interoperabilität
Eine vollständige Liste der Richtlinien finden Sie im offiziellen Android-Leitfaden zur Kotlin-Java-Interoperabilität. Ausgewählte Richtlinien wurden in diesen Leitfaden kopiert, um die Auffindbarkeit zu verbessern.
API-Sichtbarkeit
Einige Kotlin-APIs wie suspend fun
s sind nicht für Java-Entwickler gedacht. Versuchen Sie jedoch nicht, die sprachspezifische Sichtbarkeit mit @JvmSynthetic
zu steuern, da dies Auswirkungen auf die Darstellung der API in Debuggern hat, was das Debuggen erschwert.
Weitere Informationen finden Sie im Leitfaden zur Kotlin-Java-Interoperabilität oder im Leitfaden für asynchrone Funktionen.
Companion-Objekte
In Kotlin werden statische Mitglieder mit companion object
freigegeben. In einigen Fällen werden diese von Java in einer inneren Klasse namens Companion
und nicht in der enthaltenden Klasse angezeigt. Companion
-Klassen werden in API-Textdateien möglicherweise als leere Klassen angezeigt. Das ist normal.
Um die Kompatibilität mit Java zu maximieren, sollten Sie die nicht konstanten Felder von Companion-Objekten mit @JvmField
und die öffentlichen Funktionen mit @JvmStatic
annotieren, um sie direkt in der enthaltenden Klasse verfügbar zu machen.
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
Entwicklung der Android-Plattform-APIs
In diesem Abschnitt werden Richtlinien beschrieben, welche Arten von Änderungen Sie an vorhandenen Android APIs vornehmen können und wie Sie diese Änderungen implementieren sollten, um die Kompatibilität mit vorhandenen Apps und Codebases zu maximieren.
Nicht abwärtskompatible Änderungen
Vermeiden Sie Änderungen an den Oberflächen der finalisierten öffentlichen APIs, die zu Binärfehlern führen. Diese Arten von Änderungen führen in der Regel zu Fehlern, wenn make update-api
ausgeführt wird. Es kann jedoch Grenzfälle geben, die von der API-Prüfung von Metalava nicht erkannt werden. Im Zweifelsfall finden Sie im Leitfaden Evolving Java-based APIs der Eclipse Foundation eine ausführliche Erklärung dazu, welche Arten von API-Änderungen in Java kompatibel sind. Änderungen an versteckten APIs (z. B. System-APIs), die zu Binärinkompatibilitäten führen, sollten dem Zyklus „Verwerfen/Ersetzen“ folgen.
Nicht abwärtskompatible Änderungen
Wir raten von Änderungen ab, die die Funktionsweise der Quelle beeinträchtigen, auch wenn sie nicht zu einer Binäränderung führen. Ein Beispiel für eine binärkompatible, aber quellenbrüchende Änderung ist das Hinzufügen eines Generics zu einer vorhandenen Klasse. Dies ist binärkompatibel, kann aber aufgrund von Vererbung oder mehrdeutigen Verweisfehlern zu Kompilierungsfehlern führen.
Änderungen, die die Quelle brechen, führen beim Ausführen von make update-api
nicht zu Fehlern. Sie müssen daher die Auswirkungen von Änderungen an vorhandenen API-Signaturen genau kennen.
In einigen Fällen sind Änderungen, die die Quellcode-Kompatibilität beeinträchtigen, erforderlich, um die Nutzerfreundlichkeit oder Coderichtigkeit zu verbessern. Wenn Sie Java-Quellen beispielsweise Anmerkungen zur Null-Zulässigkeit hinzufügen, wird die Interoperabilität mit Kotlin-Code verbessert und die Wahrscheinlichkeit von Fehlern verringert. Dies erfordert jedoch häufig Änderungen – manchmal erhebliche Änderungen – am Quellcode.
Änderungen an privaten APIs
Sie können APIs, die mit @TestApi
gekennzeichnet sind, jederzeit ändern.
Sie müssen APIs, die mit @SystemApi
annotiert sind, drei Jahre lang aufbewahren. Sie müssen eine System-API gemäß dem folgenden Zeitplan entfernen oder umstrukturieren:
- API y – hinzugefügt
- API y+1 – Einstellung
- Markieren Sie den Code mit
@Deprecated
. - Fügen Sie Ersetzungen hinzu und verlinken Sie im Javadoc für den veralteten Code mithilfe der
@deprecated
-Anmerkung auf den Ersatz. - Melden Sie während des Entwicklungszyklus Fehler für interne Nutzer und teilen Sie ihnen mit, dass die API eingestellt wird. So lässt sich prüfen, ob die Ersatz-APIs geeignet sind.
- Markieren Sie den Code mit
- API y+2 – Vorläufige Entfernung
- Markieren Sie den Code mit
@removed
. - Optional: throw oder no-op für Apps, die auf die aktuelle SDK-Ebene für die Version ausgerichtet sind.
- Markieren Sie den Code mit
- API y+3 – Forced removal
- Entfernen Sie den Code vollständig aus dem Stammbaum.
Einstellung
Wir betrachten die Einstellung als API-Änderung und sie kann in einer Hauptversion (z. B. einer Buchstabenversion) erfolgen. Verwenden Sie die @Deprecated
-Quellenannotierung und die @deprecated
<summary>
-Dokumentannotierung zusammen, wenn Sie APIs einstellen. Ihre Zusammenfassung muss eine Migrationsstrategie enthalten. Diese Strategie kann einen Link zu einer Ersatz-API enthalten oder erklären, warum Sie die API nicht verwenden sollten:
/**
* 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)
Sie müssen auch in XML definierte und in Java freigegebene APIs mit einer Zusammenfassung aufheben, einschließlich Attributen und stilbaren Eigenschaften, die in der Klasse android.R
freigegeben sind:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
Wann sollte eine API eingestellt werden?
Eine Einstellung ist am nützlichsten, um die Verwendung einer API in neuem Code zu verhindern.
Außerdem müssen Sie APIs als @deprecated
kennzeichnen, bevor sie @removed
werden. Das ist jedoch kein starkes Argument für Entwickler, von einer API zu migrieren, die sie bereits verwenden.
Berücksichtigen Sie vor der Einstellung einer API die Auswirkungen auf Entwickler. Die Auswirkungen der Einstellung einer API sind:
javac
gibt während der Kompilierung eine Warnung aus.- Warnungen zur Einstellung können nicht global unterdrückt oder als Baseline festgelegt werden. Entwickler, die
-Werror
verwenden, müssen daher jede Verwendung einer eingestellten API einzeln korrigieren oder unterdrücken, bevor sie ihre kompilierte SDK-Version aktualisieren können. - Warnungen zur Einstellung bei Importen von eingestellten Klassen können nicht unterdrückt werden. Daher müssen Entwickler den vollständig qualifizierten Klassennamen für jede Verwendung einer eingestellten Klasse einfügen, bevor sie ihre kompilierte SDK-Version aktualisieren können.
- Warnungen zur Einstellung können nicht global unterdrückt oder als Baseline festgelegt werden. Entwickler, die
- In der Dokumentation zu
d.android.com
wird eine entsprechende Ankündigung angezeigt. - In IDEs wie Android Studio wird auf der Website zur API-Nutzung eine Warnung angezeigt.
- IDEs können die API im Autocomplete-Verfahren herabstufen oder ausblenden.
Daher kann die Einstellung einer API Entwickler, die am meisten auf die Codequalität achten (d. h. diejenigen, die -Werror
verwenden), davon abhalten, neue SDKs zu verwenden.
Entwickler, die sich nicht um Warnungen in ihrem vorhandenen Code kümmern, ignorieren die Einstellungsmeldungen wahrscheinlich vollständig.
Ein SDK, das eine große Anzahl von eingestellten Funktionen einführt, verschlimmert beide Fälle.
Aus diesem Grund empfehlen wir, APIs nur in folgenden Fällen einzustellen:
- Wir planen, die API in einer zukünftigen Version
@remove
. - Die API-Nutzung führt zu einem falschen oder nicht definierten Verhalten, das wir nicht beheben können, ohne die Kompatibilität zu brechen.
Wenn Sie eine API einstellen und durch eine neue ersetzen, empfehlen wir Ihnen dringend, einer Jetpack-Bibliothek wie androidx.core
eine entsprechende Kompatibilitäts-API hinzuzufügen, um die Unterstützung sowohl alter als auch neuer Geräte zu vereinfachen.
Wir empfehlen nicht, APIs einzustellen, die in aktuellen und zukünftigen Releases wie vorgesehen funktionieren:
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
Eine Einstellung ist in folgenden Fällen angebracht:
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
Änderungen an veralteten APIs
Sie müssen das Verhalten eingestellter APIs beibehalten. Das bedeutet, dass die Testimplementierungen unverändert bleiben müssen und die Tests auch nach der Einstellung der API bestanden werden müssen. Wenn für die API keine Tests vorhanden sind, sollten Sie diese hinzufügen.
Erweitern Sie nicht eingestellte API-Oberflächen in zukünftigen Releases. Sie können einer vorhandenen eingestellten API Lint-Korrektheitsanmerkungen (z. B. @Nullable
) hinzufügen, aber keine neuen APIs.
Fügen Sie neue APIs nicht als eingestellt hinzu. Wenn APIs hinzugefügt und anschließend innerhalb eines Prerelease-Zyklus eingestellt wurden (d. h. sie werden anfangs als eingestellt in der öffentlichen API-Oberfläche angezeigt), müssen Sie sie entfernen, bevor Sie die API fertigstellen.
Weiche Entfernung
Das vorläufige Entfernen ist eine quellenbrechende Änderung und sollte in öffentlichen APIs vermieden werden, es sei denn, der API-Rat genehmigt sie ausdrücklich.
Bei System-APIs müssen Sie die API für die Dauer einer Hauptversion veraltigen, bevor sie schrittweise eingestellt wird. Entfernen Sie alle Verweise auf die APIs aus den Dokumenten und verwenden Sie die Anmerkung @removed <summary>
, wenn Sie APIs weich entfernen. Ihre Zusammenfassung muss den Grund für die Entfernung enthalten und kann eine Migrationsstrategie umfassen, wie wir unter Einstellung erläutert haben.
Das Verhalten von APIs, die nur teilweise entfernt wurden, kann unverändert beibehalten werden, muss aber vor allem beibehalten werden, damit bestehende Aufrufer beim Aufruf der API nicht abstürzen. In einigen Fällen kann das bedeuten, dass das Verhalten beibehalten wird.
Die Testabdeckung muss beibehalten werden, aber der Inhalt der Tests muss möglicherweise geändert werden, um Verhaltensänderungen zu berücksichtigen. Bei Tests muss weiterhin geprüft werden, ob vorhandene Aufrufer zur Laufzeit nicht abstürzen. Sie können das Verhalten von APIs, die nur teilweise entfernt wurden, beibehalten. Wichtiger ist jedoch, dass Sie es so beibehalten, dass bestehende Aufrufer beim Aufruf der API nicht abstürzen. In einigen Fällen kann das bedeuten, dass das Verhalten beibehalten wird.
Sie müssen die Testabdeckung beibehalten, aber der Inhalt der Tests muss möglicherweise geändert werden, um Verhaltensänderungen zu berücksichtigen. Bei Tests muss weiterhin geprüft werden, ob vorhandene Aufrufer zur Laufzeit nicht abstürzen.
Auf technischer Ebene entfernen wir die API mithilfe der Javadoc-Anmerkung @remove
aus dem SDK-Stub-JAR und dem Compilezeit-Classpath. Sie ist jedoch weiterhin im Laufzeit-Classpath vorhanden, ähnlich wie bei @hide
APIs:
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
Für App-Entwickler wird die API nicht mehr im automatischen Vervollständigungsvorschlag angezeigt und Quellcode, der auf die API verweist, wird nicht kompiliert, wenn die compileSdk
mit dem SDK übereinstimmt oder höher ist, aus dem die API entfernt wurde. Der Quellcode wird jedoch weiterhin erfolgreich mit früheren SDKs kompiliert und Binärdateien, die auf die API verweisen, funktionieren weiterhin.
Bestimmte API-Kategorien dürfen nicht vorläufig entfernt werden. Bestimmte API-Kategorien dürfen nicht per Soft-Deaktivierung entfernt werden.
Abstrakte Methoden
Abstrakte Methoden von Klassen, die Entwickler erweitern könnten, dürfen nicht vorrübergehend entfernt werden. Dadurch ist es Entwicklern nicht möglich, die Klasse auf alle SDK-Ebenen auszuweiten.
In seltenen Fällen, in denen es niemals und auch in Zukunft nicht möglich sein wird, dass Entwickler eine Klasse erweitern, können Sie abstrakte Methoden trotzdem „weich“ entfernen.
Hard Removal
Das vollständige Entfernen ist eine binäre Änderung und sollte in öffentlichen APIs niemals vorkommen.
Nicht empfohlene Anmerkung
Mit der Anmerkung @Discouraged
geben wir an, dass wir in den meisten Fällen (> 95%) keine API empfehlen. APIs, die nicht mehr empfohlen werden, unterscheiden sich von veralteten APIs dadurch, dass es einen eng gefassten kritischen Anwendungsfall gibt, der eine Einstellung verhindert. Wenn Sie eine API als nicht empfohlen kennzeichnen, müssen Sie eine Erklärung und eine alternative Lösung angeben:
@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);
}
Sie sollten keine neuen APIs hinzufügen, die als nicht empfohlen gekennzeichnet sind.
Änderungen am Verhalten vorhandener APIs
In einigen Fällen kann es sinnvoll sein, das Implementierungsverhalten einer vorhandenen API zu ändern. In Android 7.0 haben wir beispielsweise DropBoxManager
verbessert, um Entwicklern klar mitzuteilen, wenn sie versucht haben, Ereignisse zu posten, die zu groß sind, um über Binder
gesendet zu werden.
Um Probleme mit bestehenden Apps zu vermeiden, empfehlen wir jedoch dringend, für ältere Apps ein sicheres Verhalten beizubehalten. Bisher haben wir diese Verhaltensänderungen basierend auf der ApplicationInfo.targetSdkVersion
der App überwacht. Vor Kurzem haben wir jedoch die Verwendung des App Compatibility Frameworks eingeführt. Hier ist ein Beispiel dafür, wie Sie mit diesem neuen Framework eine Verhaltensänderung implementieren:
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
}
}
}
Mit diesem App Compatibility Framework-Design können Entwickler bestimmte Verhaltensänderungen während der Vorab- und Betaversionen vorübergehend deaktivieren, um ihre Apps zu debuggen, anstatt sich gleichzeitig an Dutzende von Verhaltensänderungen anpassen zu müssen.
Vorwärtskompatibilität
Die Zukunftssicherheit ist eine Designeigenschaft, die es einem System ermöglicht, Eingaben zu akzeptieren, die für eine spätere Version des Systems bestimmt sind. Beim API-Design müssen Sie sowohl auf das ursprüngliche Design als auch auf zukünftige Änderungen achten, da Entwickler erwarten, dass Code einmal geschrieben, einmal getestet und überall problemlos ausgeführt wird.
Die folgenden Ursachen führen zu den häufigsten Problemen mit der zukünftigen Kompatibilität unter Android:
- Einem Set (z. B.
@IntDef
oderenum
), das zuvor als vollständig angenommen wurde, neue Konstanten hinzufügen (z. B. wennswitch
einedefault
hat, die eine Ausnahme auslöst). - Unterstützung für eine Funktion hinzufügen, die nicht direkt in der API-Oberfläche erfasst wird (z. B. Unterstützung für die Zuweisung von Ressourcen vom Typ
ColorStateList
in XML, wo zuvor nur<color>
-Ressourcen unterstützt wurden). - Lockerung der Einschränkungen für Laufzeitprüfungen, z. B. Entfernung einer
requireNotNull()
-Prüfung, die in niedrigeren Versionen vorhanden war.
In all diesen Fällen stellen Entwickler erst zur Laufzeit fest, dass etwas nicht stimmt. Schlimmer noch: Sie könnten es durch Absturzberichte von älteren Geräten im Einsatz erfahren.
Außerdem sind diese Fälle alle technisch gültige API-Änderungen. Sie beeinträchtigen nicht die Binär- oder Quellkompatibilität und API lint erkennt diese Probleme nicht.
Daher müssen API-Designer beim Ändern bestehender Klassen sehr vorsichtig vorgehen. Stellen Sie sich die Frage: „Wird diese Änderung dazu führen, dass Code, der nur mit der neuesten Version der Plattform geschrieben und getestet wurde, bei niedrigeren Versionen fehlschlägt?“
XML-Schemas
Wenn ein XML-Schema als stabile Schnittstelle zwischen Komponenten dient, muss dieses Schema explizit angegeben und ähnlich wie andere Android-APIs abwärtskompatibel weiterentwickelt werden. Beispielsweise muss die Struktur von XML-Elementen und ‑Attributen ähnlich wie bei Methoden und Variablen auf anderen Android API-Oberflächen beibehalten werden.
Einstellung von XML
Wenn Sie ein XML-Element oder -Attribut einstellen möchten, können Sie die Markierung xs:annotation
hinzufügen. Sie müssen jedoch alle vorhandenen XML-Dateien weiterhin unterstützen, indem Sie dem üblichen @SystemApi
-Entwicklungszyklus folgen.
<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>
Elementtypen müssen beibehalten werden
Schemas unterstützen die Elemente sequence
, choice
und all
als untergeordnete Elemente des Elements complexType
. Diese untergeordneten Elemente unterscheiden sich jedoch in Anzahl und Reihenfolge ihrer untergeordneten Elemente. Eine Änderung eines vorhandenen Typs wäre daher nicht kompatibel.
Wenn Sie einen vorhandenen Typ ändern möchten, sollten Sie den alten Typ verwerfen und einen neuen Typ einführen, um ihn zu ersetzen.
<!-- 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-spezifische Muster
Mainline ist ein Projekt, mit dem Subsysteme („Mainline-Module“) des Android-Betriebssystems einzeln aktualisiert werden können, anstatt das gesamte System-Image zu aktualisieren.
Mainline-Module müssen von der Kernplattform getrennt werden. Das bedeutet, dass alle Interaktionen zwischen den einzelnen Modulen und dem Rest der Welt über formale (öffentliche oder systemspezifische) APIs erfolgen müssen.
Es gibt bestimmte Designmuster, die Mainline-Module einhalten sollten. In diesem Abschnitt werden sie beschrieben.
Das Muster <Module>FrameworkInitializer
Wenn ein Hauptmodul @SystemService
-Klassen (z. B. JobScheduler
) bereitstellen muss, verwenden Sie das folgende Muster:
Stellen Sie eine
<YourModule>FrameworkInitializer
-Klasse aus Ihrem Modul bereit. Diese Klasse muss sich in$BOOTCLASSPATH
befinden. Beispiel: StatsFrameworkInitializerMarkieren Sie sie mit
@SystemApi(client = MODULE_LIBRARIES)
.Fügen Sie eine
public static void registerServiceWrappers()
-Methode hinzu.Verwenden Sie
SystemServiceRegistry.registerContextAwareService()
, um eine Service Manager-Klasse zu registrieren, wenn eine Referenz auf eineContext
erforderlich ist.Verwenden Sie
SystemServiceRegistry.registerStaticService()
, um eine Dienstmanagerklasse zu registrieren, wenn keine Referenz auf eineContext
erforderlich ist.Rufen Sie die Methode
registerServiceWrappers()
über den statischen Initializer vonSystemServiceRegistry
auf.
Das Muster <Module>ServiceManager
Normalerweise wird ServiceManager
verwendet, um Systemdienst-Binderobjekte zu registrieren oder Verweise darauf abzurufen. Mainline-Module können es jedoch nicht verwenden, da es ausgeblendet ist. Diese Klasse ist ausgeblendet, da Mainline-Module keine Systemdienst-Binder-Objekte registrieren oder darauf verweisen dürfen, die von der statischen Plattform oder anderen Modulen freigegeben werden.
Mainline-Module können stattdessen das folgende Muster verwenden, um Verweise auf Binderdienste zu registrieren und abzurufen, die im Modul implementiert sind.
Erstellen Sie eine
<YourModule>ServiceManager
-Klasse, die dem Design von TelephonyServiceManager folgt.Stellen Sie die Klasse als
@SystemApi
bereit. Wenn Sie nur über$BOOTCLASSPATH
- oder Systemserverklassen darauf zugreifen müssen, können Sie@SystemApi(client = MODULE_LIBRARIES)
verwenden. Andernfalls ist auch@SystemApi(client = PRIVILEGED_APPS)
möglich.Dieser Kurs würde Folgendes umfassen:
- Ein versteckter Konstruktor, der nur vom statischen Plattformcode instanziiert werden kann.
- Öffentliche Gettermethoden, die eine
ServiceRegisterer
-Instanz für einen bestimmten Namen zurückgeben. Wenn Sie ein Binder-Objekt haben, benötigen Sie eine Get-Methode. Wenn Sie zwei haben, benötigen Sie zwei Getter. - Instanzieren Sie diese Klasse in
ActivityThread.initializeMainlineModules()
und übergeben Sie sie an eine statische Methode, die von Ihrem Modul freigegeben wird. Normalerweise fügen Sie IhrerFrameworkInitializer
-Klasse eine statische@SystemApi(client = MODULE_LIBRARIES)
-API hinzu, die sie übernimmt.
Dieses Muster würde anderen Mainline-Modulen den Zugriff auf diese APIs verwehren, da andere Module keine Instanz von <YourModule>ServiceManager
abrufen können, obwohl die get()
- und register()
-APIs für sie sichtbar sind.
So erhält die Telefonie eine Referenz auf den Telefoniedienst: Link zur Codesuche.
Wenn Sie ein Dienstbinderobjekt in nativem Code implementieren, verwenden Sie die AServiceManager
nativen APIs.
Diese APIs entsprechen den ServiceManager
Java APIs, die nativen APIs sind jedoch direkt für Mainline-Module verfügbar. Verwenden Sie sie nicht, um Bindungsobjekte zu registrieren oder darauf zu verweisen, die nicht zu Ihrem Modul gehören. Wenn Sie ein Binder-Objekt aus nativen Code freigeben, ist für Ihre <YourModule>ServiceManager.ServiceRegisterer
keine register()
-Methode erforderlich.
Berechtigungsdefinitionen in Mainline-Modulen
Mainline-Module, die APKs enthalten, können (benutzerdefinierte) Berechtigungen in ihrer APK-AndroidManifest.xml
auf die gleiche Weise wie ein reguläres APK definieren.
Wenn die definierte Berechtigung nur intern innerhalb eines Moduls verwendet wird, sollte der Name der Berechtigung mit dem Namen des APK-Pakets vorangestellt werden, z. B.:
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
Wenn die definierte Berechtigung als Teil einer aktualisierbaren Plattform-API für andere Apps bereitgestellt werden soll, muss der Berechtigungsname mit „android.permission.“ beginnen. (wie bei jeder statischen Plattformberechtigung) und den Namen des Modulpakets, um anzuzeigen, dass es sich um eine Plattform-API aus einem Modul handelt, und gleichzeitig Namenskonflikte zu vermeiden, z. B.:
<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" />
Das Modul kann diesen Berechtigungsnamen dann als API-Konstante in seiner API-Oberfläche verfügbar machen, z. B. HealthPermissions.READ_ACTIVE_CALORIES_BURNED
.