These rules are about APIs, patterns, and data structures that are specific to APIs and functionality built into the Android framework (Bundle
s, Parcelable
s, etc.).
create*Intent()
patternCreators for intents should use methods named createFooIntent()
.
Bundle
s instead of creating new general-purpose data structuresInstead of creating a new type/class to hold various args or various types, consider simply using a Bundle
instead.
CREATOR
fieldParcelable inflation is exposed through CREATOR, not raw constructors. If a class implements Parcelable
, then its CREATOR
field must also public API and the class constructor taking a Parcel
argument must be private.
CharSequence
for UI stringsWhen a string will be presented in a user interface, use CharSequence
to allow for Spannable
s.
If it’s just a key or some other non-user-visible label or value, String
is fine.
IntDefs must be used over enum
s in all platform APIs, and should be strongly considered in unbundled, library APIs. Only use enums when you are certain new values will not be added.
Benefits ofIntDef
when
statements can fail at runtime if they become no-longer-exhaustive due to an added enum value in platform.Benefits of Enum
when
statement usagewhen
statement in kotlin that returns a valueThe android.*
package hierarchy has an implicit ordering, where lower-level packages cannot depend on higher-level packages.
The Android platform is an open-source project and aims to be vendor neutral. The API should be generic and equally usable by system integrators or applications with the requisite permissions.
final
Parcelable classes defined by the platform are always loaded from framework.jar
, so it’s invalid for an app to try overriding a Parcelable
implementation.
If the sending app extends a Parcelable
, the receiving app won’t have the sender’s custom implementation to unpack with. Note about backward compatibility: if your class historically wasn’t final, but didn’t have a publicly available constructor, you still can mark it final
.
RemoteException
as RuntimeException
RemoteException
is typically thrown by internal AIDL, and indicates that the system process has died, or the app is trying to send too much data. In both cases, public API should rethrow as a RuntimeException
to ensure that apps don’t accidentally persist security or policy decisions.
If you know the other side of a Binder
call is the system process, this simple boilerplate code is the best-practice:
try { ... } catch (RemoteException e) { throw e.rethrowFromSystemServer(); }
Use of the Java clone()
method is strongly discouraged due to the lack of API guarantees provided by the Object
class and difficulties inherent in extending classes that use clone()
. Instead, use a copy constructor that takes an object of the same type.
/** * Constructs a shallow copy of {@code other}. */ public Foo(Foo other)
Classes that rely on a Builder for construction should consider adding a Builder copy constructor to allow modifications to the copy.
public class Foo { public static final class Builder { /** * Constructs a Foo builder using data from {@code other}. */ public Builder(Foo other)
ParcelFileDescriptor
over FileDescriptor
.The java.io.FileDescriptor
object has a poor definition of ownership, which can result in obscure use-after-close bugs. Instead, APIs should return or accept ParcelFileDescriptor
instances. Legacy code can convert between PFD and FD if needed using dup() or getFileDescriptor().
Avoid using short
or byte
values directly, since they often limit how you might be able to evolve the API in the future.
java.util.BitSet
is great for implementation but not for public API. It's mutable, requires an allocation for high-frequency method calls, and does not provide semantic meaning for what each bit represents.
For high-performance scenarios, use an int
or long
with @IntDef
. For low-performance scenarios, consider a Set<EnumType>
. For raw binary data, use byte[]
.
android.net.Uri
.android.net.Uri
is the preferred encapsulation for URIs in Android APIs.
Avoid java.net.URI
, because it is overly strict in parsing URIs, and never use java.net.URL
, because its definition of equality is severely broken.
@IntDef
, @LongDef
or @StringDef
Annotations marked as @IntDef
, @LongDef
or @StringDef
denote a set of valid constants that can be passed to an API. However, when they are exported as APIs themselves, the compiler inlines the constants and only the (now useless) values remain in the annotation's API stub (for the platform) or JAR (for libraries).
As such, usages of these annotations must be marked @hide
in the platform or @hide
and RestrictTo.Scope.LIBRARY)
in libraries. They must be marked @Retention(RetentionPolicy.SOURCE)
in both cases to ensure they do not appear in API stubs or JARs.
/** @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) @Retention(RetentionPolicy.SOURCE) @IntDef({ STREAM_TYPE_FULL_IMAGE_DATA, STREAM_TYPE_EXIF_DATA_ONLY, }) public @interface ExifStreamType {}
When building the platform SDK and library AARs, a tool extracts the annotations and bundles them separately from the compiled sources. Android Studio reads this bundled format and enforces the type definitions.
Do not expose new keys from Settings.Global
, Settings.System
, and/or Settings.Secure
.
Instead, add a proper getter and setter Java API in a relevant class, which is typically a “manager” class. Add a listener mechanism or a broadcast to notify clients of changes as needed.
SettingsProvider
settings have a number of problems compared to getters/setters:
Example: Settings.Secure.LOCATION_MODE
has existed for a long time, but the location team has deprecated it for a proper Java API LocationManager.isLocationEnabled()
and the MODE_CHANGED_ACTION
broadcast, which gave the team a lot more flexibility, and the semantics of the APIs are a lot clearer now.
Activity
and AsyncTask
AsyncTask
is an implementation detail. Instead, expose a listener or, in androidx, a ListenableFuture
API instead.
Activity
subclasses are impossible to compose. Extending activity for your feature makes it incompatible with other features that require users to do the same. Instead, rely on composition by using tools such as LifecycleObserver.
Context
's getUser()
Classes bound to a Context
, such as anything returned from Context.getSystemService()
should use the user bound to the Context
instead of exposing members that target specific users.
class FooManager { Context mContext; void fooBar() { mIFooBar.fooBarForUser(mContext.getUser()); } }
class FooManager { Context mContext; Foobar getFoobar() { // Bad: doesn't appy mContext.getUserId(). mIFooBar.fooBarForUser(Process.myUserHandle()); } Foobar getFoobar() { // Also bad: doesn't appy mContext.getUserId(). mIFooBar.fooBar(); } Foobar getFoobarForUser(UserHandle user) { mIFooBar.fooBarForUser(user); } }
Exception: A method may accept a user argument if it accepts values that don't represent a single user, such as UserHandle.ALL
.
UserHandle
instead of plain int
sUserHandle
is preferred to ensure type safety and avoid conflating user IDs with uids.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Where unavoidable, int
s representing a user ID must be annotated with @UserIdInt
.
Foobar getFoobarForUser(@UserIdInt int user);
Broadcast intents are very powerful, but they've resulted in emergent behaviors that can negatively impact system health, and so new broadcast intents should be added judiciously.
Here are some specific concerns which result in us discouraging the introduction of new broadcast intents:
When sending broadcasts without the FLAG_RECEIVER_REGISTERED_ONLY
flag, they will force-start any applications which aren‘t already running. While this can sometimes be a desired outcome, it can result in stampeding of dozens of apps, negatively impacting system health. We’d recommend using alternative strategies, such as JobScheduler
, to better coordinate when various preconditions are met.
When sending broadcasts, there is little ability to filter or adjust the content delivered to apps. This makes it difficult or impossible to respond to future privacy concerns, or introduce behavior changes based on the target SDK of the receiving app.
Since broadcast queues are a shared resource, they can become overloaded and may not result in timely delivery of your event. We've observed several broadcast queues in the wild which have an end-to-end latency of 10 minutes or longer.
For these reasons, we encourage new features to consider using listeners/callbacks or other facilities such as JobScheduler
instead of broadcast intents.
In cases where broadcast intents still remain the ideal design, here are some best-practices that should be considered:
Intent.FLAG_RECEIVER_REGISTERED_ONLY
to limit your broadcast to apps that are already running. For example, ACTION_SCREEN_ON
uses this design to avoid waking up apps.Intent.setPackage()
or Intent.setComponent()
to target the broadcast at a specific app of interest. For example, ACTION_MEDIA_BUTTON
uses this design to focus on the current app handling playback controls.<protected-broadcast>
to ensure that malicious apps can't impersonate the OS.