NDK系列-如何使用C/C++编写带EGL功能的NativeActivity

2,184 阅读6分钟

NDK系列-如何使用C/C++编写带EGL功能的NativeActivity

1、示例地址:

https://github.com/android/ndk-samples/tree/master/native-activity

2、示例功能:

1、示例应用会使用EGL在整个屏幕上渲染一种颜色,然后根据检测到的运动,随之更改局部的颜色。

2、示例应用只需要C/C++代码编写,不包含任Java源代码,但Java 编译器仍然会创建一个可由虚拟机运行的可执行存根。该存根用作 .so 文件中实际原生程序的封装容器。

3、开发步骤

限制最低SDK版本:

<uses-sdk android:minSdkVersion="9" />

声明应用只包含原生代码,而不含 Java 代码:

<application android:hasCode="false">

声明android.app.NativeActivity类,作为Java层进入Native层的入口:

<activity android:name="android.app.NativeActivity">
</activity>

使用android:value指定NativeActivity类所使用的共享库名称:

<meta-data android:name="android.app.lib_name"
        android:value="native-activity" />

名称需是Android.mk或CMakeList.txt指定的共享库的名称。

Gradle配置CMake编译方式

android {
    ndkVersion '21.0.6113669'
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments '-DANDROID_STL=c++_static'
            }
        }
    }
    externalNativeBuild {
        cmake {
            version '3.10.2'
            path 'src/main/cpp/CMakeLists.txt'
        }
    }
}

编写CMakeList.txt

使用ndk中的native_app_glue功能源码实现Java与C/C++之间的连接。

cmake_minimum_required(VERSION 3.4.1)
set(${CMAKE_C_FLAGS}, "${CMAKE_C_FLAGS}")
//添加关联的静态库
add_library(native_app_glue STATIC
    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall -Werror")
set(CMAKE_SHARED_LINKER_FLAGS
    "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
add_library(native-activity SHARED main.cpp)
//目标关联目录
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)

关联库native_app_glue的Android.mk文件:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE:= android_native_app_glue
LOCAL_SRC_FILES:= android_native_app_glue.c
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_EXPORT_LDLIBS := -llog -landroid
# The linker will strip this as "unused" since this is a static library, but we
# need to keep it around since it's the interface for JNI.

LOCAL_EXPORT_LDFLAGS := -u ANativeActivity_onCreate

include $(BUILD_STATIC_LIBRARY)

编写main.cpp,实现具体业务功能

这是使用android_native_app_glue的本地应用程序的主入口点。 它在自己的线程中运行,有自己的事件循环来接收输入事件和做其他事情。

void android_main(struct android_app* state) {
	//TODO 
}

4、流程解析

4.1、NativeActivity.java

实现SurfaceHolder.Callback2,InputQueue.Callback, OnGlobalLayoutListener,用于处理surface、input和view相关的逻辑。

implements SurfaceHolder.Callback2,InputQueue.Callback, OnGlobalLayoutListener

默认使用的共享库和函数名称,若清单文件有配置则使用配置的:

String libname = "main";
String funcname = "ANativeActivity_onCreate";

关键代码,加载原生代码,获取原生句柄:

BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader();
String path = classLoader.findLibrary(libname);

mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(),
        getAbsolutePath(getFilesDir()), getAbsolutePath(getObbDir()),
        getAbsolutePath(getExternalFilesDir(null)),
        Build.VERSION.SDK_INT, getAssets(), nativeSavedState,
        classLoader, classLoader.getLdLibraryPath());

此方法将会调用android_native_app_glue.c的ANativeActivity_onCreate函数:

JNIEXPORT
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState,
                              size_t savedStateSize) {
    LOGV("Creating: %p\n", activity);
    activity->callbacks->onDestroy = onDestroy;
    activity->callbacks->onStart = onStart;
    activity->callbacks->onResume = onResume;
    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
    activity->callbacks->onPause = onPause;
    activity->callbacks->onStop = onStop;
    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
    activity->callbacks->onLowMemory = onLowMemory;
    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
    activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;

    activity->instance = android_app_create(activity, savedState, savedStateSize);
}

其中的回调,均是在NatveActivity中Activity生命周期、surface、input、view相关的原生代码调用:

private native String getDlError();
private native void unloadNativeCode(long handle);
private native void onStartNative(long handle);
private native void onResumeNative(long handle);
private native byte[] onSaveInstanceStateNative(long handle);
private native void onPauseNative(long handle);
private native void onStopNative(long handle);
private native void onConfigurationChangedNative(long handle);
private native void onLowMemoryNative(long handle);
private native void onWindowFocusChangedNative(long handle, boolean focused);
private native void onSurfaceCreatedNative(long handle, Surface surface);
private native void onSurfaceChangedNative(long handle, Surface surface,
        int format, int width, int height);
private native void onSurfaceRedrawNeededNative(long handle, Surface surface);
private native void onSurfaceDestroyedNative(long handle);
private native void onInputQueueCreatedNative(long handle, long queuePtr);
private native void onInputQueueDestroyedNative(long handle, long queuePtr);
private native void onContentRectChangedNative(long handle, int x, int y, int w, int h);

4.2、android_native_app_glue.c

android_native_app_glue库是Java代码NativeActivity和原生代码main.cpp中android_main函数入口的连接中介,这也是这个库为什么取名为胶水glue的原因。

调用路径为:

1、NativeActivity的loadNativeCode函数调用android_native_app_glue的ANativeActivity_onCreate函数。
2、ANativeActivity_onCreate函数调用android_app_create函数,进而调用android_app_entry函数,最后调用main.c的android_main函数,android_main函数则由工程师编写具体的业务代码。

其他Activity生命周期、surface、input、view的函数同理类推。

4.3、main.cpp

编写业务代码,此处为监听传感器、输入事件、View树的变化等,进行EGL的绘图功能,将屏幕全屏绘制成某个颜色。

void android_main(struct android_app* state) {
    struct engine engine{};

    memset(&engine, 0, sizeof(engine));
    //用户信息
    state->userData = &engine;
    //应用指令
    state->onAppCmd = engine_handle_cmd;
    //输入事件
    state->onInputEvent = engine_handle_input;
    engine.app = state;

    // Prepare to monitor accelerometer
    // 监听加速器
    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) {
        // We are starting with a previous saved state; restore from it.
        engine.state = *(struct saved_state*)state->savedState;
    }

    // loop waiting for stuff to do.

    while (true) {
        // Read all pending events.
        int ident;
        int events;
        struct android_poll_source* source;

        // If not animating, we will block forever waiting for events.
        // If animating, we loop until all events are read, then continue
        // to draw the next frame of animation.
        // 阻塞
        while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, nullptr, &events,
                                      (void**)&source)) >= 0) {

            // Process this event.
            if (source != nullptr) {
                source->process(state, source);
            }

            // If a sensor has data, process it now.
            //处理传感器数据
            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);
                    }
                }
            }

            // Check if we are exiting.
            if (state->destroyRequested != 0) {
                engine_term_display(&engine);
                return;
            }
        }

        if (engine.animating) {
            // Done with events; draw next animation frame.
            engine.state.angle += .01f;
            if (engine.state.angle > 1) {
                engine.state.angle = 0;
            }

            // Drawing is throttled to the screen update rate, so there
            // is no need to do timing here.
            // 绘制当前帧
            engine_draw_frame(&engine);
        }
    }
}

应用状态的数据引擎,包含应用信息、传感器信息以及EGL图形绘制信息:

/**
 * Shared state for our app.
 */
struct engine {
    struct android_app* app;

    ASensorManager* sensorManager;
    const ASensor* accelerometerSensor;
    ASensorEventQueue* sensorEventQueue;

    int animating;
    //显示图形
    EGLDisplay display;
    //存储图形
    EGLSurface surface;
    //上下文
    EGLContext context;
    int32_t width;
    int32_t height;
    struct saved_state state;
};

5、EGL绘图

EGL 是渲染 API(如 OpenGL ES)和原生窗口系统之间的接口。 通常来说,OpenGL 是一个操作 GPU 的 API,它通过驱动向 GPU 发送相关指令,控制图形渲染管线状态机的运行状态,但是当涉及到与本地窗口系统进行交互时,就需要这么一个中间层,且它最好是与平台无关的。 因此 EGL 被设计出来,作为 OpenGL 和原生窗口系统之间的桥梁。

使用 EGL 绘图的基本步骤:

Display(EGLDisplay) 是对实际显示设备的抽象。
Surface(EGLSurface)是对用来存储图像的内存区域FrameBuffer 的抽象,包括 Color Buffer, Stencil Buffer ,Depth Buffer。
Context (EGLContext) 存储 OpenGL ES绘图的一些状态信息。

使用EGL的绘图的一般步骤:

获取 EGL Display 对象:eglGetDisplay()
初始化与 EGLDisplay 之间的连接:eglInitialize()
获取 EGLConfig 对象:eglChooseConfig()
创建 EGLContext 实例:eglCreateContext()
创建 EGLSurface 实例:eglCreateWindowSurface()
连接 EGLContext 和 EGLSurface:eglMakeCurrent()
使用 OpenGL ES API 绘制图形:gl_*()
切换 front buffer 和 back buffer 送显:eglSwapBuffer()
断开并释放与 EGLSurface 关联的 EGLContext 对象:eglRelease()
删除 EGLSurface 对象
删除 EGLContext 对象
终止与 EGLDisplay 之间的连接

为当前连接初始化OpenGL ES 和EGL:

/**
 * Initialize an EGL context for the current display.
 */
static int engine_init_display(struct engine* engine) {
    // initialize OpenGL ES and EGL

    /*
     * Here specify the attributes of the desired configuration.
     * Below, we select an EGLConfig with at least 8 bits per color
     * component compatible with on-screen windows
     */
    const EGLint attribs[] = {
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_BLUE_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_RED_SIZE, 8,
            EGL_NONE
    };
    EGLint w, h, format;
    EGLint numConfigs;
    EGLConfig config = nullptr;
    EGLSurface surface;
    EGLContext context;
    //创建连接
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    //初始化连接
    eglInitialize(display, nullptr, nullptr);

    /* Here, the application chooses the configuration it desires.
     * find the best match if possible, otherwise use the very first one
     */
    eglChooseConfig(display, attribs, nullptr,0, &numConfigs);
    std::unique_ptr<EGLConfig[]> supportedConfigs(new EGLConfig[numConfigs]);
    assert(supportedConfigs);
    eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs);
    assert(numConfigs);
    auto i = 0;
    for (; i < numConfigs; i++) {
        auto& cfg = supportedConfigs[i];
        EGLint r, g, b, d;
        if (eglGetConfigAttrib(display, cfg, EGL_RED_SIZE, &r)   &&
            eglGetConfigAttrib(display, cfg, EGL_GREEN_SIZE, &g) &&
            eglGetConfigAttrib(display, cfg, EGL_BLUE_SIZE, &b)  &&
            eglGetConfigAttrib(display, cfg, EGL_DEPTH_SIZE, &d) &&
            r == 8 && g == 8 && b == 8 && d == 0 ) {

            config = supportedConfigs[i];
            break;
        }
    }
    if (i == numConfigs) {
        config = supportedConfigs[0];
    }

    if (config == nullptr) {
        LOGW("Unable to initialize EGLConfig");
        return -1;
    }

    /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
     * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
     * As soon as we picked a EGLConfig, we can safely reconfigure the
     * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
    //获取配置
    eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
    //创建渲染区域
    surface = eglCreateWindowSurface(display, config, engine->app->window, nullptr);
    //创建渲染上下文
    context = eglCreateContext(display, config, nullptr, nullptr);
    //关联上下文和渲染区域
    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
        LOGW("Unable to eglMakeCurrent");
        return -1;
    }

    eglQuerySurface(display, surface, EGL_WIDTH, &w);
    eglQuerySurface(display, surface, EGL_HEIGHT, &h);

    engine->display = display;
    engine->context = context;
    engine->surface = surface;
    engine->width = w;
    engine->height = h;
    engine->state.angle = 0;

    // Check openGL on the system
    auto opengl_info = {GL_VENDOR, GL_RENDERER, GL_VERSION, GL_EXTENSIONS};
    for (auto name : opengl_info) {
        auto info = glGetString(name);
        LOGI("OpenGL Info: %s", info);
    }
    // Initialize GL state.
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
    glEnable(GL_CULL_FACE);
    glShadeModel(GL_SMOOTH);
    glDisable(GL_DEPTH_TEST);

    return 0;
}

使用EGL绘制当前帧:

/**
 * Just the current frame in the display.
 */
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);
}

推荐阅读:

Android技术堆栈