| ## 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 Metalava’s API check. When in doubt, refer to |
| the Eclipse Foundation’s |
| [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?" |