【譯】Android NDK API 規範

秋城發表於2021-05-05

【譯】Android NDK API 規範

譯者按:

修改R程式碼遇到Lint tool的報錯,搜到了這篇文件,aosp倉庫地址:Android NDK API Guidelines

975a589 Merge changes Iae957d87,I1c52d7bb by Alan Viverette · 11 days ago master
58e9b5f Project import generated by Copybara. by Android API Council · 9 months ago
c0b835d Initial empty repository by Baligh Uddin · 8 years ago

提交記錄顯示最近才更新的,是官方的NDK應用程式介面規範。軟體架構涉及app與系統框架的native部分,語言是c/c++。java和kotlin的編碼規範在隔壁的Android API Guidelines文件中。

考慮到我們開發中傾向直接使用和理解英文,一些術語、關鍵詞此處也迎合這種習慣不作翻譯。本文是手冊性質文件,比較直白,所以本文僅給出標題的翻譯。如果讀者為熟悉Android native開發,相信在看過標題之後,其段落內容會很快得到理解,所以不必要做損失本味的翻譯工作了。

以下是原文。


目錄

API 規範

有的平臺程式碼一開始就沒有嚴格的規範指導,導致一些既有的API可能與當前的規範衝突,這是讓本文規範落地的難點之一。在某些情況下,正確的選擇是與周圍的程式碼風格保持一致,而不是此處列出的理想規則。

本規範尚在迭代中,將來會隨著API review而新增其他規則。

相容性

有關這些構建要求的更多資訊,請參見: https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md

標頭檔案必須是C相容的

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.

  1. Wrap in __BEGIN_DECLS and __END_DECLS from sys/cdefs.h
  2. 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.

For APEX APIs, this requirement can be relaxed. The ABI exposed must still be C (the header must be contained in an extern “C” block), but the header can make use of the following C++ features:

  1. Sized-enums which can then also be used as parameters & return values safely.

For example:

enum Foo : int16_t { One, Two, Three };
Foo transformFoo(Foo param);

API 必須標記 API Level

Wrap new methods in #if __ANDROID_API__ >= <api_level> & #endif pairs

Mark methods with __INTRODUCED_IN(<api_level>);

Example:

#if __ANDROID_API__ >= 30

binder_status_t AIBinder_getExtension(AIBinder* binder, AIBinder** outExt) __INTRODUCED_IN(30);

#endif

型別和列舉應與新增的API level 一起記錄

  1. Types & enum declaration MUST NOT be guarded by #if __ANDROID_API__ >= <api_level>. This makes opportunistic usage via dlsym harder to do.
  2. Instead their documentation should include what API level they were added in with a comment saying “Introduced in API <number>.”

匯出的 map 必須標記 API level

  1. Libraries must have a <name>.map.txt with the symbol being exported
  2. Symbols must be marked with # introduced=<api_level>
    • Can be either on each individual symbol or on a version block.

NDK API 必須有 CTS 測試用例

  1. All NDK APIs are required to be covered by at least one CTS test.
  2. The CTS test must be built against the NDK proper
    1. No includes of framework/ paths
    2. Must set an sdk_version in the Android.bp (LOCAL_SDK_VERSION for Android.mk) for the test

文件

API 必須有充分的文件記錄

  1. 如果使用了錯誤返回碼,則還必須列出可能的錯誤。
  2. 執行緒安全和不安全的物件、方法也都必須顯式地呼叫。
  3. 對於任何不是標準 new/free 配對的物件,都必須記錄其生存期。
  4. 如果使用了引用計數,acquire/release引用的方法必須這樣記錄documented**。

ABI 穩定性準則

首選不透明結構體

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.

malloc/free 方法必須來自同一個編譯依賴庫

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.

  1. If a library allocates something, such as an opaque struct, it must also provide a free function.
  2. 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.

  1. For defined steppings in a range (such as priority levels or trim memory levels), leave gaps in the numberings for future refinement.
  2. For return codes, have static_asserts or similar to ensure the values are never accidentally changed.
  3. 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.

  1. In general, prefer instead the fixed-size types int32_t, int64_t, etc...
  2. For counts of things, use size_t
  3. If libc has an existing preference, use that instead (eg, use pid_t if you’re taking a process ID)
  4. Use int32_t or int64_t for enum params/returns.
    • The backing size of an enum is technically up to the compiler. As such, even if a parameter or return value represents an enum use instead a fixed-type like int32_t.
    • While annoying, a C++ wrapper can also trivially fix this. The compatibility risks are not worth it otherwise.
  5. Avoid off_t.
    • The size of an off_t can vary based on the definition of _FILE_OFFSET_BITS. API MUST NOT use off_t and MUST use off64_t instead.

API 設計規範

命名規則

  1. Prefer AClassName for the type
  2. Typedef by default, for example:
struct AIBinder;
typedef struct AIBinder AIBinder;
  1. Class methods should follow AClassName_methodName naming
  2. Callbacks should follow a AClassName_CallbackType naming convention.
  3. “Nested” classes should also follow a AClassName_SubType naming convention

JNI

JNI 互動方法應只放在 NDK 中

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.

It is recommended to have JNI interop APIs.

Java物件和native物件應使用各自獨立的生命週期

Java物件和native物件應儘可能具有其各自環境固有的生命週期,並且與其他環境無關。

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 互動 API 應在它們自己的標頭檔案中帶有 _jni 字尾。

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

Errno 值不應穿過 JNI 邊界

Errno values, such as EINVAL, are only constant for a given arch+abi combination. As such, they should not be propagated across the JNI boundary as specific literal numbers as the Java-side would lose its arch/abi portability. Java code can, however, use the OsConstants class to refer to errno values. As in, if AFOO_ERROR_BAR is defined as being EINVAL, then it must only be referred to by EINVAL and not by the literal constant 22.

Error 處理方式

不可以失敗的方法 {.numbered}

If a method cannot fail, the return type should simply be the output or void if there is no output.

allocation/accessor (分配、訪問)方法{.numbered}

對於分配/訪問方法,其中唯一有意義的失敗是記憶體不足或類似的,返回指標並對錯誤使用NULL。

Example: only failure is ENOMEM: AMediaDataSource* AMediaDataSource_new();

Example: only failure is not set: ALooper* ALooper_forThread();

有重大error可能性的方法{.numbered}

  1. 對於有重大錯誤可能性或多個唯一error型別的方法,使用error返回值並使用輸出引數返回所有結果
  2. 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);

使用 abort message 做狀態檢測 {.numbered}

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 返回型別應該是可字串化的

  1. Error return types should have a to_string method to stringify them.
  2. The returned strings should be constants.
  3. Invalid inputs to the stringify method should return nullptr.

回撥

回撥函式應該是一個裸函式指標。

對於呼叫方提供的上下文,回撥函式應該採用void*

用 “callback(s)” 替代 “listener(s)”

對於單個回撥 API,使用setCallback術語

If 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.

對於多個回撥 API,使用register/unregister術語

If 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.

Nullability

在文件中使用 _Nonnull_Nullable

Document parameters & return values with _Nonnull or _Nullable as appropriate.

These are defined in clang, see https://clang.llvm.org/docs/AttributeReference.html#nullability-attributes

使用 Const

合適的時機使用 const

For 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*);

AFoo_create vs. AFoo_new

首選 _create 而不是 _new

Prefer _create over _new as it works better with _createFrom specializations

銷燬請使用_destroy

引用計數

首選 _acquire/_release 而不是 _inc/_dec for ref count naming

相關文章