blob: 0848ac179bd1ffc0a02d6ea7e45f096a3bbf1604 [file] [log] [blame] [view]
## Evolution of Android platform APIs <a name="evolution"></a>
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 <a name="binary-compat"></a>
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 Metalavas API check. When in doubt, refer to
the Eclipse Foundations
[Evolving Java-based APIs](https://wiki.eclipse.org/Evolving_Java-based_APIs_2)
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](#deprecation) cycle.
### Source-breaking changes <a name="source-compat"></a>
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](https://wiki.eclipse.org/Evolving_Java-based_APIs_2#Turning_non-generic_types_and_methods_into_generic_ones)
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.
### Changes to private APIs (`@SystemApi`, `@TestApi`) <a name="private-apis"></a>
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:
* API y - Added
* API y+1 - [Deprecation](#deprecation)
* Mark the code as @Deprecated
* Add replacements, and link to the replacement in the javadoc for the
deprecated code using the @deprecated tag.
* Mid-development-cycle, file bugs against internal users telling them API
is going away, giving them a chance to ensure replacement APIs are
adequate.
* API y+2 - [Soft removal](#soft-removal)
* Mark code as @removed
* Optionally, throw or no-op for apps that target the current sdk level
for the release
* API y+3 - [Hard removal](#hard-removal)
* Code is completely removed from source tree
### Deprecation <a name="deprecation"></a>
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.
```java {.good .no-copy}
/**
* 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.
```xml {.good .no-copy}
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
```
#### When is it appropriate to deprecate an API? <a name="deprecation-appropriate"></a>
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`](#soft-removal), 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
- Deprecation warnings cannot be suppressed globally or baselined, so
developers using `-Werror` will need to individually fix or suppress
*every* usage of a deprecated API before they can update their compile
SDK version
- Deprecation warnings on imports of deprecated classes cannot be
suppressed, so developers will need to inline the fully-qualified
classname for *every* usage of a deprecated class before they can update
their compile SDK version
- Documentation on d.android.com will show a deprecation notice
- IDEs like Android Studio will show a warning at the API usage site
- IDEs *may* down-rank or hide the API from auto-complete
As 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:
1. The API will be `@removed` in a future release
1. Usage of the API leads to incorrect or undefined behavior that cannot be
fixed without breaking compatibility
When 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.
```java {.bad .no-copy}
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
```
```java {.good .no-copy}
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
```
#### Changes to deprecated APIs <a name="deprecation-changes"></a>
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 <a name="soft-removal"></a>
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](mailto:android-api-council@google.com). 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
<summary>` docs annotation when soft-removing APIs. Your summary **must**
include the reason for removal and may include a migration strategy as explained
in Deprecation.
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.
```java {.good .no-copy}
/**
* 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
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 <a name="hard-removal"></a>
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.
### Discouraging {.numbered}
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.
```java {.good .no-copy}
@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.
### Changing behavior of existing APIs <a name="behavior"></a>
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:
```java {.good .no-copy}
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
public class MyClass {
@ChangeId
// This means the change will be enabled for target SDK R and higher.
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
// Use a bug number as the value, provide extra detail in the bug.
// FOO_NOW_DOES_X will be the change name, and 123456789 the change id.
static final long FOO_NOW_DOES_X = 123456789L;
public void doFoo() {
if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
// do the new thing
} else {
// do the old thing
}
}
}
```
Using this App Compatibility Framework design enables developers to temporarily
disable specific behavior changes during preview and beta releases as part of
debugging their apps, instead of forcing them to adjust to dozens of behavior
changes simultaneously.
#### Forward compatibility
Forward compatibility is a design characteristic that allows a system to accept
input intended for a later version of itself. In the case of API design --
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:
- Adding new constants to a set (e.g. `@IntDef` or `enum`) previously assumed
to be complete, e.g. where `switch` has a `default` that throws an exception
- Adding support for a feature that is not captured directly in the API
surface, e.g. support for assigning `ColorStateList`-type resources in XML
where previously only `<color>` resources were supported
- Loosening restrictions on run-time checks, e.g. removing a
`requireNotNull()` check that was present on older versions
In 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?"