Warning: NDK APIs do not support feature flagging.
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.
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.
More information on these build requirements can be found at https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md
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 NDK 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>);
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 API level is already annotated by the __INTRODUCED_IN
macro. With the annotation, the compiler triggers an error when the method shouldn‘t be used (previously it didn’t and that's why guards used to be required). Now, the #if
guard is not only redundant, but also makes it impossible for the clients to use the methods even when it is safe to do so (i.e. the existence of the API on the platform is guaranteed). For example, a client should be able to use APIs whose api_level
is higher than its target sdk version, using the __builtin_available()
macro as shown below.
if (__builtin_available(android __ANDROID_API_R__,*)) { binder_status_t ok = AIBinder_getExtension(binder, &ext); }
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.
Their documentation should include what API level they were added in 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 19).
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.All NDK APIs are required to be covered by at least one CTS test.
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.
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.
Prefer new APIs to substantially changing behavior or meaning. New APIs are self-documenting as when they were 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.
For NDK APIs, documentation must be in Doxygen syntax. To appear on DAC, all documentaion (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.
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 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:
struct AIBinder; 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_CallbackType
naming convention.
“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 do AObject_fromJava(JNIEnv, jobject)
in the NDK rather than having a long Object#getNativePointer()
in the SDK.
Similarly do instead jobject AObject_toJava(JNIEnv, AObject*)
in the NDK 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 NDK & 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.
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
. Using __builtin_available
instead of dlopen
/dlsym
only works if __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
is set. At the time of writing, that configuration is not supported for apps. There are no plans to ever remove the current behavior, nor to make __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
the default, as there are tradeoffs involved that must be made by the app developer. As such, inline code must always work in both configurations, and the only way to do that is with dlopen
/dlsym
.
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 the only meaningful failure is out of memory or similar, return the pointer and use NULL for errors.
Example: only failure is ENOMEM
: AMediaDataSource* AMediaDataSource_new();
Example: only failure is not set: ALooper* ALooper_forThread();
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
For APIs where the only error possibility is the result of a trivial check, such as a basic getter method where the only failure is a nullptr, do not introduce an error handling path but instead abort on bad parameters.
size_t AList_getSize(const AList*);
status_t AList_getSize(const AList*, const size_t* outSize);
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.
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*
must be passed to both the setCallback method as well as passed to the callback on invoke.
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*
is needed on registerCallback, invoke, and unregisterCallback.
Register & unregister must use the pair of function pointer + void as the unique key*. As in, it must support registering the same function pointer with different void* userData.
_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