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 函数交换显示缓冲区,实现将绘制的图像显示到屏幕上。 总的来说,这段代码负责绘制一帧图像,填充屏幕背景颜色并将其显示在屏幕上。

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

相关推荐
后端码匠3 小时前
MySQL 8.0安装(压缩包方式)
android·mysql·adb
梓仁沐白4 小时前
Android清单文件
android
董可伦7 小时前
Dinky 安装部署并配置提交 Flink Yarn 任务
android·adb·flink
每次的天空7 小时前
Android学习总结之Glide自定义三级缓存(面试篇)
android·学习·glide
恋猫de小郭7 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin
flying robot9 小时前
小结:Android系统架构
android·系统架构
xiaogai_gai9 小时前
有效的聚水潭数据集成到MySQL案例
android·数据库·mysql
鹅鹅鹅呢10 小时前
mysql 登录报错:ERROR 1045(28000):Access denied for user ‘root‘@‘localhost‘ (using password Yes)
android·数据库·mysql
在人间负债^10 小时前
假装自己是个小白 ---- 重新认识MySQL
android·数据库·mysql
Unity官方开发者社区10 小时前
Android App View——团结引擎车机版实现安卓应用原生嵌入 3D 开发场景
android·3d·团结引擎1.5·团结引擎车机版