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

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

相关推荐
宋发元21 分钟前
IPhone 17 Pro Max拍摄专业画质视频教程
android·gradle·iphone
出门吃三碗饭2 小时前
如何在LLM大语言模型上微调来优化数学推理能力?
android·人工智能·语言模型
shaominjin1233 小时前
Android访问OTG文件全解析:从连接到操作的完整指南Android系统访问U盘的实现机制与操作指南
android
游戏开发爱好者86 小时前
HTTPS 内容抓取实战 能抓到什么、怎么抓、不可解密时如何定位(面向开发与 iOS 真机排查)
android·网络协议·ios·小程序·https·uni-app·iphone
Tom4i8 小时前
Android 系统的进程模型
android
介一安全8 小时前
【Frida Android】基础篇9:Java层Hook基础——Hook构造函数
android·网络安全·逆向·安全性测试·frida
杨筱毅8 小时前
【Android】Compose绘制系统 VS 传统View绘制系统
android
介一安全8 小时前
【Frida Android】基础篇10:Native层Hook基础--普通 Hook
android·网络安全·逆向·安全性测试·frida
位步9 小时前
在linux系统中使用通用包安装 Mysql
android·linux·mysql
生莫甲鲁浪戴10 小时前
Android Studio新手开发第二十六天
android·前端·android studio