| ## Implementing compatibility {#compat} |
| |
| ### Referencing new APIs {#compat-newapi} |
| |
| Generally, methods on library classes should be available to all devices above |
| the library's `minSdkVersion`; however, the behavior of the method may vary |
| based on platform API availability. |
| |
| For example, a method may delegate to a platform API on SDKs where the API is |
| available, backport a subset of behavior on earlier SDKs, and no-op on very old |
| SDKs. |
| |
| #### Checking device SDK version {#compat-sdk} |
| |
| The most common way of delegating to platform or backport implementations is to |
| compare the device's `Build.VERSION.SDK_INT` field to a known-good SDK version; |
| for example, the SDK in which a method first appeared or in which a critical bug |
| was first fixed. |
| |
| When developing against pre-release SDKs where the `SDK_INT` has not been |
| finalized, SDK checks **must** use `BuildCompat.isAtLeastX()` methods and |
| **must** use a tip-of-tree `project` dependency to ensure that the |
| implementation of `BuildCompat` stays up-to-date when the SDK is finalized. |
| |
| **Do not** assume that the next SDK release's `SDK_INT` will be N+1. The value |
| is not finalized until SDK finalization happens, at which point the `isAtLeast` |
| check will be updated. **Never** write your own check for a pre-release SDK. |
| |
| ```java {.good} |
| @NonNull |
| public static List<Window> getAllWindows() { |
| if (BuildCompat.isAtLeastR()) { |
| return ApiRImpl.getAllWindows(); |
| } |
| return Collections.emptyList(); |
| } |
| ``` |
| |
| ```kotlin {.good} |
| dependencies { |
| api(project(":core:core")) |
| } |
| ``` |
| |
| #### Validating class verification |
| |
| To verify that your library does not raise class verification failures, look for |
| `dex2oat` output during install time. |
| |
| You can generate class verification logs from test APKs. Simply call the |
| class/method that should generate a class verification failure in a test. |
| |
| The test APK will generate class verification logs on install. |
| |
| ```bash |
| # Enable ART logging (requires root). Note the 2 pairs of quotes! |
| adb root |
| adb shell setprop dalvik.vm.dex2oat-flags '"--runtime-arg -verbose:verifier"' |
| |
| # Restart Android services to pick up the settings |
| adb shell stop && adb shell start |
| |
| # Optional: clear logs which aren't relevant |
| adb logcat -c |
| |
| # Install the app and check for ART logs |
| # This line is what triggers log lines, and can be repeated |
| adb install -d -r someApk.apk |
| |
| # it's useful to run this _during_ install in another shell |
| adb logcat | grep 'dex2oat' |
| ... |
| ... I dex2oat : Soft verification failures in |
| ``` |
| |
| #### View constructors {#compat-view-constructors} |
| |
| The four-arg View constructor -- `View(Context, AttributeSet, int, int)` -- was |
| added in SDK 21 and allows a developer to pass in an explicit default style |
| resource rather than relying on a theme attribute to resolve the default style |
| resource. Because this API was added in SDK 21, care must be taken to ensure |
| that it is not called through any < SDK 21 code path. |
| |
| Views *may* implement a four-arg constructor in one of the following ways: |
| |
| 1. Do not implement. |
| 1. Implement and annotate with `@RequiresApi(21)`. This means the three-arg |
| constructor **must not** call into the four-arg constructor. |
| |
| #### Device-specific issues {#compat-oem} |
| |
| Library code may work around device- or manufacturer-specific issues -- issues |
| not present in AOSP builds of Android -- *only* if a corresponding CTS test |
| and/or CDD policy is added to the next revision of the Android platform. Doing |
| so ensures that such issues can be detected and fixed by OEMs. |
| |
| #### Handling `minSdkVersion` disparity {#compat-minsdk} |
| |
| Methods that only need to be accessible on newer devices, including |
| `to<PlatformClass>()` methods, may be annotated with `@RequiresApi(<sdk>)` to |
| indicate they must not be called when running on older SDKs. This annotation is |
| enforced at build time by the `NewApi` lint check. |
| |
| #### Handling `targetSdkVersion` behavior changes {#compat-targetsdk} |
| |
| To preserve application functionality, device behavior at a given API level may |
| change based on an application's `targetSdkVersion`. For example, if an app with |
| `targetSdkVersion` set to API level 22 runs on a device with API level 29, all |
| required permissions will be granted at installation time and the run-time |
| permissions framework will emulate earlier device behavior. |
| |
| Libraries do not have control over the app's `targetSdkVersion` and -- in rare |
| cases -- may need to handle variations in platform behavior. Refer to the |
| following pages for version-specific behavior changes: |
| |
| * Android 14, |
| [API level 34](https://developer.android.com/about/versions/14/behavior-changes-14) |
| * Android 13, |
| [API level 33](https://developer.android.com/about/versions/13/behavior-changes-13) |
| * Android 12, |
| [API level 31](https://developer.android.com/about/versions/12/behavior-changes-12) |
| * Android 11, |
| [API level 30](https://developer.android.com/about/versions/11/behavior-changes-11) |
| * Android 10, |
| [API level 29](https://developer.android.com/about/versions/10/behavior-changes-10) |
| * Android Pie (9.0), |
| [API level 28](https://developer.android.com/about/versions/pie/android-9.0-changes-28) |
| * Android Oreo (8.0), |
| [API level 26](https://developer.android.com/about/versions/oreo/android-8.0-changes) |
| * Android Nougat(7.0), |
| [API level 24](https://developer.android.com/about/versions/nougat/android-7.0-changes) |
| * Android Lollipop (5.0), |
| [API level 21](https://developer.android.com/about/versions/lollipop/android-5.0-changes) |
| * Android KitKat (4.4), |
| [API level 19](https://developer.android.com/about/versions/kitkat/android-4.4#Behaviors) |
| |
| #### Working around Lint issues {#compat-lint} |
| |
| In rare cases, Lint may fail to interpret API usages and yield a `NewApi` error |
| and require the use of `@TargetApi` or `@SuppressLint('NewApi')` annotations. |
| Both of these annotations are strongly discouraged and may only be used |
| temporarily. They **must never** be used in a stable release. Any usage of these |
| annotation **must** be associated with an active bug, and the usage must be |
| removed when the bug is resolved. |
| |
| #### Java 8+ APIs and core library desugaring {#compat-desugar} |
| |
| The DEX compiler (D8) supports |
| [API desugaring](https://developer.android.com/studio/write/java8-support-table) |
| to enable usage of Java 8+ APIs on a broader range of platform API levels. |
| Libraries using AGP 8.2+ can express the toolchain requirements necessary for |
| desugaring to work as intended, but these requirements are only enforced for |
| **apps** that are also building with AGP 8.2+. |
| [While adoption of AGP 8.2+ remains low](https://issuetracker.google.com/172590889#comment12), |
| AndroidX libraries **must not** rely on `coreLibraryDesugaring` to access Java |
| language APIs on earlier platform API levels. For example, `java.time.*` may |
| only be used in code paths targeting API level 26 and above. |
| |
| ### Delegating to API-specific implementations {#delegating-to-api-specific-implementations} |
| |
| #### Referencing SDK constants {#sdk-constants} |
| |
| Generally speaking, platform and Mainline SDK constants should not be inlined. |
| |
| Constants that can be inlined by the compiler (most primitives and `String`s) |
| should be referenced directly from the SDK rather than copying and pasting the |
| value. This will raise an `InlinedApi` lint warning, which may be suppressed. |
| |
| ``` |
| public static class ViewCompat { |
| @Suppress("InlinedApi") |
| public static final int SOME_CONSTANT = View.SOME_CONSTANT |
| } |
| ``` |
| |
| In rare cases, some SDK constants are not defined at compile-time and cannot be |
| inlined by the compiler. In these cases, you will need to handle them like any |
| other API using out-of-lining and version gating. |
| |
| ``` |
| public static final int RUNTIME_CONSTANT = |
| if (SDK_INT > 34) { Api34Impl.RUNTIME_CONSTANT } else { -1 } |
| ``` |
| |
| Developers **must not** inline platform or Mainline SDK constants that are not |
| part of a finalized public SDK. **Do not** inline values from `@hide` constants |
| or public constants in an unfinalized SDK. |
| |
| #### SDK-dependent reflection {#sdk-reflection} |
| |
| Note: The |
| [BanUncheckedReflection](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lint-checks/src/main/java/androidx/build/lint/BanUncheckedReflection.kt) |
| lint check detects disallowed usages of reflection. |
| |
| Starting in API level 28, the platform restricts which |
| [non-SDK interfaces](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces) |
| can be accessed via reflection by apps and libraries. As a general rule, you |
| will **not** be able to use reflection to access hidden APIs on devices with |
| `SDK_INT` greater than `Build.VERSION_CODES.P` (28). |
| |
| In cases where a hidden API is a constant value, **do not** inline the value. |
| Hidden APIs cannot be tested by CTS and carry no stability guarantees. |
| |
| Per go/platform-parity, on earlier devices or in cases where an API is marked |
| with `@UnsupportedAppUsage`, reflection on hidden platform APIs is allowed |
| **only** when an alternative public platform API exists in a later revision of |
| the Android SDK. For example, the following implementation is allowed: |
| |
| ```java |
| public AccessibilityDelegate getAccessibilityDelegate(View v) { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { |
| // Retrieve the delegate using a public API. |
| return v.getAccessibilityDelegate(); |
| } else if (Build.VERSION.SDK_INT >= 11) { |
| // Retrieve the delegate by reflecting on a private field. If the |
| // field does not exist or cannot be accessed, this will no-op. |
| if (sAccessibilityDelegateField == null) { |
| try { |
| sAccessibilityDelegateField = View.class |
| .getDeclaredField("mAccessibilityDelegate"); |
| sAccessibilityDelegateField.setAccessible(true); |
| } catch (Throwable t) { |
| sAccessibilityDelegateCheckFailed = true; |
| return null; |
| } |
| } |
| try { |
| Object o = sAccessibilityDelegateField.get(v); |
| if (o instanceof View.AccessibilityDelegate) { |
| return (View.AccessibilityDelegate) o; |
| } |
| return null; |
| } catch (Throwable t) { |
| sAccessibilityDelegateCheckFailed = true; |
| return null; |
| } |
| } else { |
| // There is no way to retrieve the delegate, even via reflection. |
| return null; |
| } |
| ``` |
| |
| Calls to public APIs added in pre-release revisions *must* be gated using |
| `BuildCompat`: |
| |
| ```java |
| if (BuildCompat.isAtLeastQ()) { |
| // call new API added in Q |
| } else if (Build.SDK_INT.VERSION >= 23) { |
| // make a best-effort using APIs that we expect to be available |
| } else { |
| // no-op or best-effort given no information |
| } |
| ``` |
| |
| #### Shadowing platform classes {#sdk-shadowing} |
| |
| Generally, libraries should **never** create new classes in the `android.*` |
| namespace or re-define any classes that may be present in the boot classpath. |
| **Do not** create a library class with the same fully-qualified name as one in |
| the platform SDK, a Mainline module, sidecar JAR, or another library. Keep all |
| classes within your own package based on your Maven group ID. |
| |
| The reverse also applies: the platform SDK, Mainline modules, sidecar JARs, and |
| other libraries **must not** define classes in the `androidx.*` namespace. |
| |
| In *extremely limited* cases, the overhead of reflecting on a platform class may |
| cause performance issues for apps on a scale that warrants using a compile-only |
| stub of the platform class to avoid reflection. Any instances of this **must** |
| be approved by Jetpack Working Group before submitting the change. |
| |
| ### Inter-process communication {#ipc} |
| |
| Protocols and data structures used for IPC must support interoperability between |
| different versions of libraries and should be treated similarly to public API. |
| |
| **Do not** design your own serialization mechanism or wire format for disk |
| storage or inter-process communication. Preserving and verifying compatibility |
| is difficult and error-prone. |
| |
| **Do not** expose your serialization mechanism in your API surface. Neither |
| Stable AIDL nor Protobuf generate stable language APIs. |
| |
| Generally, any communication prototcol, handshake, etc. must maintain |
| compatibility consistent with SemVer guidelines. Consider how your protocol will |
| handle addition and removal of operations or constants, compatibility-breaking |
| changes, and other modifications without crashing either the host or client |
| process. |
| |
| We recommend the following IPC mechanisms, in order of preference: |
| |
| #### Stable AIDL <a name="ipc-stableaidl"></a> |
| |
| Stable AIDL is used by the Android platform and AndroidX to provide a |
| platform-native IPC mechanism with strong inter-process compatibility |
| guarantees. It supports a subset of standard AIDL. |
| |
| Use Stable AIDL if your library: |
| |
| - Needs to send and receive Android's `Parcelable` data types |
| - Communicates directly with the Android platform, System UI, or other AOSP |
| components *or* is likely to do so in the future |
| |
| **Do not** use Stable AIDL to persist data to disk. |
| |
| ##### Using Stable AIDL {#ipc-stableaidl-using} |
| |
| To add Stable AIDL definitions to your project: |
| |
| 1. Add the Stable AIDL plugin to `build.gradle`: |
| |
| ``` |
| plugins { |
| id("androidx.stableaidl") |
| } |
| ``` |
| |
| 2. Enable the AIDL build feature and specify an initial version for your Stable |
| AIDL interfaces in `build.gradle`: |
| |
| ``` |
| android { |
| buildFeatures { |
| aidl = true |
| } |
| buildTypes.all { |
| stableAidl { |
| version 1 |
| } |
| } |
| } |
| ``` |
| |
| 3. Migrate existing AIDL files or create new AIDL files under |
| `<project>/src/main/stableAidl` |
| |
| 4. Generate an initial set of Stable AIDL API tracking files by running |
| |
| ``` |
| ./gradlew :path:to:project:updateAidlApi |
| ``` |
| |
| ##### Annotating unstable AIDL {#ipc-stableaidl-unstable} |
| |
| Once an API that relies on an IPC contract ships to production in an app, the |
| contract is locked in and must maintain compatibility to prevent crashing either |
| end of an inter-process communication channel. |
| |
| Developers **should** annotate unstable IPC classes with a `@RequiresOptIn` |
| annotation explaining that they must not be used in production code. Libraries |
| **must not** opt-in to these annotations when such classes are referenced |
| internally, and should instead propagate the annotations to public API surfaces. |
| |
| A single annotation for this purpose may be defined per library or atomic group: |
| |
| ```java |
| /** |
| * Parcelables and AIDL-generated classes bearing this annotation are not |
| * guaranteed to be stable and must not be used for inter-process communication |
| * in production. |
| */ |
| @RequiresOptIn |
| public @interface UnstableAidlDefinition {} |
| ``` |
| |
| Generally speaking, at this point in time no libraries should have unstable |
| `Parcelable` classes defined in source code, but for completeness: |
| |
| ```java |
| @UnstableAidlDefinition |
| public class ResultReceiver implements Parcelable { ... } |
| ``` |
| |
| AIDL definition files under `src/aidl` should use `@JavaPassthrough` with a |
| fully-qualified class name to annotate generated classes: |
| |
| ```java |
| @JavaPassthrough(annotation="@androidx.core.util.UnstableAidlDefinition") |
| oneway interface IResultReceiver { |
| void send(int resultCode, in Bundle resultData); |
| } |
| ``` |
| |
| For Stable AIDL, the build system enforces per-CL compatibility guarantees. No |
| annotations are required for Stable AIDL definition files under |
| `src/stableAidl`. |
| |
| #### Protobuf <a name="ipc-protobuf"></a> |
| |
| Protobuf is used by many Google applications and services to provide an IPC and |
| disk persistence mechanism with strong inter-process compatibility guarantees. |
| |
| Use Protobuf if your library: |
| |
| - Communicates directly with other applications or services already using |
| Protobuf |
| - Your data structure is complex and likely to change over time - Needs to |
| persist data to disk |
| |
| If your data includes `FileDescriptor`s, `Binder`s, or other platform-defined |
| `Parcelable` data structures, consider using Stable AIDL instead. Protobuf |
| cannot directly handle these types, and they will need to be stored alongside |
| the serialized Protobuf bytes in a `Bundle`. |
| |
| See [Protobuf](#dependencies-protobuf) for more information on using protocol |
| buffers in your library. |
| |
| WARNING While Protobuf is capable of maintaining inter-process compatibility, |
| AndroidX does not currently provide compatibility tracking or enforcement. |
| Library owners must perform their own validation. |
| |
| NOTE We are currently investigating the suitability of Square's |
| [`wire` library](https://github.com/square/wire) for handling protocol buffers |
| in Android libraries. If adopted, it will replace `proto` library dependencies. |
| Libraries that expose their serialization mechanism in their API surface *will |
| not be able to migrate*. |
| |
| #### Bundle <a name="ipc-bundle"></a> |
| |
| `Bundle` is used by the Android platform and AndroidX as a lightweight IPC |
| mechanism. It has the weakest type safety and compatibility guarantees of any |
| recommendation, and it has many caveats that make it a poor choice. |
| |
| In some cases, you may need to use a `Bundle` to wrap another IPC mechanism so |
| that it can be passed through Android platform APIs, e.g. a `Bundle` that wraps |
| a `byte[]` representing a serialized Protobuf. |
| |
| Use `Bundle` if your library: |
| |
| - Has a very simple data model that is unlikely to change in the future |
| - Needs to send or receive `Binder`s, `FileDescriptor`s, or platform-defined |
| `Parcelable`s |
| ([example](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core/src/main/java/androidx/core/graphics/drawable/IconCompat.java;l=820)) |
| |
| Caveats for `Bundle` include: |
| |
| - When running on Android S and below, accessing *any* entry in a `Bundle` |
| will result in the platform attempting to deserialize *every* entry. This |
| has been fixed in Android T and later with "lazy" bundles, but developers |
| should be careful when accessing `Bundle` on earlier platforms. If a single |
| entry cannot be loaded -- for example if a developer added a custom |
| `Parcelable` that doesn't exist in the receiver's classpath -- an exception |
| will be thrown when accessing *any* entry. |
| - On all platforms, library code that receives `Bundle`s data from outside the |
| process **must** read the data defensively. See previous note regarding |
| additional concerns for Android S and below. |
| - On all platforms, library code that sends `Bundle`s outside the process |
| *should* discourage clients from passing custom `Parcelable`s. |
| - `Bundle` provides no versioning and Jetpack provides no affordances for |
| tracking the keys or value types associated with a `Bundle`. Library owners |
| are responsible for providing their own system for guaranteeing wire format |
| compatibility between versions. |
| |
| #### Versioned Parcelable <a name="ipc-versionedparcelable"></a> |
| |
| `VersionedParcelable` is a deprecated library that was intended to provide |
| compatibility guarantees around the Android platform's `Parcelable` class; |
| however, the initial version contained bugs and it was not actively maintained. |
| |
| Use `VersionedParcelable` if your library: |
| |
| - Is already using `VersionedParcelable` and you are aware of its |
| compatibility constraints |
| |
| **Do not** use `VersionedParcelable` in all other cases. |
| |
| #### Wire <a name="ipc-wire"></a> |
| |
| We are currently evaluating Square's [Wire](https://github.com/square/wire) as a |
| front-end to Protobuf. If this library meets your team's needs based on your own |
| research, feel free to use it. |
| |
| #### gRPC <a name="ipc-grpc"></a> |
| |
| Some clients have requested to use Google's [gRPC](https://grpc.io/) library to |
| align with other Google products. It's okay to use gRPC for network |
| communication or communication with libraries and services outside of AndroidX |
| that are already using gRPC. |
| |
| **Do not** use gRPC to communicate between AndroidX libraries or with the |
| Android platform. |
| |
| #### Parcelable <a name="ipc-parcelable"></a> |
| |
| **Do not** implement `Parcelable` for any class that may be used for IPC or |
| otherwise exposed as public API. By default, `Parcelable` does not provide any |
| compatibility guarantees and will result in crashes if fields are added or |
| removed between library versions. If you are using Stable AIDL, you *may* use |
| AIDL-defined parcelables for IPC but not public API. |