Policies around what types of changes may be made to existing Android APIs and how those changes should be implemented to maximize compatibility with existing apps and codebases.
Binary-breaking changes are not allowed in finalized public API and will generally raise errors when running make update-api
. There may, however, be edge cases that are not caught by Metalava’s API check. When in doubt, refer to the Eclipse Foundation’s Evolving Java-based APIs guide for a detailed explanation of what types of API changes are compatible in Java. Binary-breaking changes in non-public (ex. system) APIs should follow the deprecate/replace cycle.
Source-breaking changes are discouraged even if they are not binary-breaking. One example of a binary-compatible but source-breaking change is adding a generic to an existing class, which is binary-compatible but may introduce compilation errors due to inheritance or ambiguous references. Source-breaking changes will not raise errors when running make update-api
, so care must be taken to understand the impact of changes to existing API signatures.
In some cases, source-breaking changes are necessary to improve the developer experience or code correctness. For example, adding nullability annotations to Java sources improves interopability with Kotlin code and reduces the likelihood of errors, but often requires changes -- sometimes significant changes -- to source code.
@SystemApi
, @TestApi
)APIs annotated with @TestApi
may be changed at any time.
APIs annotated with @SystemApi
must be preserved for three years. Removal or refactoring of a system API must occur on the following schedule:
Deprecation is considered an API change and may occur in a major (e.g. letter) release. Use the @Deprecated
source annotation and @deprecated <summary>
docs annotation together when deprecating APIs. Your summary must include a migration strategy, which may link to a replacement API or explain why the API should not be used.
/** * 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)
APIs defined in XML and exposed in Java, including attributes and styleable properties exposed in the android.R
class, must also be deprecated with a summary.
<!-- Attribute whether the accessibility service ... {@deprecated Not used by the framework} --> <attr name="canRequestEnhancedWebAccessibility" format="boolean" />
Deprecations are most useful for discouraging adoption of an API in new code.
We also require that APIs are marked as @deprecated
before they are @removed
, but this does not provide strong motivation for developers to migrate away from an API they are already using.
Before deprecating an API, consider the impact on developers. The effects of deprecating an API include:
javac
will emit a warning during compilation-Werror
will need to individually fix or suppress every usage of a deprecated API before they can update their compile SDK versionAs a result, deprecating an API may discourage the developers who are the most concerned about code health -- those using -Werror
-- from adopting new SDKs. At the other end of the spectrum, developers who are not concerned about warnings in their existing code are likely to ignore deprecations altogether.
Both of these cases are made worse when an SDK introduces a large number of deprecations.
For this reason, we recommend deprecating APIs only in cases where:
@removed
in a future releaseWhen an API is deprecated and replaced with a new API, we strongly recommend that a corresponding compatibility API be added to a Jetpack library like androidx.core
to simplify supporting both old and new devices.
We do not recommend deprecating APIs that are working as intended and will continue to work in future releases.
/** * ... * @deprecated Use {@link #doThing(int, Bundle)} instead. */ @Deprecated public void doThing(int action) { ... } public void doThing(int action, @Nullable Bundle extras) { ... }
/** * ... * @deprecated No longer displayed in the status bar as of API 21. */ @Deprecated public RemoteViews tickerView;
The behavior of deprecated APIs must be maintained, which means test implementations must remain the same and tests must continue to pass after the API has been deprecated. If the API does not have tests, tests should be added.
Deprecated API surfaces should not be expanded in future releases. Lint correctness annotations (ex. @Nullable
) may be added to an existing deprecated API, but new APIs should not be added to deprecated classes or interfaces.
New APIs should not be added as deprecated. APIs that were added and subsequently deprecated within a pre-release cycle -- thus would initially enter the public API surface as deprecated -- should be removed before API finalization.
Soft removal is a source-breaking change and should be avoided in public APIs unless explicitly approved by API Council. For approval, email the Android API Council mailing list. For system APIs, soft removals must be preceded by deprecation for the duration of a major release. Remove all docs references to the APIs and use the `@removed
The behavior of soft-removed APIs may be maintained as-is but more importantly must be preserved such that existing callers will not crash when calling the API. In some cases, that may mean preserving behavior.
Test coverage must be maintained, but the content of the tests may need to change to accomodate for behavioral changes. Tests must still ensure that existing callers do not crash at run time.
/** * Ringer volume. This is ... * * @removed Not functional since API 2. */ public static final String VOLUME_RING = ...
At a technical level, the API is removed from the SDK stub JAR and compile-time classpath but still exists on the run-time classpath -- similar to @hide
APIs.
From an app developer perspective, the API no longer appears in auto-complete and source code that references the API will no longer compile when the compileSdk
is equal to or later than the SDK at which the API was removed; however, source code will continue to compile successfully against earlier SDKs and binaries that reference the API will continue to work.
Certain categories of API must not be soft removed.
Abstract methods on classes that may be extended by developers must not be soft removed. Doing so will make it impossible for developers to successfully extend the class across all SDK levels.
In rare cases where it was never and will never be possible for developers to extend a class, abstract methods may still be soft removed.
Hard removal is a binary-breaking change and should never occur in public APIs. If you have a strong justification for a removal, you can request for approval by submitting the 1 pager at go/request-android-api-removal.
The @Discouraged
annotation is used to indicate that an API is not recommended in most (>95%) cases. Discouraged APIs differ from deprecated APIs in that there exists a narrow critical use case that prevents deprecation. When marking an API as discouraged, an explanation and an alternative solution must be provided.
@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 (e.g. `R.foo.bar`) than by name (e.g. `getIdentifier()`)") public int getIdentifier(String name, String defType, String defPackage) { return mResourcesImpl.getIdentifier(name, defType, defPackage); }
New APIs should not be added as discouraged. Currently, only the Performance team can discourage an API.
In some cases it can be desirable to change the implementation behavior of an existing API. For example, in Android 7.0 we improved DropBoxManager
to clearly communicate when developers tried posting events that were too large to send across Binder
.
However, to ensure that existing apps aren‘t surprised by these behavior changes, we strongly recommend preserving a safe behavior for older applications. We’ve historically guarded these behavior changes based on the ApplicationInfo.targetSdkVersion
of the app, but we‘ve recently migrated to require using the App Compatibility Framework. Here’s an example of how to implement a behavior change using this new framework:
import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; public class MyClass { @ChangeId // This means the change will be enabled for target SDK R and higher. @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R) // Use a bug number as the value, provide extra detail in the bug. // FOO_NOW_DOES_X will be the change name, and 123456789 the change id. static final long FOO_NOW_DOES_X = 123456789L; public void doFoo() { if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) { // do the new thing } else { // do the old thing } } }
Using this App Compatibility Framework design enables developers to temporarily disable specific behavior changes during preview and beta releases as part of debugging their apps, instead of forcing them to adjust to dozens of behavior changes simultaneously.
Forward compatibility is a design characteristic that allows a system to accept input intended for a later version of itself. In the case of API design -- especially platform APIs -- special attention must be paid to the initial design as well as future changes since developers expect to write code once, test it once, and have it run everywhere without issue.
The most common forward compatibility issues in Android are caused by:
@IntDef
or enum
) previously assumed to be complete, e.g. where switch
has a default
that throws an exceptionColorStateList
-type resources in XML where previously only <color>
resources were supportedrequireNotNull()
check that was present on older versionsIn all of these cases, developers will only find out that something is wrong at run time. Worse, they may only find out as a result of crash reports from older devices in the field.
Additionally, these cases are all technically valid API changes. They do not break binary or source compatibility and API lint will not catch any of these issues.
As a result, API designers must pay careful attention when modifying existing classes. Ask the question, “Is this change going to cause code that's written and tested only against the latest version of the platform to fail on older versions?”