在 Android 平台,系统提供的一些基础能力都是为了使用 Java 或 Kotlin 来实现应用程序,或是通过 JNI 再调用一些 C/C++ 来进行混合开发。但在 Android NDK 中提供了一种纯 Native 启动 Activity 的方案。这种方案主要应用于游戏(游戏通常使用自定义渲染逻辑,通常用 OpenGL 或 Vulkan 编写)、以及一些 C/C++ 实现渲染和交互的应用程序。
NDK 示例:native-activity
NativeActivity 文档:NativeActivity
项目配置
该项目需要下载 NDK、cmake 和 LLDB,可直接从 Android Stuido 的 SDK Manager 中直接下载对应的内容,这里不展开介绍。
首先,在项目的 manifest 文件中,进行以下配置:
xml
<activity android:name="android.app.NativeActivity"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:exported="true">
<!-- Tell NativeActivity the name of our.so -->
<meta-data android:name="android.app.lib_name"
android:value="native-activity" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
首先, 标签的 android:name
需设置为 "android.app.NativeActivity";因为项目不包含任何 Java 代码,所以需要在 标签下添加 android:hasCode="false" ;
NativeActivity 可以配置两个 <meta-data>
:
android.app.lib_name
:可选的元数据,可以在此组件的清单中,指定 Native 共享库的名称,如果没有指定,使用 "main" 。android.app.func_name
:可选元数据,可以位于此组件的清单中,在 META_DATA_LIB_NAME 本机代码中指定此 NativeActivity 的主入口点的名称。如果没有指定,使用 "ANativeActivity_onCreate"。
android.app.lib_name 配置的值是 native-activity,native-activity 这个字符串来自于 gradle 中配置的 cmake 配置文件中定义的 C/C++ 动态库文件名称。
首先在 build.gradle 文件中配置的 cmake 文件路径:
groovy
externalNativeBuild {
cmake {
path "${project.repoRoot}/CMakeLists.txt"
}
}
项目根目录下的 CMakeList.txt 中配置了 native-activity 的编译信息:
cmake
#...
add_library(native-activity SHARED main.cpp)
#...
创建 NativeActivity 最好的方式是使用 native_app_glue
。这部分可以直接从 NDK 的示例代码中复制过去。
cmake
cmake_minimum_required(VERSION 3.4.1)
# 构建静态库 native_app_glue
add_library(native_app_glue STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
# 构建 app 的共享库
# 导出 ANativeActivity_onCreate()
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
# 添加名为 native-activity 的共享库,入口是 main.app
add_library(native-activity SHARED main.cpp)
# 指定 native-activity 包含的头文件路径(native_app_glue)。
target_include_directories(native-activity PRIVATE
${ANDROID_NDK}/sources/android/native_app_glue)
# 添加库依赖
target_link_libraries(native-activity
android
native_app_glue
EGL
GLESv1_CM
log)
因为 manifest 配置文件中 android.app.func_name
未指定方法入口,所以会通过 native_app_glue 静态库的实现 android_native_app_glue.c
,默认执行 ANativeActivity_onCreate
函数。
ANativeActivity_onCreate
函数的主要逻辑是使用 pthread_create 创建线程,执行 android_app_entry 函数:
c++
static struct android_app* android_app_create(ANativeActivity* activity, void* savedState, size_t savedStateSize) {
// ...
pthread_create(&android_app->thread, &attr, android_app_entry, android_app);
// ...
}
android_app_entry 函数用来创建 Looper,并执行 android_main 函数:
c
static void* android_app_entry(void* param) {
// ...
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL,
&android_app->cmdPollSource);
android_app->looper = looper;
pthread_mutex_lock(&android_app->mutex);
android_app->running = 1;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
android_main(android_app);
android_app_destroy(android_app);
return NULL;
}
android_main 函数来自于 main.cpp,这样就执行到了 native_activity 库中的函数主入口:
c++
void android_main(struct android_app* state) {
struct engine engine {};
// 初始化 engine 结构体并清零
memset(&engine, 0, sizeof(engine));
state->userData = &engine;
state->onAppCmd = engine_handle_cmd;
state->onInputEvent = engine_handle_input;
engine.app = state;
// 准备监控加速度计
engine.sensorManager = AcquireASensorManagerInstance(state);
engine.accelerometerSensor = ASensorManager_getDefaultSensor(
engine.sensorManager, ASENSOR_TYPE_ACCELEROMETER);
engine.sensorEventQueue = ASensorManager_createEventQueue(
engine.sensorManager, state->looper, LOOPER_ID_USER, nullptr, nullptr);
if (state->savedState != nullptr) {
// 从先前的保存状态开始;从中恢复
engine.state = *(struct saved_state*)state->savedState;
}
// 循环等待要处理的事情。
while (true) {
// 读取所有待处理事件。
int ident;
int events;
struct android_poll_source* source;
// 如果不是动画状态,我们将永远阻塞等待事件。
// 如果是动画状态,我们循环直到读取所有事件,然后继续绘制下一帧动画。
while ((ident = ALooper_pollAll(engine.animating ? 0 : -1, nullptr, &events, (void**)&source)) >= 0) {
// 处理此事件。
if (source != nullptr) {
source->process(state, source);
}
// 如果传感器有数据,现在处理它。
if (ident == LOOPER_ID_USER) {
if (engine.accelerometerSensor != nullptr) {
ASensorEvent event;
while (ASensorEventQueue_getEvents(engine.sensorEventQueue, &event,
1) > 0) {
LOGI("accelerometer: x=%f y=%f z=%f", event.acceleration.x,
event.acceleration.y, event.acceleration.z);
}
}
}
// 检查是否退出。
if (state->destroyRequested != 0) {
engine_term_display(&engine);
return;
}
}
if (engine.animating) {
// 完成事件处理,绘制下一帧动画。
engine.state.angle += .01f;
if (engine.state.angle > 1) {
engine.state.angle = 0;
}
// 绘制受限于屏幕更新速率,因此此处无需计时。
engine_draw_frame(&engine);
}
}
}
这里通过 engine_draw_frame 函数绘制下一帧:
c++
static void engine_draw_frame(struct engine* engine) {
if (engine->display == nullptr) {
// No display.
return;
}
// Just fill the screen with a color.
glClearColor(((float)engine->state.x) / engine->width, engine->state.angle,
((float)engine->state.y) / engine->height, 1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(engine->display, engine->surface);
}
首先,代码检查 engine 结构体中的 display 成员是否为 nullptr(空指针),即检查显示器是否存在。如果不存在,就直接返回,不执行后续的绘制操作。
然后使用 glClearColor 函数设置背景颜色。这里的颜色由 engine->state 结构体中的 x、angle 和 y 成员决定。 engine->state.x、engine->state.y 分别表示屏幕的 x 和 y 坐标,engine->state.angle 用于控制颜色的变化。 将这些值映射到背景颜色的 R(红色)、G(绿色)和 B(蓝色)分量,同时 alpha 通道设为 1(不透明)。 使用 glClear 函数清空颜色缓冲区,以设置的背景颜色填充整个屏幕。
最后,使用 eglSwapBuffers 函数交换显示缓冲区,实现将绘制的图像显示到屏幕上。 总的来说,这段代码负责绘制一帧图像,填充屏幕背景颜色并将其显示在屏幕上。
至此为止,应用就已经启动并且能够展示出一个粉色的页面了。