Android NDK NativeActivity

在 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>

  1. android.app.lib_name:可选的元数据,可以在此组件的清单中,指定 Native 共享库的名称,如果没有指定,使用 "main" 。
  2. 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 函数交换显示缓冲区,实现将绘制的图像显示到屏幕上。 总的来说,这段代码负责绘制一帧图像,填充屏幕背景颜色并将其显示在屏幕上。

至此为止,应用就已经启动并且能够展示出一个粉色的页面了。

相关推荐
姑苏风42 分钟前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k4 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小105 小时前
JavaWeb项目-----博客系统
android
风和先行5 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.6 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰7 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶7 小时前
Android——网络请求
android
干一行,爱一行7 小时前
android camera data -> surface 显示
android
断墨先生7 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员9 小时前
PHP常量
android·ide·android studio