Warning: Native APIs do not support feature flagging.
Any change that alters the stable native API surface of Android needs API review. To get an API review, assign the CL to android-ndk-api-council@google.com and go/gwsq will automatically assign a reviewer.
Many of the rules in this doc exist to protect API compatibility across releases. Each API surface has its own compatibility guarantees. go/android-api-types is the authoritative source, but briefly:
| API surface | Consumers | Map file annotation | Compatibility | : : : : guarantee : | ----------- | ------------------ | ------------------- | ------------- | | NDK | Apps | introduced-in
, or | Forever | : : : no other annotation : : | LL-NDK | Vendors/OEMs | llndk
| Forever | | APEX | The system or | apex
| Forever | : : other APEX modules : : : | SystemAPI | APEX modules | systemapi
| 4 years | | Platform | System image | platform
| None |
During the time that compatibility is guaranteed for an API, the ABI and behavior of that API must not be changed. This is a difficult guaranatee to uphold, and that's why these rules are in place.
For the duration of the compatibility window, the implementation of the API must remain, and must avoid significant behavior changes. Stable APIs can almost never be truly removed (if your API is less broken than invoke_cve()
, it probably can't be removed). To discourage developers from using an API you can annotate it with __DEPRECATED_IN(api_level, message)
, but that should never be done without an alternative for callers to migrate to, and doing so does not allow you to remove or significantly alter the implementation.
One of the difficulties in concrete rules is applying them to a platform that was developed without strict guidelines from the beginning, so some of the existing APIs may not adhere. In some cases, the right choice might be to go with what is consistent with APIs in the same general area of the code, rather than in the ideal rules laid out herein.
The rules are a work in progress and will be added to in the future as other patterns emerge from future API reviews.
Even if it is not expected that C is ever used directly, C is the common denominator of (nearly) all FFI systems & tools. It also has a simpler stable ABI story than C++, making it required for NDK’s ABI stability requirements.
Jetpack C++ APIs are exempt from this rule. See go/jetpack-cpp-guidelines (draft) for additional guidelines for Jetpack.
The header contents (after the #include
block) should begin with __BEGIN_DECLS
and end with __END_DECLS
from sys/cdefs.h
Non-typedef’ed structs must be used as struct type
not just type
Prefer typedef’ing structs, see the Naming Conventions section for more details.
Enums must specify their backing type
For example:
typedef enum AFoo : int16_t { AFOO_ONE, AFOO_TWO, AFOO_THREE } AFoo; AFoo transformAFoo(AFoo param);
This C++ feature is provided for C as a Clang extension, so this should be be used even for headers that must otherwise be C.
A rare exception to this rule will be made for interfaces defined by third- parties (e.g. Vulkan or ICU) if upstream is not willing to depend on the extension.
Enums used by name must be used as enum type
or be typedef'd.
Prefer typedefs.
Use stdbool.h
. C has bool
, but it must be included. Do not use integer types for boolean values.
Empty argument lists must be spelled fn(void)
rather than fn()
. In C, fn(void)
declares a function that accepts 0 arguments, whereas fn()
declares a function that accepts an unknown number of arguments.
Mark methods with __INTRODUCED_IN(<api_level>);
Note: Don't do this for types though. See the rule for types and enums.
Example:
binder_status_t AIBinder_getExtension(AIBinder* binder, AIBinder** outExt) __INTRODUCED_IN(__ANDROID_API_R__);
DO NOT wrap the new methods with #if __ANDROID_API__ >= <api_level>
and #endif
.
#if __ANDROID_API__ >= __ANDROID_API_R__ binder_status_t AIBinder_getExtension(AIBinder* binder, AIBinder** outExt) __INTRODUCED_IN(__ANDROID_API_R__); #endif
The __INTRODUCED_IN
annotation causes Clang to trigger an error when the method is unavailable. An additional #if
guard is not only redundant, but also makes it difficult for the clients to use the APIs conditionally as described in https://developer.android.com/ndk/guides/using-newer-apis.
Types & enum declaration MUST NOT be guarded by #if __ANDROID_API__ >= <api_level>
. This makes opportunistic usage via dlsym
or __builtin_available()
harder to do.
They should not be annotated with __INTRODUCED_IN()
either.
Documentation for individual enum values should include the first API level that they were added, with a comment saying “Introduced in API <number>
.”
Note that the documentation is not macro-replaced so should use the actual number instead of the codename since that's what users need to know for their build.gradle files.
Note: Jetpack libraries should use map files to control symbol visibility but do not need any annotations; the guidelines in this section do not apply.
Libraries must have a <name>.map.txt
with the symbol being exported
The map file must be used by an appropriate Soong module
ndk_library
module.stubs
attribute on a cc_library
module.NDK symbols must be marked with # introduced=<api_level>
Can be either on each individual symbol or on a version block. Applying tags to the same line as the version definition behaves the same as if each symbol defined in the version specified the same tag.
Note that all symbols not otherwise annotated are implicitly introduced
in the first_version
specified in the ndk_library
. If not specified, the API is implicitly available in all supported OS releases (currently back to API 21).
For example, libaaudio
was introduced in API 24, so any symbols that do not have another annotation will default to introduced=24
.
Use codenames for preview API levels in map files
foo; # introduced=Tiramisu
foo; # introduced=33
To find the correct spelling for a codename, see apiLevelsMap in Soong for released codenames (be sure to check the current version of the source for your branch) or Platform_version_active_codenames
in out/soong/soong.variables
for in-progress codenames.
After an API level is finalized it is okay to replace codenames with API numbers, but not necessary.
APEX symbols must be marked with apex
System API symbols must be marked with systemapi
LL-NDK symbols must be marked with llndk
Platform-only symbols must be marked with platform-only
_PRIVATE
or _PLATFORM
is impliclty platform-only
..map.txt
. The linker will accept a file with any name, and the .map.txt
suffix requires NDK API council review for all changes.API surface | Test suite |
---|---|
NDK | CTS |
LL-NDK | VTS |
APEX | MTS |
SystemAPI | CTS (APIs currently only callable via dlsym() : b/376759605) |
Platform | cc_test with TEST_MAPPING . Not required, but you wouldn't |
: : really skip writing tests, would you? : |
All APIs must be covered by the appropriate test suite. See the table above.
Make a best-effort attempt to cover the API behavior thoroughly.
Add query APIs if needed to test property-setting APIs with no other observable behavior (and consider them either way if they may be useful for developers). This is not a strict requirement, but is preferred without a good reason not to.
For example:
typedef struct AFoo AFoo; void AFoo_setBar(AFoo* _Nonnull foo, bool bar); bool AFoo_getBar(const AFoo* _Nonnull foo); void AFoo_act(const AFoo* _Nonnull foo);
Whenever possible, a test should be written for AFoo_setBar()
which asserts a behavior change in AFoo_act()
. When that is not possible (for example, APIs which set hints for performance infrastructure often aren't guaranteed to do anything other than set the hint flag), AFoo_getBar()
can be used to test AFoo_setBar()
instead.
Exceptions to testing requirements may be granted in the case of infrastructure gaps, or on a case-by-case basis for test-hostile APIs. In such cases there is still a requirement for a test which calls the API even if no behavior can be asserted.
The CTS test must be built against the NDK proper
No includes of any system headers. This includes anything under //frameworks
, //system
, mainline modules, etc. With rare exceptions, CTS tests for NDK APIs should not include (include_dirs
, shared_libs
, static_libs
, etc) anything outside the NDK or gtest.
A minimal set of dependencies for CTS tests improves CTS's ability to verify that NDK headers are usable. If a CTS test has //not/included/in/ndk
on the include path, the test can pass even if the NDK headers require headers that the NDK does not ship.
Must set an sdk_version
in the Android.bp (LOCAL_SDK_VERSION
for Android.mk) for the test
Tests must exercise the C APIs themselves, in addition to the C++ wrapper if one is present. The C API is the interface to the system and needs to work as documented, and the C++ wrapper could alter behavior. All exposed APIs must be tested directly.
Tip: Negative tests for APIs which abort on failure can still be tested using death tests.
Even if the only test that can be written is a test that the API can be called, that is still a valuable test because it proves that the API was exposed to the NDK. Whenever possible though, the test should verify some observable behavior, even if that means adding more APIs to query state.
Non-NDK APIs cannot be tested by CTS but still require tests.
Tests must exercise the C APIs themselves, in addition to the C++ wrapper if one is present. The C API is the interface to the system and needs to work as documented, and the C++ wrapper could alter behavior. All exposed APIs must be tested directly.
Prefer new APIs to substantially changing behavior or meaning. It is easy to tell when new APIs are added to the platform, whereas behavior changes are only surfaced in documentation but not otherwise guarded by lint or other automated checks. Behavior changes can be appropriate when backwards compatibility or overall API meaning is preserved.
When a behavior change is required, consider enabling the new behavior based on targetSdkVersion
. This helps developers maintain bug compatibility, but note that new APIs are still preferred because the app can only have a single targetSdkVersion
and the developer's dependencies could still rely on the old behavior.
NDK documentation is published from the contents of the NDK whenever a release is shipped. See go/edit-ndk-docs for more information on editing and previewing the NDK documentation. API authors do not need to manually update DAC.
If error return codes are being used, all possible errors must be listed as well, akin to how the man pages do.
If your API returns an errno
that is produced by a libc call in the implementation, you may link the relevant man7.org page rather than listing every possible errno
explicitly, but do be specific about the circumstances when it is not clear. For example, if an API that doesn't obviously require any permissions can return EPERM
, you must explain the conditions that will return EPERM
.
Thread-safe and thread-hostile objects/methods must be called out explicitly as such.
Object lifetime must be documented for anything that's not a standard new/free pair.
If ref counting is being used, methods that acquire/release a reference must be documented as such.
Character encoding must be documented for any string handling. UTF-8 is strongly preferred and any other choice should come with significant justification.
If there is no encoding because the data is a string of arbitrary bytes, use uint8_t*
instead of char*
. char*
should only be used for text.
For NDK APIs, documentation must be in Doxygen syntax. To appear on DAC, all documentation (including @file
) must be contained by a @defgroup
or @addtogroup
block.
Doxygen is not required for APEX, but API documentation in some form is. Prefer Doxygen when adding new documentation. Note to API reviewers: non-Doxygen API documentation in APEX APIs is non-blocking.
Doxygen comments for NDK APIs must include "Introduced in API <number>
.” Note that the documentation is not macro-replaced so should use the actual number instead of the codename since that's what users need to know for their build.gradle files. Non-NDK APIs should consider doing this as well.
Potential pending exceptions should be limited to runtime issues such as OutOfMemory
or ClassCastException
unless clearly documented what the other potential exceptions are and why they may occur (see also: go/android-api-guidelines#throws).
Opaque structs allow for size changes more naturally and are generally less fragile.
An exception to this is if the type is inherently fixed. For example ARect
is not an opaque struct as a rectangle is inherently 4 fields: left, top, right, bottom. Defining an HTTP header as a struct { const char* key; const char* value; }
would also be appropriate, as HTTP headers are inherently fixed.
If a non-opaque struct is needed and the previous exception does not apply, explicitly versioned structs may be used. For example:
#include <stdalign.h> #include <stddef.h> #include <stdint.h> // The data contained by the first version of the struct. typedef struct AFooV1 { void* data; } AFooV1; // Additional data added in a later release. typedef struct AFooV2 { void* other_data; } AFooV2; // The struct used by the APIs. Must be passed or returned as a pointer so the // size of the argument/return type does not depend on the OS version of the // device. The documentation must warn the user that the size of the struct can // change, and that only fields up to `version` will be initialized. New // versions of the OS must continue initializing old fields to preserve app // compat. typedef struct AFoo { alignas(alignof(max_align_t)) uint32_t version; AFooV1 v1; AFooV2 v2; // v3, v4, etc. } AFoo;
Size versioned structs (structs whose first member is sizeof(the_struct)
) are not allowed.
Callers only ever see the sizeof()
of the struct they were compiled against. This makes it harder to alter behavior for previous versions, as they cannot sizeof()
a different version of the header, and it introduces hidden behavior changes. Changing the compiled NDK version will alter the versioned behavior of these structs without warning.
More difficult to document. “This field is available when the structure size is 12” does not work because the size of the struct will often depend on the architecture (pointer size, alignment, and padding).
More error prone. It's harder to spot an accidental read of a field that is not initialized for a given struct version.
Different build artifacts may, and often do, have different implementations of malloc/free.
For example: Chrome may opt to use a different allocator for all of Chrome’s code, however the NDK libraries it uses will be using the platform’s default allocator. These may not match, and cannot free memory allocated by the other.
If a library allocates something, such as an opaque struct, it must also provide a free function.
If a library takes ownership of an allocation, it must also take the free function.
If a header defines an enum or constant, that value is forever.
For defined steppings in a range (such as priority levels or trim memory levels), leave gaps in the numberings for future refinement.
Enums do not need to specify explicit values for all members, but may choose to do so. The values assigned to each member must remain consistent across all releases, so it may be easier to assign values explicitly rather than preserving order manually.
For configuration data, such as default timeouts, use a getter method or an extern variable instead.
Primitive types like long can have varying sizes. This can lead to issues on 32-bit vs. 64-bit builds.
In general, prefer the fixed-size types int32_t
, int64_t
, etc...
For counts of things, use size_t
.
If libc has an existing preference, use that instead (eg, use pid_t
if you’re taking a process ID)
Use int32_t
or int64_t
for legacy enum params/returns that do not have an explicit backing type.
int32_t
.Avoid off_t
.
off_t
can vary based on the definition of _FILE_OFFSET_BITS
. APIs must use off64_t
instead of off_t
.Prefer AClassName
for the type (“A” being the prefix for “Android”). The provides a C-compatible pseudo-namespace for NDK APIs.
Typedef structs by default, for example:
typedef struct AIBinder AIBinder;
Class methods should follow AClassName_methodName
naming (lower case beginning the method part of the name regardless of the naming style of the backing implementation).
Callbacks should follow a AClassName_callbackNameCallback
naming convention.
Include the “Callback” suffix.
“Nested” classes should also follow a AClassName_SubType
naming convention
Constants should follow ACLASSNAME_CONSTANT_NAME
naming. Even for enum scoped constants the ACLASSNAME_
prefix is needed because C introduces all enum constants to the global scope.
Treat acroynms as words in class and method names, preferring AWidget_getHttpCookie()
to AWidget_getHTTPCookie()
.
Macros (which should be used sparingly) should follow the same rules as constants: ACLASSNAME_MACRO_NAME
. If the macros are not associated with a specific “class” of the API, use ANAMESPACE_MACRO_NAME
instead, e.g. ACAMERA_MACRO_NAME
.
Exceptions to this rule may be made for cases where the macro is expected to be used very frequently. In that case, there must be a way for the user to opt-out of the non-namespaced macros. This can be done either with a configuration macro (e.g. ANAMESPACE_ENABLE_HELPER_MACROS
), or by keeping the macros in their own header (e.g. android/namespace/macros.h
) which is not included from any of the other API headers.
Free functions (APIs that are not “methods” of a type in the API) should follow ANamespaceName_functionName
naming.
Jetpack should prefer C++ over C as much as possible, however if a C API is being made anyway the platform naming conventions should be followed with the following exceptions:
Avoid A prefix, use AX instead
Libraries must be named matching their fully-qualified package ID. This is needed to avoid naming collisions. For example, libnativehelper.so
should have been named (the library predates the rule) libcom.android.art.nativehelper.so
.
All other naming conventions follow the platform rules.
As in, always add AObject_fromJava(JNIEnv, jobject)
to the native API surface rather than having a long Object#getNativePointer()
in the SDK.
Similarly add jobject AObject_toJava(JNIEnv, AObject*)
to the native API surface rather than new Object(long nativePtr);
in the SDK.
If the native and Java types have the same name, the suffix should be just _fromJava
or toJava
. If the type names differ, the Java type name should also be in the suffix. e.g. _fromJavaSurface
.
Lifetime requirements of Java objects must be explicit in the documentation. If the API does or does not acquire a reference to the Java object, say so.
It is recommended to have JNI interop APIs.
To the extent possible, the Java objects and the native objects should have lifecycles native to their respective environments & independent of the other environment.
That is, if a native handle is created from a Java object then the Java object’s lifecycle should not be relevant to the native handle. Similarly, if a Java object is created from a native object the native object should not need to out-live the Java one.
Typically this means both the native & Java types should sit on a ref-count system to handle this if the underlying instance is shared.
If the interop just does a copy of the data (such as for a trivial type), then nothing special needs to happen.
Exceptions can be made if it’s impractical for the underlying type to be referenced counted and it’s already scoped to a constrained lifecycle. For example, AParcel_fromJavaParcel adopts the lifecycle of the jobject and as such does not have a corresponding free. This is OK as the lifecycle of a Parcel is already scoped to the duration of a method call in general anyway, so a normal JNI LocalRef will have suitable lifetime for typical usage.
_jni
suffix.JNI interop APIs should be in their own header with a trailing _jni
suffix.
Example: asset_manager.h
and asset_manager_jni.h
This helps apps to keep a clean JNI layer in their own code.
Any exceptions thrown should be left pending for the caller to handle. Most potential pending exceptions types must be documented. See go/android-ndk-api-guidelines#well-documented.
Inline functions may be included in headers when they are a trivial wrapper presenting a de-facto overload of another API.
For example, if an API exists that takes a pointer to a memory mapped file, an API that takes an FD that maps the file and calls the other API may be an inline.
Avoid using inlines wherever the implementation is complicated or would benefit from being written in C++ (Jetpack libraries may use C++ in headers).
Inline code cannot use __builtin_available()
. Inlines which call other APIs should be marked with __INTRODUCED_IN()
matching the APIs they call. This looks odd because an inline function is not actually “introduced” in any version of the OS, but doing so allows you to defer the choice between __builtin_available()
and build-time guards to the caller. See Avoiding repetition of API guards for an example and further explanation.
If a method cannot fail, the return type should simply be the output or void if there is no output.
For allocation/accessor methods where there's only a single failure mode, return the pointer and use NULL for errors.
Examples:
// Returns NULL when OOM. AMediaDataSource* _Nullable AMediaDataSource_new(); // Returns NULL when the calling thread has no assigned Looper. ALooper* _Nullable ALooper_forThread();
In both of these examples a _Nonnull
return type and an implementation that aborts is also acceptable. Whichever you choose, document it.
For methods with a non-trivial error possibility or multiple unique error types, use an error return value and use an output parameter to return any results
size_t AList_getSize(const AList*);
status_t AList_getSize(const AList*, const size_t* outSize);
For APIs where the only error possibility is the result of a trivial check, such as passing nullptr
to a _Nonnull
argument, the implementation should abort
rather than return an error.
Tip: Such APIs can still be tested using death tests.
A common case where aborts should not be used is for invalid flags or enum values passed to an API that may have new valid inputs added in future releases. Developers should be able to pass new values to the API on an old device and receive a “not supported” error rather than needing to check for availability before making the call to avoid a crash.
For example, in system_fonts.cpp:
bool AFont_isItalic(const AFont* font) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr passed as font argument"); return font->mItalic; }
Error return types should have a toString
method to stringify them.
AFoo
type, then the recommended naming for stringification of the error is AFoo_resultToString
.The returned strings should be constants. This is to prevent needing a free method for the error string.
Invalid inputs to the stringify method should return nullptr. This allows new error codes to be added that do not cause a crash on old systems if used explicitly.
Stable API boundaries are C only (the C++ ABI is not stable), so C++ exceptions are not allowed to cross the API boundary. You must use an alternative method of error handling for your API (see above).
If your implementation may raise exceptions, they must be caught and handled (probably by translating to error codes) at the API boundary.
void*
for caller-provided context.setCallback
terminologyIf the API only allows for a single callback to be set, use “setCallback” terminology
In such a case, the void*
context argument must be passed to both the setCallback
method as well as passed to the callback when invoked.
To clear the callback, allow setCallback
to take NULL
to clear.
register
/unregister
terminologyIf the API allows for multiple callbacks to be set, use register/unregister terminology
In such a case, the void*
context argument is needed on registerCallback
and unregisterCallback
and must be passed to the callback when invoked.
Register & unregister must use the pair of function pointer + void*
as the unique key. In other words, multiple registrations of the same function pointer should be allowed with different void*
user data.
_Nonnull
or _Nullable
Document parameters & return values with _Nonnull
or _Nullable
as appropriate.
Note: These cannot practically be added to any existing headers because Clang will diagnose all APIs in the same header as soon as the first annotation is introduced. These annotations should be used for any new headers. If possible, revisit existing headers to add annotations for existing APIs so new APIs can be made safer in the future.
These are defined in clang, see https://clang.llvm.org/docs/AttributeReference.html#nullability-attributes
const
const
when appropriateFor example if a method is a simple getter for an opaque type, the struct pointer argument should be marked const
.
Example:
size_t AList_getSize(const AList*);
size_t AList_getSize(AList*);
_create
over _new
Prefer _create
over _new
as it works better with _createFrom
specializations
_destroy
_acquire
/_release
over _inc
/_dec
for ref count naming